[
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 4\ntrim_trailing_whitespace = true\ninsert_final_newline = true\nend_of_line = lf\n\n[*.md]\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n---\nname: 🐞 Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**To Reproduce**\nSteps to reproduce the behavior:\n1. Execute '...'\n2. Send a request to '....'\n3. See error\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n\n**Version**\nThe version number of Easegress.\n\n**Configuration**\n* EaseAgent Configuration\n```\n```\n\n**Logs**\n```\nEaseAgent logs, if applicable.\n```\n\n**OS and Hardware**\n - OS: [e.g. Ubuntu 20.04]\n - CPU:[e.g. Intel(R) Core(TM) i5-8265U]\n - Memory: [e.g. 16GB]\n\n**Additional context**\nAdd any other context about the problem here.\n\n\n---\nThanks for contributing 🎉!\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n---\nname: 🚀 Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n\n\n---\nThanks for contributing 🎉!\n"
  },
  {
    "path": ".github/workflows/build.yml",
    "content": "# This is a basic workflow to help you get started with Actions\n\nname: Build & Test\n\n# Controls when the workflow will run\non:\n  # Triggers the workflow on push or pull request events but only for the master branch\n  push:\n    branches: [ master ]\n    paths-ignore:\n      - 'doc/**'\n      - 'resources/**'\n      - '**.md'\n  pull_request:\n    branches: [ master ]\n    paths-ignore:\n      - 'doc/**'\n      - 'resources/**'\n      - '**.md'\n\n  # Allows you to run this workflow manually from the Actions tab\n  workflow_dispatch:\n\n# A workflow run is made up of one or more jobs that can run sequentially or in parallel\njobs:\n  # This workflow contains a single job called \"build\"\n  build:\n    # The type of runner that the job will run on\n    runs-on: ${{ matrix.os }}\n\n    strategy:\n      fail-fast: false\n      matrix:\n        os: [ ubuntu-latest, windows-latest ]\n        java-version: [ 8, 11, 16, 17 ]\n        java-distribution: [ adopt, adopt-openj9 ]\n\n    # Steps represent a sequence of tasks that will be executed as part of the job\n    steps:\n\n\n      # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it\n      - name: Checkout Codebase\n        uses: actions/checkout@v2\n\n      - name: Setup Java Version\n        uses: actions/setup-java@v3.14.1\n        with:\n          java-version: ${{ matrix.java-version }}\n          distribution: ${{ matrix.java-distribution }}\n          architecture: x64\n          cache: 'maven'\n\n      # Runs a single command using the runners shell\n      - name: Build with Maven\n        run: mvn clean package\n"
  },
  {
    "path": ".github/workflows/license-checker.yml",
    "content": "name: License checker\n\non:\n  push:\n    branches:\n      - maste\n    paths-ignore:\n      - 'doc/**'\n      - 'resources/**'\n      - '**.md'\n  pull_request:\n    branches:\n      - master\n    paths-ignore:\n      - 'doc/**'\n      - 'resources/**'\n      - '**.md'\n\njobs:\n  check-license:\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v2\n      - name: Check License Header\n        uses: apache/skywalking-eyes@main\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        with:\n          log: info\n"
  },
  {
    "path": ".gitignore",
    "content": ".idea/\n*.iml\ntarget/\ndependency-reduced-pom.xml\n*.class\n*.swp\n**/*.versionsBackup\n\n.patch\n.classpath\n.factorypath\n.project\n.settings\n.DS_Store\n.vscode\nlogs/\ntmp/\n"
  },
  {
    "path": ".licenserc.yaml",
    "content": "header:\n  license:\n    content: |\n      Copyright (c) 2022, MegaEase\n      All rights reserved.\n      \n      Licensed under the Apache License, Version 2.0 (the \"License\");\n      you may not use this file except in compliance with the License.\n      You may obtain a copy of the License at\n      \n          http://www.apache.org/licenses/LICENSE-2.0\n      \n      Unless required by applicable law or agreed to in writing, software\n      distributed under the License is distributed on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n      See the License for the specific language governing permissions and\n      limitations under the License.\n  paths:\n    - './'\n\n  paths-ignore:\n    - '.editorconfig'\n    - 'install'\n    - 'release'\n    - '**/target/**'\n    - '**/*.xml'\n    - '**/*.yaml'\n    - '**/*.properties'\n    - '**/*.md'\n    - '**/*.iml'\n    - 'LICENSE'\n    - '.github/'\n    - '.git/'\n    - 'doc/'\n    - '**/resources/**'\n    - 'CHANGELOG.md'\n    - '.gitignore'\n    - 'README.md'\n\n  comment: on-failure\n\ndependency:\n  files:\n    - go.mod\n"
  },
  {
    "path": "AOSP-Checkstyles.xml",
    "content": "<?xml version=\"1.0\"?>\n<!DOCTYPE module PUBLIC\n          \"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN\"\n          \"https://checkstyle.org/dtds/configuration_1_3.dtd\">\n\n<!--\n    Checkstyle configuration that checks the Google coding conventions from Google Java Style\n    that can be found at https://google.github.io/styleguide/javaguide.html\n\n    Checkstyle is very configurable. Be sure to read the documentation at\n    http://checkstyle.org (or in your downloaded distribution).\n\n    To completely disable a check, just comment it out or delete it from the file.\n    To suppress certain violations please review suppression filters.\n\n    Authors: Max Vetrenko, Ruslan Diachenko, Roman Ivanov.\n -->\n\n<module name = \"Checker\">\n  <property name=\"charset\" value=\"UTF-8\"/>\n\n  <property name=\"severity\" value=\"warning\"/>\n\n  <property name=\"fileExtensions\" value=\"java, properties, xml\"/>\n  <!-- Excludes all 'module-info.java' files              -->\n  <!-- See https://checkstyle.org/config_filefilters.html -->\n  <module name=\"BeforeExecutionExclusionFileFilter\">\n    <property name=\"fileNamePattern\" value=\"module\\-info\\.java$\"/>\n  </module>\n  <!-- https://checkstyle.org/config_filters.html#SuppressionFilter -->\n  <module name=\"SuppressionFilter\">\n    <property name=\"file\" value=\"${org.checkstyle.google.suppressionfilter.config}\"\n           default=\"checkstyle-suppressions.xml\" />\n    <property name=\"optional\" value=\"true\"/>\n  </module>\n\n  <!-- Checks for whitespace                               -->\n  <!-- See http://checkstyle.org/config_whitespace.html -->\n  <module name=\"FileTabCharacter\">\n    <property name=\"eachLine\" value=\"true\"/>\n  </module>\n\n  <module name=\"LineLength\">\n    <property name=\"fileExtensions\" value=\"java\"/>\n    <property name=\"max\" value=\"100\"/>\n    <property name=\"ignorePattern\" value=\"^package.*|^import.*|a href|href|http://|https://|ftp://\"/>\n  </module>\n\n  <module name=\"TreeWalker\">\n    <module name=\"OuterTypeFilename\"/>\n    <module name=\"IllegalTokenText\">\n      <property name=\"tokens\" value=\"STRING_LITERAL, CHAR_LITERAL\"/>\n      <property name=\"format\"\n               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)\"/>\n      <property name=\"message\"\n               value=\"Consider using special escape sequence instead of octal value or Unicode escaped value.\"/>\n    </module>\n    <module name=\"AvoidEscapedUnicodeCharacters\">\n      <property name=\"allowEscapesForControlCharacters\" value=\"true\"/>\n      <property name=\"allowByTailComment\" value=\"true\"/>\n      <property name=\"allowNonPrintableEscapes\" value=\"true\"/>\n    </module>\n    <module name=\"AvoidStarImport\"/>\n    <module name=\"OneTopLevelClass\"/>\n    <module name=\"NoLineWrap\">\n      <property name=\"tokens\" value=\"PACKAGE_DEF, IMPORT, STATIC_IMPORT\"/>\n    </module>\n    <module name=\"EmptyBlock\">\n      <property name=\"option\" value=\"TEXT\"/>\n      <property name=\"tokens\"\n               value=\"LITERAL_TRY, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE, LITERAL_SWITCH\"/>\n    </module>\n    <module name=\"NeedBraces\">\n      <property name=\"tokens\"\n               value=\"LITERAL_DO, LITERAL_ELSE, LITERAL_FOR, LITERAL_IF, LITERAL_WHILE\"/>\n    </module>\n    <module name=\"LeftCurly\">\n      <property name=\"tokens\"\n               value=\"ANNOTATION_DEF, CLASS_DEF, CTOR_DEF, ENUM_CONSTANT_DEF, ENUM_DEF,\n                    INTERFACE_DEF, LAMBDA, LITERAL_CASE, LITERAL_CATCH, LITERAL_DEFAULT,\n                    LITERAL_DO, LITERAL_ELSE, LITERAL_FINALLY, LITERAL_FOR, LITERAL_IF,\n                    LITERAL_SWITCH, LITERAL_SYNCHRONIZED, LITERAL_TRY, LITERAL_WHILE, METHOD_DEF,\n                    OBJBLOCK, STATIC_INIT, RECORD_DEF, COMPACT_CTOR_DEF\"/>\n    </module>\n    <module name=\"RightCurly\">\n      <property name=\"id\" value=\"RightCurlySame\"/>\n      <property name=\"tokens\"\n               value=\"LITERAL_TRY, LITERAL_CATCH, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE,\n                    LITERAL_DO\"/>\n    </module>\n    <module name=\"RightCurly\">\n      <property name=\"id\" value=\"RightCurlyAlone\"/>\n      <property name=\"option\" value=\"alone\"/>\n      <property name=\"tokens\"\n               value=\"CLASS_DEF, METHOD_DEF, CTOR_DEF, LITERAL_FOR, LITERAL_WHILE, STATIC_INIT,\n                    INSTANCE_INIT, ANNOTATION_DEF, ENUM_DEF, INTERFACE_DEF, RECORD_DEF,\n                    COMPACT_CTOR_DEF\"/>\n    </module>\n    <module name=\"SuppressionXpathSingleFilter\">\n      <!-- suppresion is required till https://github.com/checkstyle/checkstyle/issues/7541 -->\n      <property name=\"id\" value=\"RightCurlyAlone\"/>\n      <property name=\"query\" value=\"//RCURLY[parent::SLIST[count(./*)=1]\n                                     or preceding-sibling::*[last()][self::LCURLY]]\"/>\n    </module>\n    <module name=\"WhitespaceAfter\">\n      <property name=\"tokens\"\n               value=\"COMMA, SEMI, TYPECAST, LITERAL_IF, LITERAL_ELSE,\n                    LITERAL_WHILE, LITERAL_DO, LITERAL_FOR, DO_WHILE\"/>\n    </module>\n    <module name=\"WhitespaceAround\">\n      <property name=\"allowEmptyConstructors\" value=\"true\"/>\n      <property name=\"allowEmptyLambdas\" value=\"true\"/>\n      <property name=\"allowEmptyMethods\" value=\"true\"/>\n      <property name=\"allowEmptyTypes\" value=\"true\"/>\n      <property name=\"allowEmptyLoops\" value=\"true\"/>\n      <property name=\"ignoreEnhancedForColon\" value=\"false\"/>\n      <property name=\"tokens\"\n               value=\"ASSIGN, BAND, BAND_ASSIGN, BOR, BOR_ASSIGN, BSR, BSR_ASSIGN, BXOR,\n                    BXOR_ASSIGN, COLON, DIV, DIV_ASSIGN, DO_WHILE, EQUAL, GE, GT, LAMBDA, LAND,\n                    LCURLY, LE, LITERAL_CATCH, LITERAL_DO, LITERAL_ELSE, LITERAL_FINALLY,\n                    LITERAL_FOR, LITERAL_IF, LITERAL_RETURN, LITERAL_SWITCH, LITERAL_SYNCHRONIZED,\n                    LITERAL_TRY, LITERAL_WHILE, LOR, LT, MINUS, MINUS_ASSIGN, MOD, MOD_ASSIGN,\n                    NOT_EQUAL, PLUS, PLUS_ASSIGN, QUESTION, RCURLY, SL, SLIST, SL_ASSIGN, SR,\n                    SR_ASSIGN, STAR, STAR_ASSIGN, LITERAL_ASSERT, TYPE_EXTENSION_AND\"/>\n      <message key=\"ws.notFollowed\"\n              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)\"/>\n      <message key=\"ws.notPreceded\"\n              value=\"WhitespaceAround: ''{0}'' is not preceded with whitespace.\"/>\n    </module>\n    <module name=\"OneStatementPerLine\"/>\n    <module name=\"MultipleVariableDeclarations\"/>\n    <module name=\"ArrayTypeStyle\"/>\n    <module name=\"MissingSwitchDefault\"/>\n    <module name=\"FallThrough\"/>\n    <module name=\"UpperEll\"/>\n    <module name=\"ModifierOrder\"/>\n    <module name=\"EmptyLineSeparator\">\n      <property name=\"tokens\"\n               value=\"PACKAGE_DEF, IMPORT, STATIC_IMPORT, CLASS_DEF, INTERFACE_DEF, ENUM_DEF,\n                    STATIC_INIT, INSTANCE_INIT, METHOD_DEF, CTOR_DEF, VARIABLE_DEF, RECORD_DEF,\n                    COMPACT_CTOR_DEF\"/>\n      <property name=\"allowNoEmptyLineBetweenFields\" value=\"true\"/>\n    </module>\n    <module name=\"SeparatorWrap\">\n      <property name=\"id\" value=\"SeparatorWrapDot\"/>\n      <property name=\"tokens\" value=\"DOT\"/>\n      <property name=\"option\" value=\"nl\"/>\n    </module>\n    <module name=\"SeparatorWrap\">\n      <property name=\"id\" value=\"SeparatorWrapComma\"/>\n      <property name=\"tokens\" value=\"COMMA\"/>\n      <property name=\"option\" value=\"EOL\"/>\n    </module>\n    <module name=\"SeparatorWrap\">\n      <!-- ELLIPSIS is EOL until https://github.com/google/styleguide/issues/259 -->\n      <property name=\"id\" value=\"SeparatorWrapEllipsis\"/>\n      <property name=\"tokens\" value=\"ELLIPSIS\"/>\n      <property name=\"option\" value=\"EOL\"/>\n    </module>\n    <module name=\"SeparatorWrap\">\n      <!-- ARRAY_DECLARATOR is EOL until https://github.com/google/styleguide/issues/258 -->\n      <property name=\"id\" value=\"SeparatorWrapArrayDeclarator\"/>\n      <property name=\"tokens\" value=\"ARRAY_DECLARATOR\"/>\n      <property name=\"option\" value=\"EOL\"/>\n    </module>\n    <module name=\"SeparatorWrap\">\n      <property name=\"id\" value=\"SeparatorWrapMethodRef\"/>\n      <property name=\"tokens\" value=\"METHOD_REF\"/>\n      <property name=\"option\" value=\"nl\"/>\n    </module>\n    <module name=\"PackageName\">\n      <property name=\"format\" value=\"^[a-z]+(\\.[a-z][a-z0-9]*)*$\"/>\n      <message key=\"name.invalidPattern\"\n             value=\"Package name ''{0}'' must match pattern ''{1}''.\"/>\n    </module>\n    <module name=\"TypeName\">\n      <property name=\"tokens\" value=\"CLASS_DEF, INTERFACE_DEF, ENUM_DEF,\n                    ANNOTATION_DEF, RECORD_DEF\"/>\n      <message key=\"name.invalidPattern\"\n             value=\"Type name ''{0}'' must match pattern ''{1}''.\"/>\n    </module>\n    <module name=\"MemberName\">\n      <property name=\"format\" value=\"^[a-z][a-z0-9][a-zA-Z0-9]*$\"/>\n      <message key=\"name.invalidPattern\"\n             value=\"Member name ''{0}'' must match pattern ''{1}''.\"/>\n    </module>\n    <module name=\"ParameterName\">\n      <property name=\"format\" value=\"^[a-z]([a-z0-9][a-zA-Z0-9]*)?$\"/>\n      <message key=\"name.invalidPattern\"\n             value=\"Parameter name ''{0}'' must match pattern ''{1}''.\"/>\n    </module>\n    <module name=\"LambdaParameterName\">\n      <property name=\"format\" value=\"^[a-z]([a-z0-9][a-zA-Z0-9]*)?$\"/>\n      <message key=\"name.invalidPattern\"\n             value=\"Lambda parameter name ''{0}'' must match pattern ''{1}''.\"/>\n    </module>\n    <module name=\"CatchParameterName\">\n      <property name=\"format\" value=\"^[a-z]([a-z0-9][a-zA-Z0-9]*)?$\"/>\n      <message key=\"name.invalidPattern\"\n             value=\"Catch parameter name ''{0}'' must match pattern ''{1}''.\"/>\n    </module>\n    <module name=\"LocalVariableName\">\n      <property name=\"format\" value=\"^[a-z]([a-z0-9][a-zA-Z0-9]*)?$\"/>\n      <message key=\"name.invalidPattern\"\n             value=\"Local variable name ''{0}'' must match pattern ''{1}''.\"/>\n    </module>\n    <module name=\"PatternVariableName\">\n      <property name=\"format\" value=\"^[a-z]([a-z0-9][a-zA-Z0-9]*)?$\"/>\n      <message key=\"name.invalidPattern\"\n             value=\"Pattern variable name ''{0}'' must match pattern ''{1}''.\"/>\n    </module>\n    <module name=\"ClassTypeParameterName\">\n      <property name=\"format\" value=\"(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)\"/>\n      <message key=\"name.invalidPattern\"\n             value=\"Class type name ''{0}'' must match pattern ''{1}''.\"/>\n    </module>\n    <module name=\"RecordComponentName\">\n      <property name=\"format\" value=\"^[a-z]([a-z0-9][a-zA-Z0-9]*)?$\"/>\n      <message key=\"name.invalidPattern\"\n               value=\"Record component name ''{0}'' must match pattern ''{1}''.\"/>\n    </module>\n    <module name=\"RecordTypeParameterName\">\n      <property name=\"format\" value=\"(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)\"/>\n      <message key=\"name.invalidPattern\"\n               value=\"Record type name ''{0}'' must match pattern ''{1}''.\"/>\n    </module>\n    <module name=\"MethodTypeParameterName\">\n      <property name=\"format\" value=\"(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)\"/>\n      <message key=\"name.invalidPattern\"\n             value=\"Method type name ''{0}'' must match pattern ''{1}''.\"/>\n    </module>\n    <module name=\"InterfaceTypeParameterName\">\n      <property name=\"format\" value=\"(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)\"/>\n      <message key=\"name.invalidPattern\"\n             value=\"Interface type name ''{0}'' must match pattern ''{1}''.\"/>\n    </module>\n    <module name=\"NoFinalizer\"/>\n    <module name=\"GenericWhitespace\">\n      <message key=\"ws.followed\"\n             value=\"GenericWhitespace ''{0}'' is followed by whitespace.\"/>\n      <message key=\"ws.preceded\"\n             value=\"GenericWhitespace ''{0}'' is preceded with whitespace.\"/>\n      <message key=\"ws.illegalFollow\"\n             value=\"GenericWhitespace ''{0}'' should followed by whitespace.\"/>\n      <message key=\"ws.notPreceded\"\n             value=\"GenericWhitespace ''{0}'' is not preceded with whitespace.\"/>\n    </module>\n    <module name=\"Indentation\">\n      <property name=\"basicOffset\" value=\"4\"/>\n      <property name=\"braceAdjustment\" value=\"4\"/>\n      <property name=\"caseIndent\" value=\"4\"/>\n      <property name=\"throwsIndent\" value=\"8\"/>\n      <property name=\"lineWrappingIndentation\" value=\"8\"/>\n      <property name=\"arrayInitIndent\" value=\"4\"/>\n    </module>\n    <module name=\"AbbreviationAsWordInName\">\n      <property name=\"ignoreFinal\" value=\"false\"/>\n      <property name=\"allowedAbbreviationLength\" value=\"0\"/>\n      <property name=\"tokens\"\n               value=\"CLASS_DEF, INTERFACE_DEF, ENUM_DEF, ANNOTATION_DEF, ANNOTATION_FIELD_DEF,\n                    PARAMETER_DEF, VARIABLE_DEF, METHOD_DEF, PATTERN_VARIABLE_DEF, RECORD_DEF,\n                    RECORD_COMPONENT_DEF\"/>\n    </module>\n    <module name=\"NoWhitespaceBeforeCaseDefaultColon\"/>\n    <module name=\"OverloadMethodsDeclarationOrder\"/>\n    <module name=\"VariableDeclarationUsageDistance\"/>\n    <module name=\"CustomImportOrder\">\n      <property name=\"sortImportsInGroupAlphabetically\" value=\"true\"/>\n      <property name=\"separateLineBetweenGroups\" value=\"true\"/>\n      <property name=\"customImportOrderRules\" value=\"STATIC###THIRD_PARTY_PACKAGE\"/>\n      <property name=\"tokens\" value=\"IMPORT, STATIC_IMPORT, PACKAGE_DEF\"/>\n    </module>\n    <module name=\"MethodParamPad\">\n      <property name=\"tokens\"\n               value=\"CTOR_DEF, LITERAL_NEW, METHOD_CALL, METHOD_DEF,\n                    SUPER_CTOR_CALL, ENUM_CONSTANT_DEF, RECORD_DEF\"/>\n    </module>\n    <module name=\"NoWhitespaceBefore\">\n      <property name=\"tokens\"\n               value=\"COMMA, SEMI, POST_INC, POST_DEC, DOT,\n                    LABELED_STAT, METHOD_REF\"/>\n      <property name=\"allowLineBreaks\" value=\"true\"/>\n    </module>\n    <module name=\"ParenPad\">\n      <property name=\"tokens\"\n               value=\"ANNOTATION, ANNOTATION_FIELD_DEF, CTOR_CALL, CTOR_DEF, DOT, ENUM_CONSTANT_DEF,\n                    EXPR, LITERAL_CATCH, LITERAL_DO, LITERAL_FOR, LITERAL_IF, LITERAL_NEW,\n                    LITERAL_SWITCH, LITERAL_SYNCHRONIZED, LITERAL_WHILE, METHOD_CALL,\n                    METHOD_DEF, QUESTION, RESOURCE_SPECIFICATION, SUPER_CTOR_CALL, LAMBDA,\n                    RECORD_DEF\"/>\n    </module>\n    <module name=\"OperatorWrap\">\n      <property name=\"option\" value=\"NL\"/>\n      <property name=\"tokens\"\n               value=\"BAND, BOR, BSR, BXOR, DIV, EQUAL, GE, GT, LAND, LE, LITERAL_INSTANCEOF, LOR,\n                    LT, MINUS, MOD, NOT_EQUAL, PLUS, QUESTION, SL, SR, STAR, METHOD_REF,\n                    TYPE_EXTENSION_AND \"/>\n    </module>\n    <module name=\"AnnotationLocation\">\n      <property name=\"id\" value=\"AnnotationLocationMostCases\"/>\n      <property name=\"tokens\"\n               value=\"CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF,\n                      RECORD_DEF, COMPACT_CTOR_DEF\"/>\n    </module>\n    <module name=\"AnnotationLocation\">\n      <property name=\"id\" value=\"AnnotationLocationVariables\"/>\n      <property name=\"tokens\" value=\"VARIABLE_DEF\"/>\n      <property name=\"allowSamelineMultipleAnnotations\" value=\"true\"/>\n    </module>\n    <module name=\"NonEmptyAtclauseDescription\"/>\n    <module name=\"InvalidJavadocPosition\"/>\n    <module name=\"JavadocTagContinuationIndentation\"/>\n    <module name=\"SummaryJavadoc\">\n      <property name=\"forbiddenSummaryFragments\"\n               value=\"^@return the *|^This method returns |^A [{]@code [a-zA-Z0-9]+[}]( is a )\"/>\n    </module>\n    <module name=\"JavadocParagraph\"/>\n    <module name=\"RequireEmptyLineBeforeBlockTagGroup\"/>\n    <module name=\"AtclauseOrder\">\n      <property name=\"tagOrder\" value=\"@param, @return, @throws, @deprecated\"/>\n      <property name=\"target\"\n               value=\"CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF, VARIABLE_DEF\"/>\n    </module>\n    <module name=\"JavadocMethod\">\n      <property name=\"accessModifiers\" value=\"public\"/>\n      <property name=\"allowMissingParamTags\" value=\"true\"/>\n      <property name=\"allowMissingReturnTag\" value=\"true\"/>\n      <property name=\"allowedAnnotations\" value=\"Override, Test\"/>\n      <property name=\"tokens\" value=\"METHOD_DEF, CTOR_DEF, ANNOTATION_FIELD_DEF, COMPACT_CTOR_DEF\"/>\n    </module>\n    <module name=\"MissingJavadocMethod\">\n      <property name=\"scope\" value=\"public\"/>\n      <property name=\"minLineCount\" value=\"2\"/>\n      <property name=\"allowedAnnotations\" value=\"Override, Test\"/>\n      <property name=\"tokens\" value=\"METHOD_DEF, CTOR_DEF, ANNOTATION_FIELD_DEF,\n                                   COMPACT_CTOR_DEF\"/>\n    </module>\n    <module name=\"MissingJavadocType\">\n      <property name=\"scope\" value=\"protected\"/>\n      <property name=\"tokens\"\n                value=\"CLASS_DEF, INTERFACE_DEF, ENUM_DEF,\n                      RECORD_DEF, ANNOTATION_DEF\"/>\n      <property name=\"excludeScope\" value=\"nothing\"/>\n    </module>\n    <module name=\"MethodName\">\n      <property name=\"format\" value=\"^[a-z][a-z0-9][a-zA-Z0-9_]*$\"/>\n      <message key=\"name.invalidPattern\"\n             value=\"Method name ''{0}'' must match pattern ''{1}''.\"/>\n    </module>\n    <module name=\"SingleLineJavadoc\"/>\n    <module name=\"EmptyCatchBlock\">\n      <property name=\"exceptionVariableName\" value=\"expected\"/>\n    </module>\n    <module name=\"CommentsIndentation\">\n      <property name=\"tokens\" value=\"SINGLE_LINE_COMMENT, BLOCK_COMMENT_BEGIN\"/>\n    </module>\n    <!-- https://checkstyle.org/config_filters.html#SuppressionXpathFilter -->\n    <module name=\"SuppressionXpathFilter\">\n      <property name=\"file\" value=\"${org.checkstyle.google.suppressionxpathfilter.config}\"\n             default=\"checkstyle-xpath-suppressions.xml\" />\n      <property name=\"optional\" value=\"true\"/>\n    </module>\n  </module>\n</module>\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participation in our\ncommunity a harassment-free experience for everyone, regardless of age, body\nsize, visible or invisible disability, ethnicity, sex characteristics, gender\nidentity and expression, level of experience, education, socio-economic status,\nnationality, personal appearance, race, religion, or sexual identity\nand orientation.\n\nWe pledge to act and interact in ways that contribute to an open, welcoming,\ndiverse, inclusive, and healthy community.\n\n## Our Standards\n\nExamples of behavior that contributes to a positive environment for our\ncommunity include:\n\n* Demonstrating empathy and kindness toward other people\n* Being respectful of differing opinions, viewpoints, and experiences\n* Giving and gracefully accepting constructive feedback\n* Accepting responsibility and apologizing to those affected by our mistakes,\n  and learning from the experience\n* Focusing on what is best not just for us as individuals, but for the\n  overall community\n\nExamples of unacceptable behavior include:\n\n* The use of sexualized language or imagery, and sexual attention or\n  advances of any kind\n* Trolling, insulting or derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or email\n  address, without their explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n## Enforcement Responsibilities\n\nCommunity leaders are responsible for clarifying and enforcing our standards of\nacceptable behavior and will take appropriate and fair corrective action in\nresponse to any behavior that they deem inappropriate, threatening, offensive,\nor harmful.\n\nCommunity leaders have the right and responsibility to remove, edit, or reject\ncomments, commits, code, wiki edits, issues, and other contributions that are\nnot aligned to this Code of Conduct, and will communicate reasons for moderation\ndecisions when appropriate.\n\n## Scope\n\nThis Code of Conduct applies within all community spaces, and also applies when\nan individual is officially representing the community in public spaces.\nExamples of representing our community include using an official e-mail address,\nposting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported to the community leaders responsible for enforcement at\nservice@megaease.com.\nAll complaints will be reviewed and investigated promptly and fairly.\n\nAll community leaders are obligated to respect the privacy and security of the\nreporter of any incident.\n\n## Enforcement Guidelines\n\nCommunity leaders will follow these Community Impact Guidelines in determining\nthe consequences for any action they deem in violation of this Code of Conduct:\n\n### 1. Correction\n\n**Community Impact**: Use of inappropriate language or other behavior deemed\nunprofessional or unwelcome in the community.\n\n**Consequence**: A private, written warning from community leaders, providing\nclarity around the nature of the violation and an explanation of why the\nbehavior was inappropriate. A public apology may be requested.\n\n### 2. Warning\n\n**Community Impact**: A violation through a single incident or series\nof actions.\n\n**Consequence**: A warning with consequences for continued behavior. No\ninteraction with the people involved, including unsolicited interaction with\nthose enforcing the Code of Conduct, for a specified period of time. This\nincludes avoiding interactions in community spaces as well as external channels\nlike social media. Violating these terms may lead to a temporary or\npermanent ban.\n\n### 3. Temporary Ban\n\n**Community Impact**: A serious violation of community standards, including\nsustained inappropriate behavior.\n\n**Consequence**: A temporary ban from any sort of interaction or public\ncommunication with the community for a specified period of time. No public or\nprivate interaction with the people involved, including unsolicited interaction\nwith those enforcing the Code of Conduct, is allowed during this period.\nViolating these terms may lead to a permanent ban.\n\n### 4. Permanent Ban\n\n**Community Impact**: Demonstrating a pattern of violation of community\nstandards, including sustained inappropriate behavior,  harassment of an\nindividual, or aggression toward or disparagement of classes of individuals.\n\n**Consequence**: A permanent ban from any sort of public interaction within\nthe community.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage],\nversion 2.0, available at\nhttps://www.contributor-covenant.org/version/2/0/code_of_conduct.html.\n\nCommunity Impact Guidelines were inspired by [Mozilla's code of conduct\nenforcement ladder](https://github.com/mozilla/diversity).\n\n[homepage]: https://www.contributor-covenant.org\n\nFor answers to common questions about this code of conduct, see the FAQ at\nhttps://www.contributor-covenant.org/faq. Translations are available at\nhttps://www.contributor-covenant.org/translations.\n"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "# EaseAgent\n\nA lightweight & opening Java Agent for Cloud-Native and APM system\n\n- [EaseAgent](#easeagent)\n  - [Overview](#overview)\n    - [Purpose](#purpose)\n    - [Principles](#principles)\n  - [Features](#features)\n  - [Architecture Diagram](#architecture-diagram)\n      - [Description](#description)\n  - [QuickStart](#quickstart)\n    - [Get And Set Environment Variable](#get-and-set-environment-variable)\n      - [Setup Environment Variable](#setup-environment-variable)\n      - [Download](#download)\n      - [Build From the Source](#build-from-the-source)\n      - [Get Configuration file](#get-configuration-file)\n    - [Monitor Spring Petclinic](#monitor-spring-petclinic)\n      - [Prerequisites](#prerequisites)\n      - [Initialize and Start the project](#initialize-and-start-the-project)\n      - [Metric](#metric)\n      - [Tracing](#tracing)\n      - [Build Spring Petclinic](#build-spring-petclinic)\n    - [Add an Enhancement Plugin](#add-an-enhancement-plugin)\n  - [User Manual](#user-manual)\n  - [Enhancement Plugin Development Guide](#enhancement-plugin-development-guide)\n  - [Report Plugin Development Guide](#report-plugin-development-guide)\n  - [Community](#community)\n  - [Licenses](#licenses)\n\n## Overview\n- EaseAgent is the underlying component that provides non-intrusive extensions to applications of the Java ecosystem. \n- EaseAgent can collect distributed application tracing, metrics, and logs, which could be used in the APM system and improve the observability of a distributed system. for the tracing, EaseAgent follows the [Google Dapper](https://research.google/pubs/pub36356/) paper.\n- EaseAgent also can work with Cloud-Native architecture. For example, it can help Service Mesh (especially for [EaseMesh](https://github.com/megaease/easemesh/) ) to do some control panel work.\n- EaseAgent supports plugins mechanism development, which is easy to extend or add new functionality.\n\n### Purpose\n- EaseAgent can be a Java agent for APM(Application Performance Management) system.\n- EaseAgent collects the basic metrics and the service tracing logs, which is very helpful for performance analysis and troubleshooting.\n- EaseAgent is compatible with mainstream monitoring ecosystems, such as Kafka, ElasticSearch, Prometheus, Zipkin, etc.\n- EaseAgent majorly focuses on the Spring Boot development environments, but users can support any Java ecosystem applications through plugins.\n- EaseAgent can support scenario-specific business requirements through the plugin mechanism, such as traffic redirection, traffic coloring, etc.\n\n### Principles\n- Safe to Java application/service.\n- Instrumenting a Java application in a non-intrusive way.\n- Lightweight and very low CPU, memory, and I/O resource usage.\n- Highly extensible, users can easily do extensions through a simple and clear plugin interface.\n- Design for Micro-Service architecture, collecting the data from a service perspective.\n\n## Features\n* Easy to use. It is right out of the box for Metrics, Tracing and Logs collecting.\n    * Collecting Metric & Tracing Logs.\n        * `JDBC 4.0`\n        * `HTTP Servlet`、`HTTP Filter`\n        * `Spring Boot 2.2.x`: `WebClient` 、 `RestTemplate`、`FeignClient`\n        * `RabbitMQ Client 5.x`、 `Kafka Client 2.4.x`\n        * `Jedis 3.5.x`、 `Lettuce 5.3.x (sync、async)`\n        * `ElasticSearch Client >= 7.x (sync、async)`\n        * `Mongodb Client >=4.0.x (sync、async)`\n        * `Motan`\n        * `Dubbo`\n        * `SofaRpc >= 5.3.0`\n    * Collecting Access Logs.\n        * `HTTP Servlet`、`HTTP Filter`\n        * `Spring Cloud Gateway`\n    * Instrumenting the `traceId` and `spanId` into user application logging automatically\n    * Supplying the `health check` endpoint\n    * Supplying the `readiness check` endpoint for `SpringBoot2.2.x`\n    * Supplying the `agent info` endpoint\n    \n*  Data Reports\n    * Console Reporter.\n    * Prometheus Exports.\n    * Http Reporter.\n    * Kafka Reporter.\n    * Custom Reporter.\n\n* Easy to Extend\n    * Simple and clear Plugin Interface, creating a plugin as few as three classes.\n    * Extremely cleanly packaged `Tracing` and `Metric` API, with a small amount of code to achieve business support.\n\n* Standardization\n    * The tracing data format is fully compatible with the Zipkin data format.\n    * Metric data format fully supports integration with `Prometheus`.\n    * The application log format is fully compatible with the `Opentelemetry` data format.\n\n\n## Architecture Diagram\n![image](./doc/images/EaseAgent-Architecture-Base-v2.0.png)\n\n#### Description\n**Plugin Framework** in `core` module is base on [Byte buddy](https://github.com/raphw/byte-buddy) technology.\n\n1. Easeagent's plugin defines where (which classes and methods) to make enhancements by implementing the `Points`  and what to do at the point of enhancement by implementing the `Interceptor`.\n2. When the program invokes the enhanced method of class defined by Points, the `unique index`(uid) owned by the method will be used as a parameter to call the common interface of `Agent Common Method Advice`, which finds the `Agent Interceptor Chain` by the `Unique Index` and calls the `before` method of each Interceptor in the chain in order of priority.\n3. Normally, both the `Metric Interceptor` and the `Tracing Interceptor` are in the agent interceptor chain and are called sequentially.\n4. According to call the `Metric API` and `Tracing API` in interceptors, the `Metric` and `Tracing` information will be stored in `MetricRegistry` and `Tracing`.\n5. The `Reporter` module will get information from `MetricRegistry` and `Tracing` and send it to `Kafka`.\n6. The `after` method of each interceptor in the `Agent Interceptor Chain` will be invoked in the reverse order of the `before` invoked at last.\n7. The `tracing` data can be sent to `kafka` server or `zipkin` server, the `metric` data can be sent to `kafka` server and pull by `Prometheus` server.\n\n## QuickStart\n\n### Get And Set Environment Variable\n\nSetup Environment Variable and then download the latest release of `easeagent.jar` or build it from the source.\n\n#### Setup Environment Variable\n```\n$ cd ~/easeagent #[Replace with agent path]\n$ export EASE_AGENT_PATH=`pwd` # export EASE_AGENT_PATH=[Replace with agent path]\n$ mkdir plugins\n```\n\n#### Download\nDownload `easeagent.jar` from releases [releases](https://github.com/megaease/easeagent/releases).\n\n```\n$ curl -Lk https://github.com/megaease/easeagent/releases/latest/download/easeagent.jar -O\n```\n\n#### Build From the Source\n\nYou need Java 1.8+ and git:\n\nDownload EaseAgent with `git clone https://github.com/megaease/easeagent.git`.\n```\n$ cd easeagent\n$ mvn clean package -Dmaven.test.skip\n$ cp ./build/target/easeagent-dep.jar $EASE_AGENT_PATH/easeagent.jar\n```\nThe `./build/target/easeagent-dep.jar` is the agent jar with all the dependencies.\n\n> For the Windows platform, please make sure git `core.autocrlf` is set to false before git clone.\n> You can use `git config --global core.autocrlf false` to modify `core.autocrlf`.\n\n[How to use easeagent.jar on host?](doc/how-to-use/use-on-host.md)\n\n[How to use easeagent.jar in docker?](doc/how-to-use/use-in-docker.md)\n\n#### Get Configuration file\nExtracting the default configuration file.\n```\n$ cd $EASE_AGENT_PATH\n$ jar xf easeagent.jar agent.properties easeagent-log4j2.xml\n```\n\nBy default, there is an agent.properties configuration file, which is configured to print all output data to the console.\n\n### Monitor Spring Petclinic\n#### Prerequisites\n- Make sure you have installed the docker, docker-compose in your environment.\n- Make sure your docker version is higher than v19.+.\n- Make sure your docker-compose version is higher than v2.+.\n\n[Project Details](https://github.com/megaease/easeagent-spring-petclinic)\n\n#### Initialize and Start the project\n```\n$ git clone https://github.com/megaease/easeagent-spring-petclinic.git\n$ cd easeagent-spring-petclinic\n$ git submodule update --init\n$ ./spring-petclinic.sh start\n```\n\n> The script will download the latest release of EaseAgent. \n> If you want to use your own built EaseAgent, copy it to the directory: `easeagent/downloaded`\n>> ```$ cp $EASE_AGENT_PATH/easeagent.jar  easeagent/downloaded/easeagent-latest.jar``` \n\nIt requires `Docker` to pull images from the docker hub, be patient. \n\nOpen Browser to visit grafana UI: [http://localhost:3000](http://localhost:3000).\n\n#### Metric\nClick the `search dashboards`, the first icon in the left menu bar. Choose the `spring-petclinic-easeagent` to open the dashboard we prepare for you.\n\nPrometheus Metric Schedule: [Prometheus Metric](doc/prometheus-metric-schedule.md)\n\n![metric](doc/images/grafana-metric.png)\n\n#### Tracing\n\nIf you want to check the tracing-data, you could click the explore in the left menu bar. Click the Search - beta to switch search mode. Click search query button in the right up corner, there is a list containing many tracing. Choose one to click.\n\n![tracing](doc/images/grafana-tracing.png)\n\n#### Build Spring Petclinic\n\n[Spring Petclinic Demo](doc/spring-petclinic-demo.md)\n\n### Add an Enhancement Plugin\n[Add a Demo Plugin to EaseAgent](doc/add-plugin-demo.md)\n\n## User Manual\nFor more information, please refer to the [User Manual](./doc/user-manual.md).\n\n## Enhancement Plugin Development Guide\nRefer to [Plugin Development Guide](./doc/development-guide.md).\n\n## Report Plugin Development Guide\nReport plugin enables user report tracing/metric data to different kinds of the backend in a different format.\n\nRefer to [Report Plugin Development Guide](./doc/report-development-guide.md)\n\n## Community\n\n* [Github Issues](https://github.com/megaease/easeagent/issues)\n* [Join Slack Workspace](https://join.slack.com/t/openmegaease/shared_invite/zt-upo7v306-lYPHvVwKnvwlqR0Zl2vveA) for requirement, issue and development.\n* [MegaEase on Twitter](https://twitter.com/megaease)\n\nIf you have any questions, welcome to discuss them in our community. Welcome to join!\n\n\n## Licenses\nEaseAgent is licensed under the Apache License, Version 2.0. See [LICENSE](./LICENSE) for the full license text.\n"
  },
  {
    "path": "build/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  Copyright (c) 2017, MegaEase\n  All rights reserved.\n\n  Licensed under the Apache License, Version 2.0 (the \"License\");\n  you may not use this file except in compliance with the License.\n  You may obtain a copy of the License at\n\n      http://www.apache.org/licenses/LICENSE-2.0\n\n  Unless required by applicable law or agreed to in writing, software\n  distributed under the License is distributed on an \"AS IS\" BASIS,\n  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  See the License for the specific language governing permissions and\n  limitations under the License.\n-->\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>easeagent</artifactId>\n        <groupId>com.megaease.easeagent</groupId>\n        <version>2.3.0</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>build</artifactId>\n\n    <dependencies>\n        <dependency>\n            <groupId>org.apache.kafka</groupId>\n            <artifactId>kafka-clients</artifactId>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>javax.servlet</groupId>\n            <artifactId>javax.servlet-api</artifactId>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>metrics</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>zipkin</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>log4j2-api</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>core</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>loader</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <finalName>easeagent</finalName>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-compiler-plugin</artifactId>\n                <version>3.6.1</version>\n                <configuration>\n                    <source>${version.java}</source>\n                    <target>${version.java}</target>\n                    <encoding>${encoding.file}</encoding>\n                    <compilerArgs>\n                        <arg>-Xlint:unchecked</arg>\n                    </compilerArgs>\n                </configuration>\n            </plugin>\n            <plugin>\n                <artifactId>maven-antrun-plugin</artifactId>\n                <version>3.0.0</version>\n                <executions>\n                    <execution>\n                        <phase>prepare-package</phase>\n                        <configuration>\n                            <target>\n                                <mkdir dir=\"target/inner-plugins\"/>\n                                <copy todir=\"target/inner-plugins\" flatten=\"true\">\n                                    <fileset dir=\"${project.basedir}/../plugins\" includes=\"**/*.jar\"/>\n                                </copy>\n\n                                <!--\n                                <mkdir dir=\"target/boot\"/>\n                                <copy todir=\"target/boot\" flatten=\"true\">\n                                    <fileset dir=\"${project.basedir}/../log4j2/log4j2-api\" includes=\"**/log4j2-api*.jar\"/>\n                                </copy>\n                                -->\n\n                                <mkdir dir=\"target/log4j2\"/>\n                                <copy todir=\"target/log4j2\" flatten=\"true\">\n                                    <fileset dir=\"${project.basedir}/../log4j2/log4j2-impl\" includes=\"**/*agent-lib.jar\"/>\n                                </copy>\n                            </target>\n                        </configuration>\n                        <goals>\n                            <goal>run</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-shade-plugin</artifactId>\n                <version>${version.maven-shade-plugin}</version>\n                <configuration>\n                    <minimizeJar>true</minimizeJar>\n                    <transformers>\n                        <transformer\n                                implementation=\"org.apache.maven.plugins.shade.resource.ServicesResourceTransformer\"/>\n                        <transformer\n                                implementation=\"com.github.edwgiz.maven_shade_plugin.log4j2_cache_transformer.PluginsCacheFileTransformer\">\n                        </transformer>\n                    </transformers>\n                    <filters>\n                        <filter>\n                            <artifact>org.apache.logging.log4j:*</artifact>\n                            <includes>\n                                <include>**</include>\n                            </includes>\n                        </filter>\n                    </filters>\n                </configuration>\n                <executions>\n                    <execution>\n                        <phase>package</phase>\n                        <goals>\n                            <goal>shade</goal>\n                        </goals>\n                    </execution>\n                </executions>\n                <dependencies>\n                    <dependency>\n                        <groupId>com.github.edwgiz</groupId>\n                        <artifactId>maven-shade-plugin.log4j2-cachefile-transformer</artifactId>\n                        <version>2.14.0</version>\n                    </dependency>\n                </dependencies>\n            </plugin>\n            <plugin>\n                <groupId>pl.project13.maven</groupId>\n                <artifactId>git-commit-id-plugin</artifactId>\n                <version>4.9.10</version>\n                <executions>\n                    <execution>\n                        <goals>\n                            <goal>revision</goal>\n                        </goals>\n                    </execution>\n                </executions>\n                <configuration>\n                    <verbose>true</verbose>\n                    <gitDescribe>\n                        <skip>false</skip>\n                        <always>true</always>\n                    </gitDescribe>\n                    <dateFormat>yyyy-MM-dd'T'HH:mm:ssZ</dateFormat>\n                    <generateGitPropertiesFile>true</generateGitPropertiesFile>\n                    <generateGitPropertiesFilename>${project.build.outputDirectory}/git.properties\n                    </generateGitPropertiesFilename>\n                    <includeOnlyProperties>\n                        <includeOnlyProperty>git.commit.*</includeOnlyProperty>\n                    </includeOnlyProperties>\n                </configuration>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-assembly-plugin</artifactId>\n                <version>3.0.0</version>\n                <configuration>\n                    <descriptors>\n                        <descriptor>src/assembly/src.xml</descriptor>\n                    </descriptors>\n                    <archive>\n                        <manifestEntries>\n                            <Can-Redefine-Classes>true</Can-Redefine-Classes>\n                            <Can-Retransform-Classes>true</Can-Retransform-Classes>\n                            <Premain-Class>com.megaease.easeagent.Main</Premain-Class>\n                            <Bootstrap-Class>com.megaease.easeagent.StartBootstrap</Bootstrap-Class>\n                            <Logging-Property>log4j.configurationFile</Logging-Property>\n                            <Easeagent-Version>${version}</Easeagent-Version>\n                        </manifestEntries>\n                    </archive>\n                    <archiverConfig>\n                        <compress>false</compress>\n                    </archiverConfig>\n                </configuration>\n                <executions>\n                    <execution>\n                        <phase>package</phase>\n                        <goals>\n                            <goal>single</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n        </plugins>\n    </build>\n\n\n</project>\n"
  },
  {
    "path": "build/src/assembly/src.xml",
    "content": "<assembly xmlns=\"http://maven.apache.org/ASSEMBLY/2.0.0\"\n          xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n          xsi:schemaLocation=\"http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd\">\n    <id>dep</id>\n    <baseDirectory>/</baseDirectory>\n    <formats>\n        <format>jar</format>\n    </formats>\n    <fileSets>\n        <fileSet>\n            <directory>${project.build.outputDirectory}</directory>\n            <outputDirectory/>\n            <includes>\n                <include>git.properties</include>\n                <include>agent.yaml</include>\n                <include>agent.properties</include>\n                <include>user-minimal-cfg.properties</include>\n                <include>easeagent-log4j2.xml</include>\n            </includes>\n        </fileSet>\n\n        <!--\n        <fileSet>\n            <directory>${project.build.basedir}/plugins/</directory>\n            <outputDirectory>plugins</outputDirectory>\n            <includes>\n                <include>pom.xml</include>\n                <include>**/*.jar</include>\n            </includes>\n        </fileSet>\n\n        <fileSet>\n            <directory>${project.basedir}/target/plugins/</directory>\n            <outputDirectory>plugins</outputDirectory>\n            <includes>\n                <include>pom.xml</include>\n                <include>**/*.jar</include>\n            </includes>\n        </fileSet>\n        -->\n\n        <fileSet>\n            <directory>${project.basedir}/target/inner-plugins/</directory>\n            <outputDirectory>plugins</outputDirectory>\n            <excludes>\n                <exclude>original-*.jar</exclude>\n            </excludes>\n            <includes>\n                <include>*.jar</include>\n            </includes>\n        </fileSet>\n        <fileSet>\n            <directory>${project.basedir}/target/log4j2/</directory>\n            <outputDirectory>log4j2</outputDirectory>\n            <includes>\n                <include>*.jar</include>\n            </includes>\n        </fileSet>\n\n        <fileSet>\n            <directory>${project.basedir}/target/boot/</directory>\n            <outputDirectory>boot</outputDirectory>\n            <includes>\n                <include>*.jar</include>\n            </includes>\n        </fileSet>\n    </fileSets>\n    <dependencySets>\n        <dependencySet>\n            <outputDirectory>boot</outputDirectory>\n            <includes>\n                <include>com.megaease.easeagent:plugin-api</include>\n            </includes>\n            <unpack>false</unpack>\n        </dependencySet>\n        <dependencySet>\n            <outputDirectory>lib</outputDirectory>\n            <excludes>\n                <exclude>**/com/megaease/easeagent/plugin/*</exclude>\n                <exclude>**/com/megaease/easeagent/boot/*</exclude>\n                <exclude>**/com/megaease/easeagent/log4j2/*</exclude>\n            </excludes>\n            <includes>\n                <include>com.megaease.easeagent:build</include>\n            </includes>\n            <!--\n            <unpack>false</unpack>\n            -->\n        </dependencySet>\n        <dependencySet>\n            <outputDirectory/>\n            <includes>\n                <include>com.megaease.easeagent:loader</include>\n            </includes>\n            <unpack>true</unpack>\n        </dependencySet>\n    </dependencySets>\n</assembly>\n"
  },
  {
    "path": "build/src/main/java/com/megaease/easeagent/StartBootstrap.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent;\n\nimport com.megaease.easeagent.core.Bootstrap;\n\nimport java.lang.instrument.Instrumentation;\n\npublic class StartBootstrap {\n    private StartBootstrap() {}\n\n    public static void premain(String args, Instrumentation inst, String javaAgentJarPath) {\n        Bootstrap.start(args, inst, javaAgentJarPath);\n    }\n}\n"
  },
  {
    "path": "build/src/main/resources/agent-to-cloud_1.0.properties",
    "content": "# for v2.0 agent send to megacloud-v1.0 kafka topic setting\n\nplugin.observability.global.metric.topic=application-meter\nplugin.observability.access.metric.topic=application-log\nplugin.observability.redis.metric.topic=platform-meter\nplugin.observability.rabbit.metric.topic=platform-meter\nplugin.observability.kafka.metric.topic=platform-meter\nplugin.observability.elasticsearch.metric.topic=platform-meter\n\nplugin.observability.jvmGc.metric.topic=platform-meter\nplugin.observability.jvmMemory.metric.topic=platform-meter\n\n\n\n"
  },
  {
    "path": "build/src/main/resources/agent.properties",
    "content": "name=demo-service\nsystem=demo-system\n### http server\n# When the enabled value = false, agent will not start the http server\n# You can use -Deaseagent.server.enabled=[true | false] to override.\neaseagent.server.enabled=true\n# http server port. You can use -Deaseagent.server.port=[port] to override.\neaseagent.server.port=9900\n# Enable health/readiness\neaseagent.health.readiness.enabled=true\nplugin.integrability.global.healthReady.enabled=true\n# forwarded headers page\n# Pass-through headers from the root process all the way to the end\n# format: easeagent.progress.forwarded.headers={headerName}\n#easeagent.progress.forwarded.headers=X-Forwarded-For\n#easeagent.progress.forwarded.headers=X-Location,X-Mesh-Service-Canary,X-Phone-Os\n###\n### default tracings configuration\n###\n# sampledType:\n## counting: percentage sampling, sampled limit 0.01 to 1, 1 is always sample, 0 is never sample, 0.1 is ten samples per hundred\n## rate_limiting: traces per second, sampled >= 0, 0 is never sample, 10 is max 10 traces per second\n## boundary: percentage sampling by traceId, sampled limit 0.0001 to 1, 1 is always sample, 0 is never sample\n##           if sampled=0.001, when (traceId^random)%10000<=(0.001*10000) sampled\n## sampledType must be used with sampled, otherwise the default value is used Sampler.ALWAYS_SAMPLE\nobservability.tracings.sampledType=\nobservability.tracings.sampled=1\n# get header from response headers then tag to tracing span\n# format: observability.tracings.tag.response.headers.{key}={value}\n# support ease mesh\n# X-EG-Circuit-Breaker\n# X-EG-Retryer\n# X-EG-Rate-Limiter\n# X-EG-Time-Limiter\nobservability.tracings.tag.response.headers.eg.0=X-EG-Circuit-Breaker\nobservability.tracings.tag.response.headers.eg.1=X-EG-Retryer\nobservability.tracings.tag.response.headers.eg.2=X-EG-Rate-Limiter\nobservability.tracings.tag.response.headers.eg.3=X-EG-Time-Limiter\n# -------------------- plugin global config ---------------------\nplugin.observability.global.init.enabled=true\nplugin.observability.global.tracing.enabled=true\nplugin.observability.global.metric.enabled=true\nplugin.observability.global.metric.interval=30\nplugin.observability.global.metric.topic=application-metrics\nplugin.observability.global.metric.url=/application-metrics\n##\n# if different with reporter.outputServer.appendType,\n# following options can be used in user config file to override\n# the default or global one\n#\n## when it's scrape by prometheus, noop can be used\n# plugin.observability.global.metric.appendType=noop\n## for debug, console can be used\n# plugin.observability.global.metric.appendType=console\n# plugin.observability.global.metric.appendType=http\n#\n# add service name to header enabled by name for easemesh\nplugin.integrability.global.addServiceNameHead.enabled=true\n# redirect the middleware address when env has address, see: com.megaease.easeagent.plugin.api.middleware.RedirectProcessor\n# about redirect: jdbc, kafka, rabbitmq, redis,\nplugin.integrability.global.redirect.enabled=true\n# forwarded headers enabled.\n# headers see config: easeagent.progress.forwarded.headers.???=???\nplugin.integrability.global.forwarded.enabled=true\nplugin.hook.global.foundation.enabled=true\nplugin.observability.global.log.enabled=true\nplugin.observability.global.log.topic=application-log\nplugin.observability.global.log.url=/application-log\n#plugin.observability.global.log.appendType=console\nplugin.observability.global.log.level=INFO\nplugin.observability.global.log.encoder=LogDataJsonEncoder\n#plugin.observability.global.log.encoder.collectMdcKeys=\n# support pattern:\n# \"logLevel\": \"%-5level\",\n# \"threadId\": \"%thread\",\n# \"location\": \"%logger{36}\",\n# \"message\": \"%msg%n\",\nplugin.observability.global.log.encoder.timestamp=%d{UNIX_MILLIS}\nplugin.observability.global.log.encoder.logLevel=%-5level\nplugin.observability.global.log.encoder.threadId=%thread\nplugin.observability.global.log.encoder.location=%logger{36}\nplugin.observability.global.log.encoder.message=%msg%n%xEx{3}\n#\n# -------------------- access ---------------------\n## access: servlet and spring gateway\nplugin.observability.access.log.encoder=AccessLogJsonEncoder\n# plugin.observability.access.metric.appendType=kafka\n#plugin.observability.logback.log.enabled=false\n#plugin.observability.log4j2.log.enabled=false\n# ----------------------------------------------\n# if the plugin configuration is consistent with the global namespace,\n# do not add configuration items not commented out in this default configuration file.\n# otherwise, they can not be overridden by Global configuration in user's configuration file.\n#\n# -------------------- jvm  ---------------------\n# plugin.observability.jvmGc.metric.enabled=true\n# plugin.observability.jvmGc.metric.interval=30\nplugin.observability.jvmGc.metric.topic=platform-metrics\nplugin.observability.jvmGc.metric.url=/platform-metrics\n# plugin.observability.jvmGc.metric.appendType=kafka\n# plugin.observability.jvmMemory.metric.enabled=true\n# plugin.observability.jvmMemory.metric.interval=30\nplugin.observability.jvmMemory.metric.topic=platform-metrics\nplugin.observability.jvmMemory.metric.url=/platform-metrics\n# plugin.observability.jvmMemory.metric.appendType=kafka\n#\n# -------------------- async ---------------------\n# plugin.observability.async.tracing.enabled=true\n#\n# -------------------- elasticsearch redirect ---------------------\n# plugin.integrability.elasticsearch.redirect.enabled=true\n# plugin.observability.elasticsearch.tracing.enabled=true\n# elasticsearch metric\n# plugin.observability.elasticsearch.metric.enabled=true\n# plugin.observability.elasticsearch.metric.interval=30\nplugin.observability.elasticsearch.metric.topic=platform-metrics\nplugin.observability.elasticsearch.metric.url=/platform-metrics\n# plugin.observability.elasticsearch.metric.appendType=kafka\n#\n# -------------------- httpServlet ---------------------\n# plugin.observability.httpServlet.tracing.enabled=true\n# plugin.observability.httpServlet.metric.enabled=true\n# plugin.observability.httpServlet.metric.interval=30\n# plugin.observability.httpServlet.metric.topic=application-metrics\n# plugin.observability.httpServlet.metric.url=/application-metrics\n# plugin.observability.httpServlet.metric.appendType=kafka\n#\n#\n# -------------------- tomcat ---------------------\n# plugin.observability.tomcat.tracing.enabled=true\n# plugin.observability.tomcat.metric.enabled=true\n# plugin.observability.tomcat.metric.interval=30\n# plugin.observability.tomcat.metric.topic=application-metrics\n# plugin.observability.tomcat.metric.url=/application-metrics\n# plugin.observability.tomcat.metric.appendType=kafka\n#\n# -------------------- jdbc ---------------------\n## jdbc tracing\n# plugin.observability.jdbc.tracing.enabled=true\n# jdbcStatement metric\n# plugin.observability.jdbcStatement.metric.enabled=true\n# plugin.observability.jdbcStatement.metric.interval=30\n# plugin.observability.jdbcStatement.metric.topic=application-metrics\n# plugin.observability.jdbcStatement.metric.url=/application-metrics\n# plugin.observability.jdbcStatement.metric.appendType=kafka\n## jdbcConnection metric\n# plugin.observability.jdbcConnection.metric.enabled=true\n# plugin.observability.jdbcConnection.metric.interval=30\n# plugin.observability.jdbcConnection.metric.topic=application-metrics\n# plugin.observability.jdbcConnection.metric.url=/application-metrics\n# plugin.observability.jdbcConnection.metric.appendType=kafka\n## sql compress\n## compress.enabled=true, can use md5Dictionary to compress\n## compress.enabled=false, use original sql\nplugin.observability.jdbc.sql.compress.enabled=true\n## md5Dictionary metric\n# plugin.observability.md5Dictionary.metric.enabled=true\n# plugin.observability.md5Dictionary.metric.interval=30\n# plugin.observability.md5Dictionary.metric.topic=application-metrics\n# plugin.observability.md5Dictionary.metric.url=/application-metrics\n# plugin.observability.md5Dictionary.metric.appendType=kafka\n## jdbc redirect\n# plugin.integrability.jdbc.redirect.enabled=true\n#\n# -------------------- kafka ---------------------\n# kafka tracing\n# plugin.observability.kafka.tracing.enabled=true\n# kafka metric\n# plugin.observability.kafka.metric.enabled=true\n# plugin.observability.kafka.metric.interval=30\nplugin.observability.kafka.metric.topic=platform-metrics\nplugin.observability.kafka.metric.url=/platform-metrics\n# plugin.observability.kafka.metric.appendType=kafka\n# kafka redirect\n# plugin.integrability.kafka.redirect.enabled=true\n#\n# -------------------- rabbitmq ---------------------\n# rabbitmq tracing\n# plugin.observability.rabbitmq.tracing.enabled=true\n# rabbitmq metric\n# plugin.observability.rabbitmq.metric.enabled=true\n# plugin.observability.rabbitmq.metric.interval=30\nplugin.observability.rabbitmq.metric.topic=platform-metrics\nplugin.observability.rabbitmq.metric.url=/platform-metrics\n# plugin.observability.rabbitmq.metric.appendType=kafka\n# rabbitmq redirect\n# plugin.integrability.rabbitmq.redirect.enabled=true\n#\n# -------------------- redis ---------------------\n# redis tracing\n# plugin.observability.redis.tracing.enabled=true\n# redis metric\n# plugin.observability.redis.metric.enabled=true\n# plugin.observability.redis.metric.interval=30\nplugin.observability.redis.metric.topic=platform-metrics\nplugin.observability.redis.metric.url=/platform-metrics\n# plugin.observability.redis.metric.appendType=kafka\n# redis redirect\n# plugin.integrability.redis.redirect.enabled=true\n#\n# -------------------- springGateway ---------------------\n# springGateway tracing\n# plugin.observability.springGateway.tracing.enabled=true\n# springGateway metric\n# plugin.observability.springGateway.metric.enabled=true\n# plugin.observability.springGateway.metric.interval=30\n# plugin.observability.springGateway.metric.topic=application-metrics\n# plugin.observability.springGateway.metric.url=/application-metrics\n# plugin.observability.springGateway.metric.appendType=kafka\n#\n# -------------------- request ---------------------\n## httpclient tracing\\uFF1Ahttpclient and httpclient5\n# plugin.observability.httpclient.tracing.enabled=true\n## okHttp tracing\n# plugin.observability.okHttp.tracing.enabled=true\n## webclient tracing\n# plugin.observability.webclient.tracing.enabled=true\n## feignClient tracing\n# plugin.observability.feignClient.tracing.enabled=true\n## restTemplate tracing\n# plugin.observability.restTemplate.tracing.enabled=true\n## httpURLConnection tracing\n# plugin.observability.httpURLConnection.tracing.enabled=true\n# -------------------- service name ---------------------\n## add service name to header by name for easemesh. default name: X-Mesh-RPC-Service\n# plugin.integrability.serviceName.addServiceNameHead.propagate.head=X-Mesh-RPC-Service\n#\n# -------------------- mongodb ---------------------\n## mongodb tracing\n# plugin.observability.mongodb.tracing.enabled=true\n## mongodb metric\n# plugin.observability.mongodb.metric.enabled=true\n# plugin.observability.mongodb.metric.interval=30\nplugin.observability.mongodb.metric.topic=platform-metrics\nplugin.observability.mongodb.metric.url=/platform-metrics\n# plugin.observability.mongodb.metric.appendType=kafka\n## mongodb redirect\n# plugin.integrability.mongodb.redirect.enabled=true\n## mongodb foundation\n# plugin.hook.mongodb.foundation.enabled=true\n#\n# -------------------- motan ---------------------\n# motan tracing\n# plugin.observability.motan.tracing.enabled=true\n## motan args collect switch\n# plugin.observability.motan.tracing.args.collect.enabled=false\n## motan result collect switch\n# plugin.observability.motan.tracing.result.collect.enabled=false\n# motan metric\n# plugin.observability.motan.metric.enabled=true\n# plugin.observability.motan.metric.interval=30\nplugin.observability.motan.metric.topic=platform-metrics\nplugin.observability.motan.metric.url=/platform-metrics\n# plugin.observability.motan.metric.appendType=kafka\n#\n# -------------------- dubbo ---------------------\n## dubbo tracing\n#plugin.observability.dubbo.tracing.enabled=true\n## dubbo arguments collect switch\n#plugin.observability.dubbo.tracing.args.collect.enabled=false\n## dubbo return result collect switch\n#plugin.observability.dubbo.tracing.result.collect.enabled=false\n## dubbo metric\n#plugin.observability.dubbo.metric.enabled=true\n#plugin.observability.dubbo.metric.interval=30\n#plugin.observability.dubbo.metric.topic=platform-metrics\n#plugin.observability.dubbo.metric.url=/platform-metrics\n# plugin.observability.dubbo.metric.appendType=kafka\n# -------------------- sofarpc ---------------------\n# sofarpc tracing\n# plugin.observability.sofarpc.tracing.enabled=true\n## sofarpc args collect switch\n# plugin.observability.sofarpc.tracing.args.collect.enabled=false\n## sofarpc result collect switch\n# plugin.observability.sofarpc.tracing.result.collect.enabled=false\n# sofarpc metric\n# plugin.observability.sofarpc.metric.enabled=true\n# plugin.observability.sofarpc.metric.interval=30\nplugin.observability.sofarpc.metric.topic=platform-metrics\nplugin.observability.sofarpc.metric.url=/platform-metrics\n# plugin.observability.sofarpc.metric.appendType=kafka\n# -------------- output ------------------\n## http/kafka/zipkin server host and port for tracing and metric\n###### example ######\n## http: [http|https]://127.0.0.1:8080/report\n## kafka: 192.168.1.2:9092, 192.168.1.3:9092, 192.168.1.3:9092\n## zipkin: [http|https]://127.0.0.1:8080/zipkin\nreporter.outputServer.bootstrapServer=127.0.0.1:9092\nreporter.outputServer.appendType=console\nreporter.outputServer.timeout=1000\n## enabled=false: disable output tracing and metric\n## enabled=true: output tracing and metric\nreporter.outputServer.enabled=true\n## username and password for http basic auth\nreporter.outputServer.username=\nreporter.outputServer.password=\n## enable=false: disable mtls\n## enable=true: enable tls\n## key, cert, ca_cert is enabled when tls.enable=true\nreporter.outputServer.tls.enable=false\nreporter.outputServer.tls.key=\nreporter.outputServer.tls.cert=\nreporter.outputServer.tls.ca_cert=\n# --- redefine to output properties\nreporter.log.output.messageMaxBytes=999900\nreporter.log.output.reportThread=1\nreporter.log.output.queuedMaxSpans=1000\nreporter.log.output.queuedMaxSize=1000000\nreporter.log.output.messageTimeout=1000\n## sender.appendType config\n## [http] send to http server\n## [kafka] send to kafka\n## [console] send to console\n## reporter.log.sender.appendType=console\n## enabled=true:\n# reporter.log.sender.enabled=true\n# reporter.log.sender.url=/application-log\n## sender.appendType config\n## [http] send to http server\n## [kafka] send to kafka\n## [console] send to console\n# reporter.tracing.sender.appendType=http\n# reporter.tracing.sender.appendType=console\n## enabled=true:\nreporter.tracing.sender.enabled=true\n## url is only used in http\n## append to outputServer.bootstrapServer\n###### example ######\n## reporter.outputServer.bootstrapServer=http://127.0.0.1:8080/report\n## reporter.tracing.sender.url=/tracing\n## final output url: http://127.0.0.1:8080/report/tracing\n## if url is start with [http|https], url override reporter.outputServer.bootstrapServer\n###### example ######\n## reporter.outputServer.bootstrapServer=http://127.0.0.1:8080/report\n## reporter.tracing.sender.url=http://127.0.0.10:9090/tracing\n## final output url: http://127.0.0.10:9090/tracing\nreporter.tracing.sender.url=/application-tracing-log\n## topic for kafka use\nreporter.tracing.sender.topic=application-tracing-log\nreporter.tracing.encoder=SpanJsonEncoder\n# --- redefine to output properties\nreporter.tracing.output.messageMaxBytes=999900\nreporter.tracing.output.reportThread=1\nreporter.tracing.output.queuedMaxSpans=1000\nreporter.tracing.output.queuedMaxSize=1000000\nreporter.tracing.output.messageTimeout=1000\n## sender.appendType config\n## [http] send to http server\n## [metricKafka] send to kafka\n## [console] send to console\n#reporter.metric.sender.appendType=http\n#reporter.metric.sender.appendType=console\n## url is only used in http\n## append to outputServer.bootstrapServer\n###### example ######\n## reporter.outputServer.bootstrapServer=http://127.0.0.1:8080/report\n## reporter.metric.sender.url=/metric\n## final output url: http://127.0.0.1:8080/report/metric\n## if url is start with [http|https], url override reporter.outputServer.bootstrapServer\n###### example ######\n## reporter.outputServer.bootstrapServer=http://127.0.0.1:8080/report\n## reporter.metric.sender.url=http://127.0.0.10:9090/metric\n## final output url: http://127.0.0.10:9090/metric\n#reporter.metric.sender.url=/metrics\n\n## support spring boot 3.5.3: jdk17+3.5.3\n#runtime.code.version.points.jdk=jdk17\n#runtime.code.version.points.spring-boot=3.x.x\n"
  },
  {
    "path": "build/src/main/resources/easeagent-log4j2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Configuration status=\"WARN\">\n    <Appenders>\n        <RollingFile name=\"RollingFile\" fileName=\"logs/easeagent.log\" filePattern=\"logs/easeagent-%d{MM-dd-yyyy}.log\"\n                     ignoreExceptions=\"false\">\n            <PatternLayout>\n                <Pattern>%d [%10.10t] %5p %10.10c{1} - %msg%n</Pattern>\n            </PatternLayout>\n            <TimeBasedTriggeringPolicy />\n        </RollingFile>\n        <Console name=\"Console\" target=\"SYSTEM_OUT\">\n            <PatternLayout pattern=\"%d [%10.10t] %5p %10.10c{1} - [%X{traceId}/%X{spanId}] %msg%n\"/>\n        </Console>\n    </Appenders>\n    <Loggers>\n        <Root level=\"info\">\n<!--            <AppenderRef ref=\"RollingFile\"/>-->\n            <AppenderRef ref=\"Console\"/>\n        </Root>\n    </Loggers>\n</Configuration>"
  },
  {
    "path": "build/src/main/resources/user-minimal-cfg.properties",
    "content": "## This a minimal configuration required to start the EaseAgent.\n## In most cases, this is all the configuration items that the normal user needs to be concerned about.\n## \n## When the user specifies a user configration file as this one, the items in user config file will override the default \n## configuration in agent.properties packaged in easeagent.jar\n##\n## -Deaseagent.config.path=/path/to/user-cfg-file\n\nname=demo-springweb\nsystem=demo-system\n\n###\n### report configuration  \n###\nreporter.outputServer.bootstrapServer=http://127.0.0.1:9411\nreporter.outputServer.appendType=console\n\n##\n## Global metric configuration\n## the appendType is same as outputServer, so comment out\n# plugin.observability.global.metric.appendType=console\n\n##\n## tracing sender\n\n## [http] send to http server\n## [kafka] send to kafka\n## [console] send to console\n#\nreporter.tracing.sender.appendType=\n# reporter.tracing.sender.url=http://tempo:9411/api/v2/spans\nreporter.tracing.sender.url=http://localhost:9411/api/v2/spans\n\n## access log sender\n## [http] send to http server\n## [kafka] send to kafka\n## [console] send to console\n## the appendType is same as outputServer, so comment out\n## reporter.log.sender.appendType=console\n\n\n"
  },
  {
    "path": "config/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright (c) 2021, MegaEase\n  ~ All rights reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~   http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>easeagent</artifactId>\n        <groupId>com.megaease.easeagent</groupId>\n        <version>2.3.0</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>config</artifactId>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>plugin-api</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>com.fasterxml.jackson.core</groupId>\n            <artifactId>jackson-databind</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>log4j2-api</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.slf4j</groupId>\n            <artifactId>slf4j-api</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>log4j2-mock</artifactId>\n            <version>${project.version}</version>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.yaml</groupId>\n            <artifactId>snakeyaml</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>com.google.guava</groupId>\n            <artifactId>guava</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>com.github.stefanbirkner</groupId>\n            <artifactId>system-rules</artifactId>\n            <version>1.19.0</version>\n            <scope>test</scope>\n        </dependency>\n    </dependencies>\n</project>\n"
  },
  {
    "path": "config/src/main/java/com/megaease/easeagent/config/AutoRefreshConfigItem.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.config;\n\nimport com.megaease.easeagent.plugin.api.config.Config;\n\nimport java.util.function.BiFunction;\n\npublic class AutoRefreshConfigItem<T> {\n    private volatile T value;\n\n    public AutoRefreshConfigItem(Config config, String name, BiFunction<Config, String, T> func) {\n        ConfigUtils.bindProp(name, config, func, v -> this.value = v);\n    }\n\n    public T getValue() {\n        return value;\n    }\n}\n"
  },
  {
    "path": "config/src/main/java/com/megaease/easeagent/config/CompatibilityConversion.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.megaease.easeagent.config;\n\nimport com.megaease.easeagent.log4j2.Logger;\nimport com.megaease.easeagent.log4j2.LoggerFactory;\nimport com.megaease.easeagent.plugin.api.ProgressFields;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\n\nimport javax.annotation.Nonnull;\nimport java.util.*;\nimport java.util.function.BiFunction;\n\npublic class CompatibilityConversion {\n    private static final Logger LOGGER = LoggerFactory.getLogger(CompatibilityConversion.class);\n    protected static final String[] REQUEST_NAMESPACE = new String[]{\n        ConfigConst.Namespace.HTTPCLIENT,\n        ConfigConst.Namespace.OK_HTTP,\n        ConfigConst.Namespace.WEB_CLIENT,\n        ConfigConst.Namespace.FEIGN_CLIENT,\n        ConfigConst.Namespace.REST_TEMPLATE,\n    };\n    private static final Map<String, BiFunction<String, String, Conversion<?>>> KEY_TO_NAMESPACE;\n    private static final Set<String> METRIC_SKIP;\n    private static final Set<String> TRACING_SKIP;\n\n    static {\n        Map<String, BiFunction<String, String, Conversion<?>>> map = new HashMap<>();\n        map.put(ConfigConst.Observability.KEY_METRICS_ACCESS, SingleBuilder.observability(ConfigConst.Namespace.ACCESS));\n\n        map.put(ConfigConst.Observability.KEY_METRICS_REQUEST, MultipleBuilder.observability(Arrays.asList(REQUEST_NAMESPACE)));\n        map.put(ConfigConst.Observability.KEY_METRICS_JDBC_STATEMENT, SingleBuilder.observability(ConfigConst.Namespace.JDBC_STATEMENT));\n        map.put(ConfigConst.Observability.KEY_METRICS_JDBC_CONNECTION, SingleBuilder.observability(ConfigConst.Namespace.JDBC_CONNECTION));\n        map.put(ConfigConst.Observability.KEY_METRICS_MD5_DICTIONARY, SingleBuilder.observability(ConfigConst.Namespace.MD5_DICTIONARY));\n        map.put(ConfigConst.Observability.KEY_METRICS_RABBIT, SingleBuilder.observability(ConfigConst.Namespace.RABBITMQ));\n        map.put(ConfigConst.Observability.KEY_METRICS_KAFKA, SingleBuilder.observability(ConfigConst.Namespace.KAFKA));\n        map.put(ConfigConst.Observability.KEY_METRICS_CACHE, SingleBuilder.observability(ConfigConst.Namespace.REDIS));\n        map.put(ConfigConst.Observability.KEY_METRICS_JVM_GC, null);\n        map.put(ConfigConst.Observability.KEY_METRICS_JVM_MEMORY, null);\n\n        map.put(ConfigConst.Observability.KEY_TRACE_REQUEST, MultipleBuilder.observability(Arrays.asList(REQUEST_NAMESPACE)));\n        map.put(ConfigConst.Observability.KEY_TRACE_REMOTE_INVOKE, SingleBuilder.observability(ConfigConst.Namespace.WEB_CLIENT));\n        map.put(ConfigConst.Observability.KEY_TRACE_KAFKA, SingleBuilder.observability(ConfigConst.Namespace.KAFKA));\n        map.put(ConfigConst.Observability.KEY_TRACE_JDBC, SingleBuilder.observability(ConfigConst.Namespace.JDBC));\n        map.put(ConfigConst.Observability.KEY_TRACE_CACHE, SingleBuilder.observability(ConfigConst.Namespace.REDIS));\n        map.put(ConfigConst.Observability.KEY_TRACE_RABBIT, SingleBuilder.observability(ConfigConst.Namespace.RABBITMQ));\n\n        KEY_TO_NAMESPACE = map;\n\n        TRACING_SKIP = new HashSet<>();\n        TRACING_SKIP.add(ConfigConst.Observability.KEY_COMM_ENABLED);\n        TRACING_SKIP.add(ConfigConst.Observability.KEY_COMM_SAMPLED_TYPE);\n        TRACING_SKIP.add(ConfigConst.Observability.KEY_COMM_SAMPLED);\n        TRACING_SKIP.add(ConfigConst.Observability.KEY_COMM_OUTPUT);\n        TRACING_SKIP.add(ConfigConst.Observability.KEY_COMM_TAG);\n\n        METRIC_SKIP = new HashSet<>();\n        METRIC_SKIP.add(ConfigConst.Observability.KEY_METRICS_JVM_GC);\n        METRIC_SKIP.add(ConfigConst.Observability.KEY_METRICS_JVM_MEMORY);\n    }\n\n    public static Map<String, String> transform(Map<String, String> oldConfigs) {\n        Map<String, Object> changedKeys = new HashMap<>();\n        Map<String, String> newConfigs = new HashMap<>();\n        for (Map.Entry<String, String> entry : oldConfigs.entrySet()) {\n            Conversion<?> conversion = transformConversion(entry.getKey());\n            Object changed = conversion.transform(newConfigs, entry.getValue());\n            if (conversion.isChange()) {\n                changedKeys.put(entry.getKey(), changed);\n            }\n        }\n        if (changedKeys.isEmpty()) {\n            return oldConfigs;\n        }\n        if (LOGGER.isInfoEnabled()) {\n            LOGGER.info(\"config key has transform: \");\n            for (Map.Entry<String, Object> entry : changedKeys.entrySet()) {\n                LOGGER.info(\"{} to {}\", entry.getKey(), entry.getValue());\n            }\n        }\n        return newConfigs;\n    }\n\n    private static Conversion<?> transformConversion(String key) {\n        if (key.startsWith(\"observability.metrics.\")) {\n            return metricConversion(key);\n        } else if (key.startsWith(\"observability.tracings.\")) {\n            return tracingConversion(key);\n        } else if (key.startsWith(ConfigConst.GlobalCanaryLabels.SERVICE_HEADERS + \".\")) {\n//            return forwardedHeadersConversion(key);\n        }\n        return new FinalConversion(key, false);\n    }\n\n    private static Conversion<?> metricConversion(String key) {\n        if (key.equals(ConfigConst.Observability.METRICS_ENABLED)) {\n            return new MultipleFinalConversion(Arrays.asList(new FinalConversion(ConfigConst.Observability.METRICS_ENABLED, true),\n                new FinalConversion(ConfigConst.Plugin.OBSERVABILITY_GLOBAL_METRIC_ENABLED, true)), true);\n        }\n        return conversion(key, METRIC_SKIP, ConfigConst.PluginID.METRIC);\n    }\n\n\n    private static Conversion<?> tracingConversion(String key) {\n        if (key.equals(ConfigConst.Observability.TRACE_ENABLED)) {\n            return new FinalConversion(ConfigConst.Plugin.OBSERVABILITY_GLOBAL_TRACING_ENABLED, true);\n        }\n        return conversion(key, TRACING_SKIP, ConfigConst.PluginID.TRACING);\n    }\n\n//    private static Conversion<?> forwardedHeadersConversion(String key) {\n//        return new FinalConversion(key.replace(ConfigConst.GlobalCanaryLabels.SERVICE_HEADERS + \".\", ProgressFields.EASEAGENT_PROGRESS_FORWARDED_HEADERS_CONFIG + \".\"), true);\n//    }\n\n    private static Conversion<?> conversion(String key, Set<String> skipSet, String pluginId) {\n        String[] keys = ConfigConst.split(key);\n        if (keys.length < 4) {\n            return new FinalConversion(key, false);\n        }\n        String key2 = keys[2];\n        if (skipSet.contains(key2)) {\n            return new FinalConversion(key, false);\n        }\n        BiFunction<String, String, Conversion<?>> builder = KEY_TO_NAMESPACE.get(key2);\n        if (builder == null) {\n            builder = SingleBuilder.observability(key2);\n        }\n        String[] properties = new String[keys.length - 3];\n        int index = 0;\n        for (int i = 3; i < keys.length; i++) {\n            properties[index++] = keys[i];\n        }\n        return builder.apply(pluginId, ConfigConst.join(properties));\n    }\n\n    interface Conversion<K> {\n        K transform(Map<String, String> configs, String value);\n\n        boolean isChange();\n    }\n\n    static class FinalConversion implements Conversion<String> {\n        private final String key;\n        private final boolean change;\n\n        public FinalConversion(String key, boolean change) {\n            this.key = key;\n            this.change = change;\n        }\n\n        @Override\n        public String transform(Map<String, String> configs, String value) {\n            configs.put(key, value);\n            return key;\n        }\n\n        public boolean isChange() {\n            return change;\n        }\n    }\n\n    static class MultipleFinalConversion implements Conversion<List<String>> {\n        private final List<FinalConversion> conversions;\n        private final boolean change;\n\n        MultipleFinalConversion(@Nonnull List<FinalConversion> conversions, boolean change) {\n            this.conversions = conversions;\n            this.change = change;\n        }\n\n        @Override\n        public List<String> transform(Map<String, String> configs, String value) {\n            List<String> result = new ArrayList<>();\n            for (FinalConversion conversion : conversions) {\n                result.add(conversion.transform(configs, value));\n            }\n            return result;\n        }\n\n        @Override\n        public boolean isChange() {\n            return change;\n        }\n    }\n\n    static class SingleConversion implements Conversion<String> {\n        private final String domain;\n        private final String namespace;\n        private final String id;\n        private final String properties;\n\n        public SingleConversion(String domain, String namespace, String id, String properties) {\n            this.domain = domain;\n            this.namespace = namespace;\n            this.id = id;\n            this.properties = properties;\n        }\n\n        @Override\n        public String transform(Map<String, String> configs, String value) {\n            String key = ConfigUtils.buildPluginProperty(domain, namespace, id, properties);\n            configs.put(key, value);\n            return key;\n        }\n\n        @Override\n        public boolean isChange() {\n            return true;\n        }\n    }\n\n    static class MultipleConversion implements Conversion<List<String>> {\n        private final String domain;\n        private final List<String> namespaces;\n        private final String id;\n        private final String properties;\n\n        public MultipleConversion(String domain, List<String> namespaces, String id, String properties) {\n            this.domain = domain;\n            this.namespaces = namespaces;\n            this.id = id;\n            this.properties = properties;\n        }\n\n        @Override\n        public List<String> transform(Map<String, String> configs, String value) {\n            List<String> keys = new ArrayList<>();\n            for (String namespace : namespaces) {\n                String key = ConfigUtils.buildPluginProperty(domain, namespace, id, properties);\n                keys.add(key);\n                configs.put(key, value);\n            }\n            return keys;\n        }\n\n        @Override\n        public boolean isChange() {\n            return true;\n        }\n    }\n\n    static class SingleBuilder implements BiFunction<String, String, Conversion<?>> {\n        private final String domain;\n        private final String namespace;\n\n        public SingleBuilder(String domain, String namespace) {\n            this.domain = domain;\n            this.namespace = namespace;\n        }\n\n        @Override\n        public Conversion apply(String id, String properties) {\n            return new SingleConversion(domain, namespace, id, properties);\n        }\n\n        static SingleBuilder observability(String namespace) {\n            return new SingleBuilder(ConfigConst.OBSERVABILITY, namespace);\n        }\n    }\n\n    static class MultipleBuilder implements BiFunction<String, String, Conversion<?>> {\n        private final String domain;\n        private final List<String> namespaces;\n\n        public MultipleBuilder(String domain, List<String> namespaces) {\n            this.domain = domain;\n            this.namespaces = namespaces;\n        }\n\n        @Override\n        public Conversion apply(String id, String properties) {\n            return new MultipleConversion(domain, namespaces, id, properties);\n        }\n\n        static MultipleBuilder observability(List<String> namespaces) {\n            return new MultipleBuilder(ConfigConst.OBSERVABILITY, namespaces);\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "config/src/main/java/com/megaease/easeagent/config/ConfigAware.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.config;\n\nimport com.megaease.easeagent.plugin.api.config.Config;\n\npublic interface ConfigAware {\n    void setConfig(Config config);\n}\n"
  },
  {
    "path": "config/src/main/java/com/megaease/easeagent/config/ConfigFactory.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.config;\n\nimport com.megaease.easeagent.log4j2.Logger;\nimport com.megaease.easeagent.log4j2.LoggerFactory;\nimport com.megaease.easeagent.plugin.utils.ImmutableMap;\nimport com.megaease.easeagent.plugin.utils.SystemEnv;\nimport com.megaease.easeagent.plugin.utils.common.JsonUtil;\nimport com.megaease.easeagent.plugin.utils.common.StringUtils;\n\nimport java.io.File;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.TreeMap;\n\npublic class ConfigFactory {\n    private static final Logger LOGGER = LoggerFactory.getLogger(ConfigFactory.class);\n    private static final String CONFIG_PROP_FILE = \"agent.properties\";\n    private static final String CONFIG_YAML_FILE = \"agent.yaml\";\n\n    public static final String AGENT_CONFIG_PATH_PROP_KEY = \"easeagent.config.path\";\n\n    public static final String AGENT_SERVICE = \"name\";\n    public static final String AGENT_SYSTEM = \"system\";\n\n    public static final String AGENT_SERVER_PORT = \"easeagent.server.port\";\n    public static final String AGENT_SERVER_ENABLED = \"easeagent.server.enabled\";\n\n    public static final String EASEAGENT_ENV_CONFIG = \"EASEAGENT_ENV_CONFIG\";\n\n    private static final Map<String, String> AGENT_CONFIG_KEYS_TO_PROPS =\n        ImmutableMap.<String, String>builder()\n            .put(\"easeagent.name\", AGENT_SERVICE)\n            .put(\"easeagent.system\", AGENT_SYSTEM)\n            .put(\"easeagent.server.port\", AGENT_SERVER_PORT)\n            .put(\"easeagent.server.enabled\", AGENT_SERVER_ENABLED)\n            .build();\n\n    // OTEL_SERVICE_NAME=xxx\n    private static final Map<String, String> AGENT_ENV_KEY_TO_PROPS = new HashMap<>();\n\n\n    static {\n        for (Map.Entry<String, String> entry : AGENT_CONFIG_KEYS_TO_PROPS.entrySet()) {\n            // dot.case -> UPPER_UNDERSCORE\n            AGENT_ENV_KEY_TO_PROPS.put(\n                ConfigPropertiesUtils.toEnvVarName(entry.getKey()),\n                entry.getValue()\n            );\n        }\n    }\n\n    /**\n     * update config value from environment variables and java properties\n     * <p>\n     * java properties > environment variables > env:EASEAGENT_ENV_CONFIG={} > default\n     */\n    static Map<String, String> updateEnvCfg() {\n        Map<String, String> envCfg = new TreeMap<>();\n\n        String configEnv = SystemEnv.get(EASEAGENT_ENV_CONFIG);\n        if (StringUtils.isNotEmpty(configEnv)) {\n            Map<String, Object> map = JsonUtil.toMap(configEnv);\n            Map<String, String> strMap = new HashMap<>();\n            if (!map.isEmpty()) {\n                for (Map.Entry<String, Object> entry : map.entrySet()) {\n                    strMap.put(entry.getKey(), entry.getValue().toString());\n                }\n            }\n            envCfg.putAll(strMap);\n        }\n\n        // override by environment variables, eg: export EASEAGENT_NAME=xxx\n        for (Map.Entry<String, String> entry : AGENT_ENV_KEY_TO_PROPS.entrySet()) {\n            String value = SystemEnv.get(entry.getKey());\n            if (!StringUtils.isEmpty(value)) {\n                envCfg.put(entry.getValue(), value);\n            }\n        }\n\n        // override by java properties; eg: java -Deaseagent.name=xxx\n        for (Map.Entry<String, String> entry : AGENT_CONFIG_KEYS_TO_PROPS.entrySet()) {\n            String value = System.getProperty(entry.getKey());\n            if (!StringUtils.isEmpty(value)) {\n                envCfg.put(entry.getValue(), value);\n            }\n        }\n\n        return envCfg;\n    }\n\n    private ConfigFactory() {\n    }\n\n    /**\n     * Get config file path from system properties or environment variables\n     */\n    public static String getConfigPath() {\n        // get config path from -Deaseagent.config.path=/easeagent/agent.properties || export EASEAGENT_CONFIG_PATH=/easeagent/agent.properties\n        String path = ConfigPropertiesUtils.getString(AGENT_CONFIG_PATH_PROP_KEY);\n\n        if (StringUtils.isEmpty(path)) {\n            // eg: -Dotel.javaagent.configuration-file=/easeagent/agent.properties || export OTEL_JAVAAGENT_CONFIGURATION_FILE=/easeagent/agent.properties\n            path = OtelSdkConfigs.getConfigPath();\n        }\n        return path;\n    }\n\n    public static GlobalConfigs loadConfigs(String pathname, ClassLoader loader) {\n        // load property configuration file if exist\n        GlobalConfigs configs = loadDefaultConfigs(loader, CONFIG_PROP_FILE);\n\n        // load yaml configuration file if exist\n        GlobalConfigs yConfigs = loadDefaultConfigs(loader, CONFIG_YAML_FILE);\n        configs.mergeConfigs(yConfigs);\n\n        // override by user special config file\n        if (StringUtils.isNotEmpty(pathname)) {\n            GlobalConfigs configsFromOuterFile = ConfigLoader.loadFromFile(new File(pathname));\n            LOGGER.info(\"Loaded user special config file: {}\", pathname);\n            configs.mergeConfigs(configsFromOuterFile);\n        }\n\n        // override by opentelemetry sdk env config\n        configs.updateConfigsNotNotify(OtelSdkConfigs.updateEnvCfg());\n\n        // check environment cfg override\n        configs.updateConfigsNotNotify(updateEnvCfg());\n\n        if (LOGGER.isDebugEnabled()) {\n            final String display = configs.toPrettyDisplay();\n            LOGGER.debug(\"Loaded conf:\\n{}\", display);\n        }\n        return configs;\n    }\n\n    private static GlobalConfigs loadDefaultConfigs(ClassLoader loader, String file) {\n        GlobalConfigs globalConfigs = JarFileConfigLoader.load(file);\n        if (globalConfigs != null) {\n            return globalConfigs;\n        }\n        return ConfigLoader.loadFromClasspath(loader, file);\n    }\n\n}\n"
  },
  {
    "path": "config/src/main/java/com/megaease/easeagent/config/ConfigLoader.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.config;\n\nimport com.megaease.easeagent.config.yaml.YamlReader;\nimport com.megaease.easeagent.log4j2.Logger;\nimport com.megaease.easeagent.log4j2.LoggerFactory;\nimport org.yaml.snakeyaml.parser.ParserException;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Properties;\n\npublic class ConfigLoader {\n    private static final Logger LOGGER = LoggerFactory.getLogger(ConfigLoader.class);\n\n    private static boolean checkYaml(String filename) {\n        return filename.endsWith(\".yaml\") || filename.endsWith(\".yml\");\n    }\n\n    static GlobalConfigs loadFromFile(File file) {\n        try (FileInputStream in = new FileInputStream(file)) {\n            return ConfigLoader.loadFromStream(in, file.getAbsolutePath());\n        } catch (IOException e) {\n            LOGGER.warn(\"Load config file failure: {}\", file.getAbsolutePath());\n        }\n        return new GlobalConfigs(Collections.emptyMap());\n    }\n\n    static GlobalConfigs loadFromStream(InputStream in, String filename) throws IOException {\n        if (in != null) {\n            Map<String, String> map;\n            if (checkYaml(filename)) {\n                try {\n                    map = new YamlReader().load(in).compress();\n                } catch (ParserException e) {\n                    LOGGER.warn(\"Wrong Yaml format, load config file failure: {}\", filename);\n                    map = Collections.emptyMap();\n                }\n            } else {\n                map = extractPropsMap(in);\n            }\n            return new GlobalConfigs(map);\n        } else {\n            return new GlobalConfigs(Collections.emptyMap());\n        }\n    }\n\n    private static HashMap<String, String> extractPropsMap(InputStream in) throws IOException {\n        Properties properties = new Properties();\n        properties.load(in);\n        HashMap<String, String> map = new HashMap<>();\n        for (String one : properties.stringPropertyNames()) {\n            map.put(one, properties.getProperty(one));\n        }\n        return map;\n    }\n\n    static GlobalConfigs loadFromClasspath(ClassLoader classLoader, String file) {\n        try (InputStream in = classLoader.getResourceAsStream(file)) {\n            return ConfigLoader.loadFromStream(in, file);\n        } catch (IOException e) {\n            LOGGER.warn(\"Load config file:{} by classloader:{} failure: {}\", file, classLoader.toString(), e);\n        }\n\n        return new GlobalConfigs(Collections.emptyMap());\n    }\n}\n"
  },
  {
    "path": "config/src/main/java/com/megaease/easeagent/config/ConfigManagerMXBean.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.config;\n\nimport java.io.IOException;\nimport java.util.List;\nimport java.util.Map;\n\npublic interface ConfigManagerMXBean {\n    void updateConfigs(Map<String, String> configs);\n\n    void updateService(String json, String version) throws IOException;\n\n    void updateCanary(String json, String version) throws IOException;\n\n    void updateService2(Map<String, String> configs, String version);\n\n    void updateCanary2(Map<String, String> configs, String version);\n\n    Map<String, String> getConfigs();\n\n    List<String> availableConfigNames();\n\n    default void healthz() {\n    }\n}\n"
  },
  {
    "path": "config/src/main/java/com/megaease/easeagent/config/ConfigNotifier.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.config;\n\nimport com.megaease.easeagent.log4j2.Logger;\nimport com.megaease.easeagent.log4j2.LoggerFactory;\nimport com.megaease.easeagent.plugin.api.config.ChangeItem;\nimport com.megaease.easeagent.plugin.api.config.ConfigChangeListener;\n\nimport java.util.List;\nimport java.util.concurrent.CopyOnWriteArrayList;\nimport java.util.stream.Collectors;\n\npublic class ConfigNotifier {\n    private static final Logger LOGGER = LoggerFactory.getLogger(ConfigNotifier.class);\n    private final CopyOnWriteArrayList<ConfigChangeListener> listeners = new CopyOnWriteArrayList<>();\n    private final String prefix;\n\n    public ConfigNotifier(String prefix) {\n        this.prefix = prefix;\n    }\n\n    public Runnable addChangeListener(ConfigChangeListener listener) {\n        final boolean add = listeners.add(listener);\n        return () -> {\n            if (add) {\n                listeners.remove(listener);\n            }\n        };\n    }\n\n    public void handleChanges(List<ChangeItem> list) {\n        final List<ChangeItem> changes = this.prefix.isEmpty() ? list : filterChanges(list);\n        if (changes.isEmpty()) {\n            return;\n        }\n        listeners.forEach(one -> {\n            try {\n                one.onChange(changes);\n            } catch (Exception e) {\n                LOGGER.warn(\"Notify config changes to listener failure: {}\", e);\n            }\n        });\n    }\n\n    private List<ChangeItem> filterChanges(List<ChangeItem> list) {\n        return list.stream().filter(one -> one.getFullName().startsWith(prefix))\n            .map(e -> new ChangeItem(e.getFullName().substring(prefix.length()), e.getFullName(), e.getOldValue(), e.getNewValue()))\n            .collect(Collectors.toList());\n    }\n}\n"
  },
  {
    "path": "config/src/main/java/com/megaease/easeagent/config/ConfigPropertiesUtils.java",
    "content": "/*\n * Copyright (c) 2022, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.config;\n\nimport com.megaease.easeagent.plugin.utils.SystemEnv;\n\nimport javax.annotation.Nullable;\nimport java.util.Locale;\n\n/**\n * Get config from system properties or environment variables.\n */\nfinal class ConfigPropertiesUtils {\n\n    public static boolean getBoolean(String propertyName, boolean defaultValue) {\n        String strValue = getString(propertyName);\n        return strValue == null ? defaultValue : Boolean.parseBoolean(strValue);\n    }\n\n    public static int getInt(String propertyName, int defaultValue) {\n        String strValue = getString(propertyName);\n        if (strValue == null) {\n            return defaultValue;\n        }\n        try {\n            return Integer.parseInt(strValue);\n        } catch (NumberFormatException ignored) {\n            return defaultValue;\n        }\n    }\n\n    @Nullable\n    public static String getString(String propertyName) {\n        String value = System.getProperty(propertyName);\n        if (value != null) {\n            return value;\n        }\n        return SystemEnv.get(toEnvVarName(propertyName));\n    }\n\n    /**\n     * dot.case -> UPPER_UNDERSCORE\n     */\n    public static String toEnvVarName(String propertyName) {\n        return propertyName.toUpperCase(Locale.ROOT).replace('-', '_').replace('.', '_');\n    }\n\n    private ConfigPropertiesUtils() {\n    }\n}\n"
  },
  {
    "path": "config/src/main/java/com/megaease/easeagent/config/ConfigUtils.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.config;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.megaease.easeagent.plugin.api.config.ChangeItem;\nimport com.megaease.easeagent.plugin.api.config.Config;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\n\nimport java.io.IOException;\nimport java.util.*;\nimport java.util.function.BiFunction;\nimport java.util.function.Consumer;\nimport java.util.stream.Collectors;\n\nimport static com.megaease.easeagent.plugin.api.config.ConfigConst.*;\n\npublic class ConfigUtils {\n    private ConfigUtils() {\n    }\n\n    public static <R> void bindProp(String name, Config configs, BiFunction<Config, String, R> func, Consumer<R> consumer, R def) {\n        Runnable process = () -> {\n            R result = func.apply(configs, name);\n            result = firstNotNull(result, def);\n            if (result != null) {\n                consumer.accept(result);\n            }\n        };\n        process.run();\n        configs.addChangeListener(list -> {\n            boolean hasChange = list.stream().map(ChangeItem::getFullName).anyMatch(fn -> fn.equals(name));\n            if (hasChange) {\n                process.run();\n            }\n        });\n    }\n\n    @SafeVarargs\n    private static <R> R firstNotNull(R... ars) {\n        for (R one : ars) {\n            if (one != null) {\n                return one;\n            }\n        }\n        return null;\n    }\n\n    public static <R> void bindProp(String name, Config configs, BiFunction<Config, String, R> func, Consumer<R> consumer) {\n        bindProp(name, configs, func, consumer, null);\n    }\n\n    public static Map<String, String> json2KVMap(String json) throws IOException {\n        ObjectMapper mapper = new ObjectMapper();\n        JsonNode node = mapper.readTree(json);\n        List<Map.Entry<String, String>> list = extractKVs(null, node);\n        return list.stream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));\n    }\n\n    public static List<Map.Entry<String, String>> extractKVs(String prefix, JsonNode node) {\n        List<Map.Entry<String, String>> rst = new LinkedList<>();\n        if (node.isObject()) {\n            Iterator<String> names = node.fieldNames();\n            while (names.hasNext()) {\n                String current = names.next();\n                rst.addAll(extractKVs(join(prefix, current), node.path(current)));\n            }\n        } else if (node.isArray()) {\n            int len = node.size();\n            for (int i = 0; i < len; i++) {\n                rst.addAll(extractKVs(join(prefix, i + \"\"), node.path(i)));\n            }\n        } else {\n            rst.add(new AbstractMap.SimpleEntry<>(prefix, node.asText(\"\")));\n        }\n        return rst;\n    }\n\n    private static String join(String prefix, String current) {\n        return prefix == null ? current : ConfigConst.join(prefix, current);\n    }\n\n    public static boolean isGlobal(String namespace) {\n        return PLUGIN_GLOBAL.equals(namespace);\n    }\n\n    public static boolean isPluginConfig(String key) {\n        return key != null && key.startsWith(PLUGIN_PREFIX);\n    }\n\n    public static boolean isPluginConfig(String key, String domain, String namespace, String id) {\n        return key != null && key.startsWith(ConfigConst.join(PLUGIN, domain, namespace, id));\n    }\n\n    public static PluginProperty pluginProperty(String path) {\n        String[] configs = path.split(\"\\\\\" + DELIMITER);\n        if (configs.length < 5) {\n            throw new ValidateUtils.ValidException(String.format(\"Property[%s] must be format: %s\", path, ConfigConst.join(PLUGIN, \"<Domain>\", \"<Namespace>\", \"<Id>\", \"<Properties>\")));\n        }\n\n        for (int idOffsetEnd = 3; idOffsetEnd < configs.length - 1; idOffsetEnd++) {\n            new PluginProperty(configs[1], configs[2],\n                ConfigConst.join(Arrays.copyOfRange(configs, 3, idOffsetEnd)),\n                ConfigConst.join(Arrays.copyOfRange(configs, idOffsetEnd + 1, configs.length)));\n        }\n\n        return new PluginProperty(configs[1], configs[2], configs[3], ConfigConst.join(Arrays.copyOfRange(configs, 4, configs.length)));\n    }\n\n\n    public static String requireNonEmpty(String obj, String message) {\n        if (obj == null || obj.trim().isEmpty()) {\n            throw new ValidateUtils.ValidException(message);\n        }\n        return obj.trim();\n    }\n\n    public static String buildPluginProperty(String domain, String namespace, String id, String property) {\n        return String.format(PLUGIN_FORMAT, domain, namespace, id, property);\n    }\n\n    public static String buildCodeVersionKey(String key) {\n        return RUNTIME_CODE_VERSION_POINTS_PREFIX + key;\n    }\n\n    /**\n     * extract config item with a fromPrefix to and convert the prefix to 'toPrefix' for configuration Compatibility\n     *\n     * @param cfg        config source map\n     * @param fromPrefix from\n     * @param toPrefix   to\n     * @return Extracted and converted KV map\n     */\n    public static Map<String, String> extractAndConvertPrefix(Map<String, String> cfg, String fromPrefix, String toPrefix) {\n        Map<String, String> convert = new HashMap<>();\n\n        Set<String> keys = new HashSet<>();\n        cfg.forEach((key, value) -> {\n            if (key.startsWith(fromPrefix)) {\n                keys.add(key);\n                key = toPrefix + key.substring(fromPrefix.length());\n                convert.put(key, value);\n            }\n        });\n\n        // override, new configuration KV override previous KV\n        convert.putAll(extractByPrefix(cfg, toPrefix));\n\n        return convert;\n    }\n\n    /**\n     * Extract config items from config by prefix\n     *\n     * @param config config\n     * @param prefix prefix\n     * @return Extracted KV\n     */\n    public static Map<String, String> extractByPrefix(Config config, String prefix) {\n        return extractByPrefix(config.getConfigs(), prefix);\n    }\n\n    public static Map<String, String> extractByPrefix(Map<String, String> cfg, String prefix) {\n        Map<String, String> extract = new TreeMap<>();\n\n        // override, new configuration KV override previous KV\n        cfg.forEach((key, value) -> {\n            if (key.startsWith(prefix)) {\n                extract.put(key, value);\n            }\n        });\n\n        return extract;\n    }\n\n    public static int isChanged(String name, Map<String, String> map, String check) {\n        if (map.get(name) == null || map.get(name).equals(check)) {\n            return 0;\n        }\n        return 1;\n    }\n}\n"
  },
  {
    "path": "config/src/main/java/com/megaease/easeagent/config/Configs.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.config;\n\n\nimport com.megaease.easeagent.log4j2.Logger;\nimport com.megaease.easeagent.log4j2.LoggerFactory;\nimport com.megaease.easeagent.plugin.api.config.ChangeItem;\nimport com.megaease.easeagent.plugin.api.config.Config;\nimport com.megaease.easeagent.plugin.api.config.ConfigChangeListener;\n\nimport java.util.*;\nimport java.util.stream.Collectors;\n\npublic class Configs implements Config {\n    private static final Logger LOGGER = LoggerFactory.getLogger(Configs.class);\n    protected Map<String, String> source;\n    protected ConfigNotifier notifier;\n\n    protected Configs() {\n    }\n\n    public Configs(Map<String, String> source) {\n        this.source = new TreeMap<>(source);\n        notifier = new ConfigNotifier(\"\");\n    }\n\n    public void updateConfigsNotNotify(Map<String, String> changes) {\n        this.source.putAll(changes);\n    }\n\n    public void updateConfigs(Map<String, String> changes) {\n        Map<String, String> dump = new TreeMap<>(this.source);\n        List<ChangeItem> items = new LinkedList<>();\n        changes.forEach((name, value) -> {\n            String old = dump.get(name);\n            if (!Objects.equals(old, value)) {\n                dump.put(name, value);\n                items.add(new ChangeItem(name, name, old, value));\n            }\n        });\n        if (!items.isEmpty()) {\n            LOGGER.info(\"change items: {}\", items);\n            this.source = dump;\n            this.notifier.handleChanges(items);\n        }\n    }\n\n    protected boolean hasText(String text) {\n        return text != null && text.trim().length() > 0;\n    }\n\n    @Override\n    public Map<String, String> getConfigs() {\n        return new TreeMap<>(this.source);\n    }\n\n    public String toPrettyDisplay() {\n        return this.source.toString();\n    }\n\n    public boolean hasPath(String path) {\n        return this.source.containsKey(path);\n    }\n\n\n    public String getString(String name) {\n        return this.source.get(name);\n    }\n\n    public String getString(String name, String defVal) {\n        String val = this.source.get(name);\n\n        return val == null ? defVal : val;\n    }\n\n    public Integer getInt(String name) {\n        String value = this.source.get(name);\n        if (value == null) {\n            return null;\n        }\n        try {\n            return Integer.parseInt(value);\n        } catch (Exception e) {\n            return null;\n        }\n    }\n\n    @Override\n    public Integer getInt(String name, int defValue) {\n        Integer anInt = getInt(name);\n        if (anInt == null) {\n            return defValue;\n        }\n        return anInt;\n    }\n\n    public Boolean getBooleanNullForUnset(String name) {\n        String value = this.source.get(name);\n        if (value == null) {\n            return null;\n        }\n        return value.equalsIgnoreCase(\"yes\") || value.equalsIgnoreCase(\"true\");\n    }\n\n    public Boolean getBoolean(String name) {\n        String value = this.source.get(name);\n        if (value == null) {\n            return false;\n        }\n        return value.equalsIgnoreCase(\"yes\") || value.equalsIgnoreCase(\"true\");\n    }\n\n    @Override\n    public Boolean getBoolean(String name, boolean defValue) {\n        Boolean aBoolean = getBooleanNullForUnset(name);\n        if (aBoolean == null) {\n            return defValue;\n        }\n        return aBoolean;\n    }\n\n    public Double getDouble(String name) {\n        String value = this.source.get(name);\n        if (value == null) {\n            return null;\n        }\n        try {\n            return Double.parseDouble(value);\n        } catch (Exception e) {\n            return null;\n        }\n    }\n\n    @Override\n    public Double getDouble(String name, double defValue) {\n        Double aDouble = getDouble(name);\n        if (aDouble == null) {\n            return defValue;\n        }\n        return aDouble;\n    }\n\n    public Long getLong(String name) {\n        String value = this.source.get(name);\n        if (value == null) {\n            return null;\n        }\n        try {\n            return Long.parseLong(value);\n        } catch (Exception e) {\n            return null;\n        }\n    }\n\n    @Override\n    public Long getLong(String name, long defValue) {\n        Long aLong = getLong(name);\n        if (aLong == null) {\n            return defValue;\n        }\n        return aLong;\n    }\n\n    public List<String> getStringList(String name) {\n        String value = this.source.get(name);\n        if (value == null) {\n            return Collections.emptyList();\n        }\n        return Arrays.stream(value.split(\",\")).filter(Objects::nonNull).collect(Collectors.toList());\n    }\n\n    @Override\n    public Runnable addChangeListener(ConfigChangeListener listener) {\n        return notifier.addChangeListener(listener);\n    }\n\n    @Override\n    public Set<String> keySet() {\n        return this.source.keySet();\n    }\n}\n"
  },
  {
    "path": "config/src/main/java/com/megaease/easeagent/config/GlobalConfigs.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.config;\n\nimport com.megaease.easeagent.config.report.ReportConfigAdapter;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\n\nimport java.io.IOException;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.TreeMap;\n\npublic class GlobalConfigs extends Configs implements ConfigManagerMXBean {\n    Configs originalConfig;\n\n    public GlobalConfigs(Map<String, String> source) {\n        super();\n        this.originalConfig = new Configs(source);\n        // reporter adapter\n        Map<String, String> map = new TreeMap<>(source);\n        ReportConfigAdapter.convertConfig(map);\n        // check environment config\n        this.source = new TreeMap<>(map);\n        this.notifier = new ConfigNotifier(\"\");\n    }\n\n    public Configs getOriginalConfig() {\n        return this.originalConfig;\n    }\n\n    @Override\n    public void updateConfigsNotNotify(Map<String, String> changes) {\n        // update original config\n        Map<String, String> newGlobalCfg = new TreeMap<>(this.originalConfig.getConfigs());\n        newGlobalCfg.putAll(changes);\n        this.originalConfig.updateConfigsNotNotify(changes);\n\n        // report adapter\n        ReportConfigAdapter.convertConfig(newGlobalCfg);\n\n        super.updateConfigsNotNotify(newGlobalCfg);\n    }\n\n    @Override\n    public void updateConfigs(Map<String, String> changes) {\n        // update original config\n        Map<String, String> newGlobalCfg = new TreeMap<>(this.originalConfig.getConfigs());\n        newGlobalCfg.putAll(changes);\n        this.originalConfig.updateConfigsNotNotify(changes);\n\n        // report adapter\n        ReportConfigAdapter.convertConfig(newGlobalCfg);\n\n        super.updateConfigs(newGlobalCfg);\n    }\n\n    public void mergeConfigs(GlobalConfigs configs) {\n        Map<String, String> merged = configs.getOriginalConfig().getConfigs();\n        if (merged.isEmpty()) {\n            return;\n        }\n        this.updateConfigsNotNotify(merged);\n        return;\n    }\n\n    @Override\n    public List<String> availableConfigNames() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void updateService(String json, String version) throws IOException {\n        this.updateConfigs(ConfigUtils.json2KVMap(json));\n    }\n\n    @Override\n    public void updateCanary(String json, String version) throws IOException {\n        Map<String, String> originals = ConfigUtils.json2KVMap(json);\n        HashMap<String, String> rst = new HashMap<>();\n        originals.forEach((k, v) -> rst.put(ConfigConst.join(ConfigConst.GLOBAL_CANARY_LABELS, k), v));\n        this.updateConfigs(rst);\n    }\n\n    @Override\n    public void updateService2(Map<String, String> configs, String version) {\n        this.updateConfigs(configs);\n    }\n\n    @Override\n    public void updateCanary2(Map<String, String> configs, String version) {\n        HashMap<String, String> rst = new HashMap<>();\n        for (Map.Entry<String, String> entry : configs.entrySet()) {\n            String k = entry.getKey();\n            String v = entry.getValue();\n            rst.put(ConfigConst.join(ConfigConst.GLOBAL_CANARY_LABELS, k), v);\n        }\n        this.updateConfigs(CompatibilityConversion.transform(rst));\n    }\n}\n"
  },
  {
    "path": "config/src/main/java/com/megaease/easeagent/config/JarFileConfigLoader.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.config;\n\nimport com.megaease.easeagent.log4j2.Logger;\nimport com.megaease.easeagent.log4j2.LoggerFactory;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.jar.JarFile;\nimport java.util.zip.ZipEntry;\n\npublic class JarFileConfigLoader {\n    private static final Logger LOGGER = LoggerFactory.getLogger(JarFileConfigLoader.class);\n\n    static GlobalConfigs load(String file) {\n        String agentJarPath = System.getProperty(ConfigConst.AGENT_JAR_PATH);\n        if (agentJarPath == null) {\n            return null;\n        }\n        try {\n            JarFile jarFile = new JarFile(new File(agentJarPath));\n            ZipEntry zipEntry = jarFile.getEntry(file);\n            if (zipEntry == null) {\n                return null;\n            }\n            try (InputStream in = jarFile.getInputStream(zipEntry)) {\n                return ConfigLoader.loadFromStream(in, file);\n            } catch (IOException e) {\n                LOGGER.debug(\"Load config file:{} failure: {}\", file, e);\n            }\n        } catch (IOException e) {\n            LOGGER.debug(\"create JarFile:{} failure: {}\", agentJarPath, e);\n        }\n        return null;\n    }\n}\n"
  },
  {
    "path": "config/src/main/java/com/megaease/easeagent/config/OtelSdkConfigs.java",
    "content": "/*\n * Copyright (c) 2022, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.config;\n\nimport com.google.common.base.Splitter;\nimport com.megaease.easeagent.plugin.utils.ImmutableMap;\nimport com.megaease.easeagent.plugin.utils.SystemEnv;\nimport com.megaease.easeagent.plugin.utils.common.StringUtils;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.TreeMap;\n\n\n/**\n * Compatible with opentelemetry-java.\n * <p>\n * {@see https://github.com/open-telemetry/opentelemetry-java/blob/main/sdk-extensions/autoconfigure/README.md#disabling-opentelemetrysdk}\n */\npublic class OtelSdkConfigs {\n    private static final String OTEL_RESOURCE_ATTRIBUTES_KEY = \"otel.resource.attributes\";\n\n    private static final String CONFIG_PATH_PROP_KEY = \"otel.javaagent.configuration-file\";\n\n    private static final Splitter.MapSplitter OTEL_RESOURCE_ATTRIBUTES_SPLITTER\n        = Splitter.on(\",\")\n        .omitEmptyStrings()\n        .withKeyValueSeparator(\"=\");\n\n    private static final Map<String, String> SDK_ATTRIBUTES_TO_EASE_AGENT_PROPS =\n        ImmutableMap.<String, String>builder()\n            .put(\"sdk.disabled\", \"easeagent.server.enabled\")\n            .put(\"service.name\", \"name\") //\"easeagent.name\"\n            .put(\"service.namespace\", \"system\") //\"easeagent.system\"\n            .build();\n\n    // -Dotel.service.name=xxx\n    private static final Map<String, String> OTEL_SDK_PROPS_TO_EASE_AGENT_PROPS = new HashMap<>();\n\n    // OTEL_SERVICE_NAME=xxx\n    private static final Map<String, String> OTEL_SDK_ENV_VAR_TO_EASE_AGENT_PROPS = new HashMap<>();\n\n    static {\n        for (Map.Entry<String, String> entry : SDK_ATTRIBUTES_TO_EASE_AGENT_PROPS.entrySet()) {\n            // lower.hyphen -> UPPER_UNDERSCORE\n            OTEL_SDK_PROPS_TO_EASE_AGENT_PROPS.put(\n                \"otel.\" + entry.getKey(),\n                entry.getValue()\n            );\n        }\n\n        for (Map.Entry<String, String> entry : OTEL_SDK_PROPS_TO_EASE_AGENT_PROPS.entrySet()) {\n            // dot.case -> UPPER_UNDERSCORE\n            OTEL_SDK_ENV_VAR_TO_EASE_AGENT_PROPS.put(\n                ConfigPropertiesUtils.toEnvVarName(entry.getKey()),\n                entry.getValue()\n            );\n        }\n    }\n\n    /**\n     * Get config path from java properties or environment variables\n     */\n    static String getConfigPath() {\n        return ConfigPropertiesUtils.getString(CONFIG_PATH_PROP_KEY);\n    }\n\n\n    /**\n     * update config value from environment variables and java properties\n     * <p>\n     * java properties > environment variables > OTEL_RESOURCE_ATTRIBUTES\n     */\n    static Map<String, String> updateEnvCfg() {\n        Map<String, String> envCfg = new TreeMap<>();\n\n        String configEnv = ConfigPropertiesUtils.getString(OTEL_RESOURCE_ATTRIBUTES_KEY);\n        if (StringUtils.isNotEmpty(configEnv)) {\n            Map<String, String> map = OTEL_RESOURCE_ATTRIBUTES_SPLITTER.split(configEnv);\n            if (!map.isEmpty()) {\n                for (Map.Entry<String, String> entry : SDK_ATTRIBUTES_TO_EASE_AGENT_PROPS.entrySet()) {\n                    String value = map.get(entry.getKey());\n                    if (!StringUtils.isEmpty(value)) {\n                        envCfg.put(entry.getValue(), value);\n                    }\n                }\n            }\n        }\n\n        // override by environment variables, eg: export OTEL_SERVICE_NAME=xxx\n        for (Map.Entry<String, String> entry : OTEL_SDK_ENV_VAR_TO_EASE_AGENT_PROPS.entrySet()) {\n            String value = SystemEnv.get(entry.getKey());\n            if (!StringUtils.isEmpty(value)) {\n                envCfg.put(entry.getValue(), value);\n            }\n        }\n\n        // override by java properties; eg: java -Dotel.service.name=xxx\n        for (Map.Entry<String, String> entry : OTEL_SDK_PROPS_TO_EASE_AGENT_PROPS.entrySet()) {\n            String value = System.getProperty(entry.getKey());\n            if (!StringUtils.isEmpty(value)) {\n                envCfg.put(entry.getValue(), value);\n            }\n        }\n\n        return envCfg;\n    }\n}\n"
  },
  {
    "path": "config/src/main/java/com/megaease/easeagent/config/PluginConfig.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.config;\n\nimport com.megaease.easeagent.log4j2.Logger;\nimport com.megaease.easeagent.log4j2.LoggerFactory;\nimport com.megaease.easeagent.plugin.api.config.Const;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.config.PluginConfigChangeListener;\nimport com.megaease.easeagent.plugin.utils.common.StringUtils;\n\nimport javax.annotation.Nonnull;\nimport java.util.*;\nimport java.util.function.Consumer;\nimport java.util.stream.Collectors;\n\npublic class PluginConfig implements IPluginConfig {\n    private static final Logger LOGGER = LoggerFactory.getLogger(PluginConfig.class);\n    private final Set<PluginConfigChangeListener> listeners;\n    private final String domain;\n    private final String namespace;\n    private final String id;\n    private final Map<String, String> global;\n    private final Map<String, String> cover;\n    private final boolean enabled;\n\n    protected PluginConfig(@Nonnull String domain, @Nonnull String id, @Nonnull Map<String, String> global, @Nonnull String namespace, @Nonnull Map<String, String> cover, @Nonnull Set<PluginConfigChangeListener> listeners) {\n        this.domain = domain;\n        this.namespace = namespace;\n        this.id = id;\n        this.global = global;\n        this.cover = cover;\n        this.listeners = listeners;\n        Boolean b = getBoolean(Const.ENABLED_CONFIG);\n        if (b == null) {\n            enabled = false;\n        } else {\n            enabled = b;\n        }\n    }\n\n    public static PluginConfig build(@Nonnull String domain, @Nonnull String id,\n                                     @Nonnull Map<String, String> global, @Nonnull String namespace,\n                                     @Nonnull Map<String, String> cover, PluginConfig oldConfig) {\n        Set<PluginConfigChangeListener> listeners;\n        if (oldConfig == null) {\n            listeners = new HashSet<>();\n        } else {\n            listeners = oldConfig.listeners;\n        }\n        return new PluginConfig(domain, id, global, namespace, cover, listeners);\n    }\n\n    @Override\n    public String domain() {\n        return domain;\n    }\n\n    @Override\n    public String namespace() {\n        return namespace;\n    }\n\n    @Override\n    public String id() {\n        return id;\n    }\n\n\n    @Override\n    public boolean hasProperty(String property) {\n        return global.containsKey(property) || cover.containsKey(property);\n    }\n\n    @Override\n    public String getString(String property) {\n        String value = cover.get(property);\n        if (value != null) {\n            return value;\n        }\n        return global.get(property);\n    }\n\n\n    @Override\n    public Integer getInt(String property) {\n        String value = this.getString(property);\n        if (value == null) {\n            return null;\n        }\n        try {\n            return Integer.parseInt(value);\n        } catch (Exception e) {\n            return null;\n        }\n    }\n\n    private boolean isTrue(String value) {\n        return value.equalsIgnoreCase(\"yes\") || value.equalsIgnoreCase(\"true\");\n    }\n\n    @Override\n    public Boolean getBoolean(String property) {\n        String value = cover.get(property);\n        boolean implB = true;\n        if (value != null) {\n            implB = isTrue(value);\n        }\n        value = global.get(property);\n        boolean globalB = false;\n        if (value != null) {\n            globalB = isTrue(value);\n        }\n        return implB && globalB;\n    }\n\n    @Override\n    public boolean enabled() {\n        return enabled;\n    }\n\n    @Override\n    public Double getDouble(String property) {\n        String value = this.getString(property);\n        if (value == null) {\n            return null;\n        }\n        try {\n            return Double.parseDouble(value);\n        } catch (Exception e) {\n            return null;\n        }\n    }\n\n    @Override\n    public Long getLong(String property) {\n        String value = this.getString(property);\n        if (value == null) {\n            return null;\n        }\n        try {\n            return Long.parseLong(value);\n        } catch (Exception e) {\n            return null;\n        }\n    }\n\n    @Override\n    public List<String> getStringList(String property) {\n        String value = this.getString(property);\n        if (StringUtils.isEmpty(value)) {\n            return Collections.emptyList();\n        }\n        return Arrays.stream(value.split(\",\"))\n            .map(String::trim)\n            .collect(Collectors.toList());\n    }\n\n    @Override\n    public IPluginConfig getGlobal() {\n        return new Global(domain, id, global, namespace);\n    }\n\n    @Override\n    public Set<String> keySet() {\n        Set<String> keys = new HashSet<>(global.keySet());\n        keys.addAll(cover.keySet());\n        return keys;\n    }\n\n    @Override\n    public void addChangeListener(PluginConfigChangeListener listener) {\n        synchronized (listeners) {\n            listeners.add(listener);\n        }\n    }\n\n    public void foreachConfigChangeListener(Consumer<PluginConfigChangeListener> action) {\n        Set<PluginConfigChangeListener> oldListeners;\n        synchronized (listeners) {\n            oldListeners = new HashSet<>(listeners);\n        }\n        for (PluginConfigChangeListener oldListener : oldListeners) {\n            try {\n                action.accept(oldListener);\n            } catch (Exception e) {\n                LOGGER.error(\"PluginConfigChangeListener<{}> change plugin config fail : {}\", oldListener.getClass(), e.getMessage());\n            }\n        }\n    }\n\n    public class Global extends PluginConfig implements IPluginConfig {\n\n        public Global(String domain, String id, Map<String, String> global, String namespace) {\n            super(domain, id, global, namespace, Collections.emptyMap(), Collections.emptySet());\n        }\n\n        @Override\n        public void addChangeListener(PluginConfigChangeListener listener) {\n            PluginConfig.this.addChangeListener(listener);\n        }\n\n        @Override\n        public void foreachConfigChangeListener(Consumer<PluginConfigChangeListener> action) {\n            PluginConfig.this.foreachConfigChangeListener(action);\n        }\n    }\n}\n"
  },
  {
    "path": "config/src/main/java/com/megaease/easeagent/config/PluginConfigManager.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.config;\n\nimport com.megaease.easeagent.log4j2.Logger;\nimport com.megaease.easeagent.log4j2.LoggerFactory;\nimport com.megaease.easeagent.plugin.api.config.ChangeItem;\nimport com.megaease.easeagent.plugin.api.config.Config;\nimport com.megaease.easeagent.plugin.api.config.ConfigChangeListener;\nimport com.megaease.easeagent.plugin.api.config.IConfigFactory;\n\nimport java.util.*;\n\nimport static com.megaease.easeagent.plugin.api.config.ConfigConst.PLUGIN_GLOBAL;\n\npublic class PluginConfigManager implements IConfigFactory {\n    private static final Logger LOGGER = LoggerFactory.getLogger(PluginConfigManager.class);\n    private Runnable shutdownRunnable;\n    private final Configs configs;\n    private final Map<Key, PluginSourceConfig> pluginSourceConfigs;\n    private final Map<Key, PluginConfig> pluginConfigs;\n\n    private PluginConfigManager(Configs configs, Map<Key, PluginSourceConfig> pluginSourceConfigs, Map<Key, PluginConfig> pluginConfigs) {\n        this.configs = Objects.requireNonNull(configs, \"configs must not be null.\");\n        this.pluginSourceConfigs = Objects.requireNonNull(pluginSourceConfigs, \"pluginSourceConfigs must not be null.\");\n        this.pluginConfigs = Objects.requireNonNull(pluginConfigs, \"pluginConfigs must not be null.\");\n    }\n\n    public static PluginConfigManager.Builder builder(Configs configs) {\n        PluginConfigManager pluginConfigManager = new PluginConfigManager(configs, new HashMap<>(), new HashMap<>());\n        return pluginConfigManager.new Builder();\n    }\n\n    @Override\n    public Config getConfig() {\n        return this.configs;\n    }\n\n    @Override\n    public String getConfig(String property) {\n        return configs.getString(property);\n    }\n\n    @Override\n    public String getConfig(String property, String defaultValue) {\n        return configs.getString(property, defaultValue);\n    }\n\n    public PluginConfig getConfig(String domain, String namespace, String id) {\n        return getConfig(domain, namespace, id, null);\n    }\n\n    public synchronized PluginConfig getConfig(String domain, String namespace, String id, PluginConfig oldConfig) {\n        Key key = new Key(domain, namespace, id);\n        PluginConfig pluginConfig = pluginConfigs.get(key);\n        if (pluginConfig != null) {\n            return pluginConfig;\n        }\n        Map<String, String> globalConfig = getGlobalConfig(domain, id);\n        Map<String, String> coverConfig = getCoverConfig(domain, namespace, id);\n        PluginConfig newPluginConfig = PluginConfig.build(domain, id, globalConfig, namespace, coverConfig, oldConfig);\n        pluginConfigs.put(key, newPluginConfig);\n        return newPluginConfig;\n    }\n\n    private Map<String, String> getGlobalConfig(String domain, String id) {\n        return getConfigSource(domain, PLUGIN_GLOBAL, id);\n    }\n\n    private Map<String, String> getCoverConfig(String domain, String namespace, String id) {\n        return getConfigSource(domain, namespace, id);\n    }\n\n    private Map<String, String> getConfigSource(String domain, String namespace, String id) {\n        PluginSourceConfig sourceConfig = pluginSourceConfigs.get(new Key(domain, namespace, id));\n        if (sourceConfig == null) {\n            return Collections.emptyMap();\n        }\n        return sourceConfig.getProperties();\n    }\n\n\n    private Set<Key> keys(Set<String> keys) {\n        Set<Key> propertyKeys = new HashSet<>();\n        for (String k : keys) {\n            if (!ConfigUtils.isPluginConfig(k)) {\n                continue;\n            }\n            PluginProperty property = ConfigUtils.pluginProperty(k);\n            Key key = new Key(property.getDomain(), property.getNamespace(), property.getId());\n            propertyKeys.add(key);\n        }\n        return propertyKeys;\n    }\n\n\n    public void shutdown() {\n        shutdownRunnable.run();\n    }\n\n    protected synchronized void onChange(Map<String, String> sources) {\n        Set<Key> sourceKeys = keys(sources.keySet());\n        Map<String, String> newSources = buildNewSources(sourceKeys, sources);\n        for (Key sourceKey : sourceKeys) {\n            pluginSourceConfigs.put(sourceKey, PluginSourceConfig.build(sourceKey.getDomain(), sourceKey.getNamespace(), sourceKey.getId(), newSources));\n        }\n        Set<Key> changeKeys = buildChangeKeys(sourceKeys);\n\n        for (Key changeKey : changeKeys) {\n            final PluginConfig oldConfig = pluginConfigs.remove(changeKey);\n            final PluginConfig newConfig = getConfig(changeKey.getDomain(), changeKey.getNamespace(), changeKey.id, oldConfig);\n            if (oldConfig == null) {\n                continue;\n            }\n            try {\n                oldConfig.foreachConfigChangeListener(listener -> listener.onChange(oldConfig, newConfig));\n            } catch (Exception e) {\n                LOGGER.warn(\"change config<{}> fail: {}\", changeKey.toString(), e.getMessage());\n            }\n        }\n    }\n\n    private Map<String, String> buildNewSources(Set<Key> sourceKeys, Map<String, String> sources) {\n        Map<String, String> newSources = new HashMap<>();\n        for (Key sourceKey : sourceKeys) {\n            PluginSourceConfig pluginSourceConfig = pluginSourceConfigs.get(sourceKey);\n            if (pluginSourceConfig == null) {\n                continue;\n            }\n            newSources.putAll(pluginSourceConfig.getSource());\n        }\n        newSources.putAll(sources);\n        return newSources;\n    }\n\n    private Set<Key> buildChangeKeys(Set<Key> sourceKeys) {\n        Set<Key> changeKeys = new HashSet<>(sourceKeys);\n        for (Key key : sourceKeys) {\n            if (!ConfigUtils.isGlobal(key.getNamespace())) {\n                continue;\n            }\n            for (Key oldKey : pluginConfigs.keySet()) {\n                if (!key.id.equals(oldKey.id)) {\n                    continue;\n                }\n                changeKeys.add(oldKey);\n            }\n        }\n        return changeKeys;\n    }\n\n    class Key {\n        private final String domain;\n        private final String namespace;\n        private final String id;\n\n        public Key(String domain, String namespace, String id) {\n            this.domain = domain;\n            this.namespace = namespace;\n            this.id = id;\n        }\n\n        public String getDomain() {\n            return domain;\n        }\n\n        public String getNamespace() {\n            return namespace;\n        }\n\n        public String getId() {\n            return id;\n        }\n\n        @Override\n        public boolean equals(Object o) {\n            if (this == o) return true;\n            if (o == null || getClass() != o.getClass()) return false;\n            Key key = (Key) o;\n            return Objects.equals(domain, key.domain) &&\n                Objects.equals(namespace, key.namespace) &&\n                Objects.equals(id, key.id);\n        }\n\n        @Override\n        public int hashCode() {\n\n            return Objects.hash(domain, namespace, id);\n        }\n\n        @Override\n        public String toString() {\n            return \"Key{\" +\n                \"domain='\" + domain + '\\'' +\n                \", namespace='\" + namespace + '\\'' +\n                \", id='\" + id + '\\'' +\n                '}';\n        }\n    }\n\n    public class Builder {\n        public PluginConfigManager build() {\n            synchronized (PluginConfigManager.this) {\n                Map<String, String> sources = configs.getConfigs();\n                Set<Key> sourceKeys = keys(sources.keySet());\n                for (Key sourceKey : sourceKeys) {\n                    pluginSourceConfigs.put(sourceKey, PluginSourceConfig.build(sourceKey.getDomain(), sourceKey.getNamespace(), sourceKey.getId(), sources));\n                }\n                for (Key key : pluginSourceConfigs.keySet()) {\n                    getConfig(key.getDomain(), key.getNamespace(), key.getId());\n                }\n                shutdownRunnable = configs.addChangeListener(new ChangeListener());\n            }\n            return PluginConfigManager.this;\n        }\n    }\n\n    class ChangeListener implements ConfigChangeListener {\n\n        @Override\n        public void onChange(List<ChangeItem> list) {\n            Map<String, String> sources = new HashMap<>();\n            for (ChangeItem changeItem : list) {\n                sources.put(changeItem.getFullName(), changeItem.getNewValue());\n            }\n            PluginConfigManager.this.onChange(sources);\n        }\n    }\n}\n"
  },
  {
    "path": "config/src/main/java/com/megaease/easeagent/config/PluginProperty.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.config;\n\nimport java.util.Objects;\n\npublic class PluginProperty {\n    private final String domain;\n    private final String namespace;\n    private final String id;\n    private final String property;\n\n    public PluginProperty(String domain, String namespace, String id, String property) {\n        this.domain = domain;\n        this.namespace = namespace;\n        this.id = id;\n        this.property = property;\n    }\n\n    public String getDomain() {\n        return domain;\n    }\n\n    public String getNamespace() {\n        return namespace;\n    }\n\n    public String getId() {\n        return id;\n    }\n\n    public String getProperty() {\n        return property;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) return true;\n        if (o == null || getClass() != o.getClass()) return false;\n        PluginProperty that = (PluginProperty) o;\n        return Objects.equals(domain, that.domain) &&\n            Objects.equals(namespace, that.namespace) &&\n            Objects.equals(id, that.id) &&\n            Objects.equals(property, that.property);\n    }\n\n    @Override\n    public int hashCode() {\n\n        return Objects.hash(domain, namespace, id, property);\n    }\n}\n"
  },
  {
    "path": "config/src/main/java/com/megaease/easeagent/config/PluginSourceConfig.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.config;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Objects;\n\npublic class PluginSourceConfig {\n    private final String domain;\n    private final String namespace;\n    private final String id;\n    private final Map<String, String> source;\n    private final Map<PluginProperty, String> properties;\n\n    public PluginSourceConfig(String domain, String namespace, String id, Map<String, String> source, Map<PluginProperty, String> properties) {\n        this.domain = Objects.requireNonNull(domain, \"domain must not be null.\");\n        this.namespace = Objects.requireNonNull(namespace, \"namespace must not be null.\");\n        this.id = Objects.requireNonNull(id, \"id must not be null.\");\n        this.source = Objects.requireNonNull(source, \"source must not be null.\");\n        this.properties = Objects.requireNonNull(properties, \"properties must not be null.\");\n    }\n\n    public static PluginSourceConfig build(String domain, String namespace, String id, Map<String, String> source) {\n        Map<String, String> pluginSource = new HashMap<>();\n        Map<PluginProperty, String> properties = new HashMap<>();\n        for (Map.Entry<String, String> sourceEntry : source.entrySet()) {\n            String key = sourceEntry.getKey();\n            if (!ConfigUtils.isPluginConfig(key, domain, namespace, id)) {\n                continue;\n            }\n            pluginSource.put(key, sourceEntry.getValue());\n            PluginProperty property = ConfigUtils.pluginProperty(key);\n            properties.put(property, sourceEntry.getValue());\n        }\n        return new PluginSourceConfig(domain, namespace, id, pluginSource, properties);\n    }\n\n    public Map<String, String> getSource() {\n        return source;\n    }\n\n    public String getDomain() {\n        return domain;\n    }\n\n    public String getNamespace() {\n        return namespace;\n    }\n\n    public String getId() {\n        return id;\n    }\n\n    public Map<String, String> getProperties() {\n        Map<String, String> result = new HashMap<>();\n        for (Map.Entry<PluginProperty, String> propertyEntry : properties.entrySet()) {\n            result.put(propertyEntry.getKey().getProperty(), propertyEntry.getValue());\n        }\n        return result;\n    }\n}\n"
  },
  {
    "path": "config/src/main/java/com/megaease/easeagent/config/ValidateUtils.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.config;\n\npublic class ValidateUtils {\n    public static class ValidException extends RuntimeException {\n        public ValidException(String message) {\n            super(message);\n        }\n    }\n\n    public interface Validator {\n        void validate(String name, String value);\n    }\n\n    public static void validate(Configs configs, String name, Validator... vs){\n        String value = configs.getString(name);\n        for (Validator one : vs) {\n            one.validate(name, value);\n        }\n    }\n\n    public static final Validator HasText = (name, value) -> {\n        if (value == null || value.trim().length() == 0) {\n            throw new ValidException(String.format(\"Property[%s] has no non-empty value\", name));\n        }\n    };\n\n    public static final Validator Bool = (name, value) -> {\n        String upper = value.toUpperCase();\n        if (upper.equals(\"TRUE\") || upper.equals(\"FALSE\")) {\n            return;\n        }\n        throw new ValidException(String.format(\"Property[%s] has no boolean value\", name));\n    };\n\n    public static final Validator NumberInt = (name, value) -> {\n        try {\n            Integer.parseInt(value.trim());\n        } catch (Exception e) {\n            throw new ValidException(String.format(\"Property[%s] has no integer value\", name));\n        }\n    };\n}\n"
  },
  {
    "path": "config/src/main/java/com/megaease/easeagent/config/WrappedConfigManager.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.config;\n\nimport com.megaease.easeagent.plugin.async.ThreadUtils;\n\nimport java.io.IOException;\nimport java.util.List;\nimport java.util.Map;\n\npublic class WrappedConfigManager implements ConfigManagerMXBean {\n    private final ClassLoader customClassLoader;\n    private final ConfigManagerMXBean conf;\n\n    public WrappedConfigManager(ClassLoader customClassLoader, ConfigManagerMXBean config) {\n        this.customClassLoader = customClassLoader;\n        this.conf = config;\n    }\n\n    @Override\n    public void updateConfigs(Map<String, String> configs) {\n        ThreadUtils.callWithClassLoader(customClassLoader, () -> {\n            conf.updateConfigs(configs);\n            return null;\n        });\n    }\n\n    @Override\n    public void updateService(String json, String version) throws IOException {\n        try {\n            ThreadUtils.callWithClassLoader(customClassLoader, () -> {\n                try {\n                    conf.updateService(json, version);\n                } catch (IOException e) {\n                    throw new RuntimeException(e);\n                }\n                return null;\n            });\n        } catch (RuntimeException e) {\n            if (e.getCause() instanceof IOException) {\n                throw (IOException) e.getCause();\n            }\n            throw e;\n        }\n    }\n\n    @Override\n    public void updateCanary(String json, String version) throws IOException {\n        try {\n            ThreadUtils.callWithClassLoader(customClassLoader, () -> {\n                try {\n                    conf.updateCanary(json, version);\n                } catch (IOException e) {\n                    throw new RuntimeException(e);\n                }\n                return null;\n            });\n        } catch (RuntimeException e) {\n            if (e.getCause() instanceof IOException) {\n                throw (IOException) e.getCause();\n            }\n            throw e;\n        }\n    }\n\n    @Override\n    public void updateService2(Map<String, String> configs, String version) {\n        ThreadUtils.callWithClassLoader(customClassLoader, () -> {\n            conf.updateService2(configs, version);\n            return null;\n        });\n    }\n\n    @Override\n    public void updateCanary2(Map<String, String> configs, String version) {\n        ThreadUtils.callWithClassLoader(customClassLoader, () -> {\n            conf.updateCanary2(configs, version);\n            return null;\n        });\n    }\n\n    @Override\n    public Map<String, String> getConfigs() {\n        return ThreadUtils.callWithClassLoader(customClassLoader, conf::getConfigs);\n    }\n\n    @Override\n    public List<String> availableConfigNames() {\n        return ThreadUtils.callWithClassLoader(customClassLoader, conf::availableConfigNames);\n    }\n}\n"
  },
  {
    "path": "config/src/main/java/com/megaease/easeagent/config/report/ReportConfigAdapter.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.config.report;\n\nimport com.megaease.easeagent.plugin.api.config.Config;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.api.config.Const;\nimport com.megaease.easeagent.plugin.utils.NoNull;\nimport com.megaease.easeagent.plugin.utils.common.StringUtils;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.Map;\nimport java.util.TreeMap;\n\nimport static com.megaease.easeagent.config.ConfigUtils.extractAndConvertPrefix;\nimport static com.megaease.easeagent.config.ConfigUtils.extractByPrefix;\nimport static com.megaease.easeagent.config.report.ReportConfigConst.*;\n\n@Slf4j\npublic class ReportConfigAdapter {\n    private ReportConfigAdapter() {}\n\n    public static void convertConfig(Map<String, String> config) {\n        Map<String, String> cfg = extractAndConvertReporterConfig(config);\n        config.putAll(cfg);\n    }\n\n    public static Map<String, String> extractReporterConfig(Config configs) {\n        Map<String, String> cfg = extractByPrefix(configs.getConfigs(), REPORT);\n\n        // default config\n        cfg.put(TRACE_ENCODER, NoNull.of(cfg.get(TRACE_ENCODER), SPAN_JSON_ENCODER_NAME));\n        cfg.put(METRIC_ENCODER, NoNull.of(cfg.get(METRIC_ENCODER), METRIC_JSON_ENCODER_NAME));\n        cfg.put(LOG_ENCODER, NoNull.of(cfg.get(LOG_ENCODER), LOG_DATA_JSON_ENCODER_NAME));\n        cfg.put(LOG_ACCESS_ENCODER, NoNull.of(cfg.get(LOG_ACCESS_ENCODER), ACCESS_LOG_JSON_ENCODER_NAME));\n\n        cfg.put(TRACE_SENDER_NAME, NoNull.of(cfg.get(TRACE_SENDER_NAME), getDefaultAppender(cfg)));\n        cfg.put(METRIC_SENDER_NAME, NoNull.of(cfg.get(METRIC_SENDER_NAME), getDefaultAppender(cfg)));\n        cfg.put(LOG_ACCESS_SENDER_NAME, NoNull.of(cfg.get(LOG_ACCESS_SENDER_NAME), getDefaultAppender(cfg)));\n        cfg.put(LOG_SENDER_NAME, NoNull.of(cfg.get(LOG_SENDER_NAME), getDefaultAppender(cfg)));\n\n        return cfg;\n    }\n\n    public static String getDefaultAppender(Map<String, String> cfg) {\n        String outputAppender = cfg.get(join(OUTPUT_SERVER_V2, APPEND_TYPE_KEY));\n\n        if (StringUtils.isEmpty(outputAppender)) {\n            return Const.DEFAULT_APPEND_TYPE;\n        }\n\n        return outputAppender;\n    }\n\n    private static Map<String, String> extractAndConvertReporterConfig(Map<String, String> srcConfig) {\n        Map<String, String> extract = extractTracingConfig(srcConfig);\n        Map<String, String> outputCfg = new TreeMap<>(extract);\n\n        // metric config\n        extract = extractMetricPluginConfig(srcConfig);\n        outputCfg.putAll(extract);\n\n        // log config\n        extract = extractLogPluginConfig(srcConfig);\n        outputCfg.putAll(extract);\n\n        // if there are access log in metric\n        updateAccessLogCfg(outputCfg);\n\n        // all extract configuration will be overridden by config items start with \"report\" in srcConfig\n        extract = extractByPrefix(srcConfig, REPORT);\n        outputCfg.putAll(extract);\n\n        return outputCfg;\n    }\n\n\n    /**\n     * this can be deleted if there is not any v1 configuration needed to compatible with\n     * convert v1 tracing config to v2\n     */\n    private static Map<String, String> extractTracingConfig(Map<String, String> srcCfg) {\n        // outputServer config\n        Map<String, String> extract = extractAndConvertPrefix(srcCfg, OUTPUT_SERVER_V1, OUTPUT_SERVER_V2);\n        Map<String, String> outputCfg = new TreeMap<>(extract);\n\n        // async output config\n        extract = extractAndConvertPrefix(srcCfg, TRACE_OUTPUT_V1, TRACE_ASYNC);\n\n        String target = srcCfg.get(join(TRACE_OUTPUT_V1, \"target\"));\n        extract.remove(join(TRACE_ASYNC, \"target\"));\n\n        if (!StringUtils.isEmpty(outputCfg.get(TRACE_SENDER_NAME))) {\n            log.info(\"Reporter V2 config trace sender as: {}\", outputCfg.get(TRACE_SENDER_NAME));\n        } else if (\"system\".equals(target)) {\n            // check output servers\n            if (StringUtils.hasText(outputCfg.get(BOOTSTRAP_SERVERS))) {\n                outputCfg.put(TRACE_SENDER_NAME, KAFKA_SENDER_NAME);\n                outputCfg.put(TRACE_SENDER_TOPIC_V2, extract.remove(join(TRACE_ASYNC, TOPIC_KEY)));\n            } else {\n                outputCfg.put(TRACE_SENDER_NAME, CONSOLE_SENDER_NAME);\n            }\n        } else if (\"zipkin\".equals(target)) {\n            outputCfg.put(TRACE_SENDER_NAME, ZIPKIN_SENDER_NAME);\n            String url = extract.remove(join(TRACE_ASYNC, \"target.zipkinUrl\"));\n            if (StringUtils.isEmpty(url)) {\n                outputCfg.put(TRACE_SENDER_NAME, CONSOLE_SENDER_NAME);\n            } else {\n                outputCfg.put(join(TRACE_SENDER, \"url\"), url);\n            }\n        } else if (!StringUtils.isEmpty(target)) {\n            outputCfg.put(TRACE_SENDER_NAME, CONSOLE_SENDER_NAME);\n            log.info(\"Unsupported output configuration item:{}={}\", TRACE_OUTPUT_TARGET_V1, target);\n        }\n        outputCfg.putAll(extract);\n\n        return outputCfg;\n    }\n\n    /**\n     * For Compatibility, call after metric config adapter\n     *\n     * extract 'reporter.metric.access.*' to 'reporter.log.access.*'\n     */\n    private static void updateAccessLogCfg(Map<String, String> outputCfg) {\n        // reporter.metric.access.*\n        String prefix = join(METRIC_V2, ConfigConst.Namespace.ACCESS);\n        Map<String, String> metricAccess = extractByPrefix(outputCfg, prefix);\n        Map<String, String> accessLog = extractAndConvertPrefix(metricAccess, prefix, LOG_ACCESS);\n\n        // access log use `kafka` sender\n        if (METRIC_KAFKA_SENDER_NAME.equals(accessLog.get(LOG_ACCESS_SENDER_NAME))) {\n            accessLog.put(LOG_ACCESS_SENDER_NAME, KAFKA_SENDER_NAME);\n        }\n\n        outputCfg.putAll(accessLog);\n    }\n\n    /**\n     * metric report configuration\n     *\n     * extract `plugin.observability.global.metric.*` config items to reporter.metric.sender.*`\n     *\n     * extract `plugin.observability.[namespace].metric.*` config items\n     * to reporter.metric.[namespace].sender.*`\n     *\n     * @param srcCfg source configuration map\n     * @return metric reporter config start with 'reporter.metric.[namespace].sender'\n     */\n    private static Map<String, String> extractMetricPluginConfig(Map<String, String> srcCfg) {\n        final String globalKey = \".\" + ConfigConst.PLUGIN_GLOBAL + \".\";\n        final String prefix = join(ConfigConst.PLUGIN, ConfigConst.OBSERVABILITY);\n        int metricKeyLength = ConfigConst.METRIC_SERVICE_ID.length();\n\n        Map<String, String> global = extractGlobalMetricConfig(srcCfg);\n        HashSet<String> namespaces = new HashSet<>();\n        Map<String, String> metricConfigs = new HashMap<>(global);\n\n        for (Map.Entry<String, String> e : srcCfg.entrySet()) {\n            String key = e.getKey();\n            if (!key.startsWith(prefix)) {\n                continue;\n            }\n            int idx = key.indexOf(ConfigConst.METRIC_SERVICE_ID, prefix.length());\n            if (idx < 0) {\n                continue;\n            }\n            String namespaceWithSeparator = key.substring(prefix.length(), idx);\n            String suffix = key.substring(idx + metricKeyLength + 1);\n            String newKey;\n\n            if (namespaceWithSeparator.equals(globalKey)) {\n                continue;\n            } else {\n                if (!namespaces.contains(namespaceWithSeparator)) {\n                    namespaces.add(namespaceWithSeparator);\n                    Map<String, String> d = extractAndConvertPrefix(global,\n                        METRIC_V2 + \".\", METRIC_V2 + namespaceWithSeparator);\n                    metricConfigs.putAll(d);\n                }\n            }\n\n            if (suffix.startsWith(ENCODER_KEY) || suffix.startsWith(ASYNC_KEY)) {\n                newKey = METRIC_V2 + namespaceWithSeparator + suffix;\n            } else if (suffix.equals(INTERVAL_KEY)) {\n                newKey = METRIC_V2 + namespaceWithSeparator + join(ASYNC_KEY, suffix);\n            } else {\n                newKey = METRIC_V2 + namespaceWithSeparator + join(SENDER_KEY, suffix);\n            }\n\n            if (newKey.endsWith(APPEND_TYPE_KEY) && e.getValue().equals(\"kafka\")) {\n                metricConfigs.put(newKey, METRIC_KAFKA_SENDER_NAME);\n            } else {\n                metricConfigs.put(newKey, e.getValue());\n            }\n        }\n\n        return metricConfigs;\n    }\n\n    private static Map<String, String> extractGlobalMetricConfig(Map<String, String> srcCfg) {\n        final String prefix = join(ConfigConst.PLUGIN, ConfigConst.OBSERVABILITY,\n            ConfigConst.PLUGIN_GLOBAL,\n            ConfigConst.PluginID.METRIC);\n        Map<String, String> global = new TreeMap<>();\n        Map<String, String> extract = extractAndConvertPrefix(srcCfg, prefix, METRIC_SENDER);\n\n        for (Map.Entry<String, String> e : extract.entrySet()) {\n            if (e.getKey().startsWith(ENCODER_KEY, METRIC_SENDER.length() + 1)) {\n                global.put(join(METRIC_V2, e.getKey().substring(METRIC_SENDER.length() + 1)), e.getValue());\n            } else if (e.getKey().endsWith(INTERVAL_KEY)) {\n                global.put(join(METRIC_ASYNC, INTERVAL_KEY), e.getValue());\n            } else if (e.getKey().endsWith(APPEND_TYPE_KEY) && e.getValue().equals(\"kafka\")) {\n                global.put(e.getKey(), METRIC_KAFKA_SENDER_NAME);\n            } else {\n                global.put(e.getKey(), e.getValue());\n            }\n        }\n\n\n        // global log level (async)\n        global.putAll(extractByPrefix(srcCfg, METRIC_SENDER));\n        global.putAll(extractByPrefix(srcCfg, METRIC_ASYNC));\n        global.putAll(extractByPrefix(srcCfg, METRIC_ENCODER));\n\n        return global;\n    }\n\n    /**\n     * metric report configuration\n     *\n     * extract `plugin.observability.global.metric.*` config items to reporter.metric.sender.*`\n     *\n     * extract `plugin.observability.[namespace].metric.*` config items\n     * to reporter.metric.[namespace].sender.*`\n     *\n     * @param srcCfg source configuration map\n     * @return metric reporter config start with 'reporter.metric.[namespace].sender'\n     */\n    private static Map<String, String> extractLogPluginConfig(Map<String, String> srcCfg) {\n        final String globalKey = \".\" + ConfigConst.PLUGIN_GLOBAL + \".\";\n        final String prefix = join(ConfigConst.PLUGIN, ConfigConst.OBSERVABILITY);\n\n        String typeKey = join(\"\", ConfigConst.PluginID.LOG, \"\");\n        int typeKeyLength = ConfigConst.PluginID.LOG.length();\n\n        final String reporterPrefix = LOGS;\n\n        Map<String, String> global = extractGlobalLogConfig(srcCfg);\n        HashSet<String> namespaces = new HashSet<>();\n        Map<String, String> outputConfigs = new TreeMap<>(global);\n\n        for (Map.Entry<String, String> e : srcCfg.entrySet()) {\n            String key = e.getKey();\n            if (!key.startsWith(prefix)) {\n                continue;\n            }\n            int idx = key.indexOf(typeKey, prefix.length());\n            if (idx < 0) {\n                continue;\n            } else {\n                idx += 1;\n            }\n            String namespaceWithSeparator = key.substring(prefix.length(), idx);\n            String suffix = key.substring(idx + typeKeyLength + 1);\n            String newKey;\n\n            if (namespaceWithSeparator.equals(globalKey)) {\n                continue;\n            } else {\n                if (!namespaces.contains(namespaceWithSeparator)) {\n                    namespaces.add(namespaceWithSeparator);\n                    Map<String, String> d = extractAndConvertPrefix(global,\n                        reporterPrefix + \".\", reporterPrefix + namespaceWithSeparator);\n                    outputConfigs.putAll(d);\n                }\n            }\n\n            if (suffix.startsWith(ENCODER_KEY) || suffix.startsWith(ASYNC_KEY)) {\n                newKey = reporterPrefix + namespaceWithSeparator + suffix;\n            } else {\n                newKey = reporterPrefix + namespaceWithSeparator + join(SENDER_KEY, suffix);\n            }\n\n            outputConfigs.put(newKey, e.getValue());\n        }\n\n        return outputConfigs;\n    }\n\n    /**\n     * extract `plugin.observability.global.log.*` config items to `reporter.log.sender.*`\n     * extract `plugin.observability.global.log.output.*` config items to `reporter.log.output.*`\n     *\n     * @param srcCfg source config map\n     * @return reporter log config\n     */\n    private static Map<String, String> extractGlobalLogConfig(Map<String, String> srcCfg) {\n        final String prefix = join(ConfigConst.PLUGIN, ConfigConst.OBSERVABILITY,\n            ConfigConst.PLUGIN_GLOBAL,\n            ConfigConst.PluginID.LOG);\n        Map<String, String> global = new TreeMap<>();\n        Map<String, String> extract = extractAndConvertPrefix(srcCfg, prefix, LOG_SENDER);\n\n        for (Map.Entry<String, String> e : extract.entrySet()) {\n            String key = e.getKey();\n            if (key.startsWith(ENCODER_KEY, LOG_SENDER.length() + 1)) {\n                global.put(join(LOGS, key.substring(LOG_SENDER.length() + 1)), e.getValue());\n            } else if (key.startsWith(ASYNC_KEY, LOG_SENDER.length() + 1)) {\n                global.put(join(LOGS, key.substring(LOG_SENDER.length() + 1)), e.getValue());\n            } else {\n                global.put(e.getKey(), e.getValue());\n            }\n        }\n\n        // global log level (async)\n        global.putAll(extractByPrefix(srcCfg, LOG_SENDER));\n        global.putAll(extractByPrefix(srcCfg, LOG_ASYNC));\n        global.putAll(extractByPrefix(srcCfg, LOG_ENCODER));\n\n        return global;\n    }\n}\n"
  },
  {
    "path": "config/src/main/java/com/megaease/easeagent/config/report/ReportConfigConst.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.config.report;\n\n@SuppressWarnings(\"unused\")\npublic class ReportConfigConst {\n    private ReportConfigConst() {}\n\n    public static final String KAFKA_SENDER_NAME = \"kafka\";\n    public static final String METRIC_KAFKA_SENDER_NAME = \"metricKafka\";\n    public static final String CONSOLE_SENDER_NAME = \"console\";\n    public static final String ZIPKIN_SENDER_NAME = \"http\";\n\n    public static final String NOOP_SENDER_NAME = \"noop\";\n\n    public static final String SPAN_JSON_ENCODER_NAME = \"SpanJsonEncoder\";\n    public static final String METRIC_JSON_ENCODER_NAME = \"MetricJsonEncoder\";\n    public static final String LOG_DATA_JSON_ENCODER_NAME = \"LogDataJsonEncoder\";\n    public static final String ACCESS_LOG_JSON_ENCODER_NAME = \"AccessLogJsonEncoder\";\n\n    public static final String HTTP_SPAN_JSON_ENCODER_NAME = \"HttpSpanJsonEncoder\";\n\n    public static final String LOG_ENCODER_NAME = \"StringEncoder\";\n\n    static final String DELIMITER = \".\";\n    public static final String TOPIC_KEY = \"topic\";\n    public static final String LOG_APPENDER_KEY = \"appenderName\";\n\n    public static final String ENABLED_KEY = \"enabled\";\n    public static final String SENDER_KEY = \"sender\";\n    public static final String ENCODER_KEY = \"encoder\";\n    public static final String ASYNC_KEY = \"output\";\n    public static final String APPEND_TYPE_KEY = \"appendType\";\n    public static final String INTERVAL_KEY = \"interval\";\n\n    public static final String ASYNC_THREAD_KEY = \"reportThread\";\n    public static final String ASYNC_MSG_MAX_BYTES_KEY = \"messageMaxBytes\";\n    public static final String ASYNC_MSG_TIMEOUT_KEY = \"messageTimeout\";\n    public static final String ASYNC_QUEUE_MAX_SIZE_KEY = \"queuedMaxSize\";\n    public static final String ASYNC_QUEUE_MAX_LOGS_KEY = \"queuedMaxLogs\";\n    public static final String ASYNC_QUEUE_MAX_ITEMS_KEY = \"queuedMaxItems\";\n\n    /**\n     * Reporter v2 configuration\n     */\n    // -- lv1 --\n    public static final String REPORT = \"reporter\";\n    // ---- lv2 ----\n    public static final String OUTPUT_SERVER_V2 = join(REPORT, \"outputServer\");\n    public static final String TRACE_V2 = join(REPORT, \"tracing\");\n    public static final String LOGS = join(REPORT, \"log\");\n    public static final String METRIC_V2 = join(REPORT, \"metric\");\n    public static final String GENERAL = join(REPORT, \"general\");\n    // ------ lv3 ------\n    public static final String BOOTSTRAP_SERVERS = join(OUTPUT_SERVER_V2, \"bootstrapServer\");\n    public static final String OUTPUT_SERVERS_ENABLE = join(OUTPUT_SERVER_V2, ENABLED_KEY);\n    public static final String OUTPUT_SERVERS_TIMEOUT = join(OUTPUT_SERVER_V2, \"timeout\");\n\n    public static final String OUTPUT_SECURITY_PROTOCOL_V2 = join(OUTPUT_SERVER_V2, \"security.protocol\");\n    public static final String OUTPUT_SERVERS_SSL = join(OUTPUT_SERVER_V2, \"ssl\");\n\n    public static final String LOG_ASYNC = join(LOGS, ASYNC_KEY);\n\n    public static final String LOG_SENDER = join(LOGS, SENDER_KEY);\n    public static final String LOG_ENCODER = join(LOGS, ENCODER_KEY);\n\n    public static final String LOG_ACCESS = join(LOGS, \"access\");\n    public static final String LOG_ACCESS_SENDER = join(LOG_ACCESS, SENDER_KEY);\n    public static final String LOG_ACCESS_ENCODER = join(LOG_ACCESS, ENCODER_KEY);\n\n    public static final String TRACE_SENDER = join(TRACE_V2, SENDER_KEY);\n    public static final String TRACE_ENCODER = join(TRACE_V2, ENCODER_KEY);\n    public static final String TRACE_ASYNC = join(TRACE_V2, ASYNC_KEY);\n\n    public static final String METRIC_SENDER = join(METRIC_V2, SENDER_KEY);\n    public static final String METRIC_ENCODER = join(METRIC_V2, ENCODER_KEY);\n    public static final String METRIC_ASYNC = join(METRIC_V2, ASYNC_KEY);\n\n    // -------- lv4  --------\n    public static final String LOG_SENDER_TOPIC = join(LOG_SENDER, TOPIC_KEY);\n    public static final String LOG_SENDER_NAME = join(LOG_SENDER, APPEND_TYPE_KEY);\n\n    public static final String LOG_ACCESS_SENDER_NAME = join(LOG_ACCESS_SENDER, APPEND_TYPE_KEY);\n    public static final String LOG_ACCESS_SENDER_ENABLED = join(LOG_ACCESS_SENDER, ENABLED_KEY);\n    public static final String LOG_ACCESS_SENDER_TOPIC = join(LOG_ACCESS_SENDER, TOPIC_KEY);\n\n    public static final String LOG_ASYNC_MESSAGE_MAX_BYTES = join(LOG_ASYNC, ASYNC_MSG_MAX_BYTES_KEY);\n    public static final String LOG_ASYNC_REPORT_THREAD = join(LOG_ASYNC, ASYNC_THREAD_KEY);\n    public static final String LOG_ASYNC_MESSAGE_TIMEOUT = join(LOG_ASYNC, ASYNC_MSG_TIMEOUT_KEY);\n    public static final String LOG_ASYNC_QUEUED_MAX_LOGS = join(LOG_ASYNC, ASYNC_QUEUE_MAX_LOGS_KEY);\n    public static final String LOG_ASYNC_QUEUED_MAX_SIZE = join(LOG_ASYNC, ASYNC_QUEUE_MAX_SIZE_KEY);\n\n    public static final String TRACE_SENDER_NAME = join(TRACE_SENDER, APPEND_TYPE_KEY);\n    public static final String TRACE_SENDER_ENABLED_V2 = join(TRACE_SENDER, ENABLED_KEY);\n    public static final String TRACE_SENDER_TOPIC_V2 = join(TRACE_SENDER, TOPIC_KEY);\n\n    public static final String TRACE_ASYNC_MESSAGE_MAX_BYTES_V2 = join(TRACE_ASYNC, ASYNC_MSG_MAX_BYTES_KEY);\n    public static final String TRACE_ASYNC_REPORT_THREAD_V2 = join(TRACE_ASYNC, ASYNC_THREAD_KEY);\n    public static final String TRACE_ASYNC_MESSAGE_TIMEOUT_V2 = join(TRACE_ASYNC, ASYNC_MSG_TIMEOUT_KEY);\n    public static final String TRACE_ASYNC_QUEUED_MAX_SPANS_V2 = join(TRACE_ASYNC, \"queuedMaxSpans\");\n    public static final String TRACE_ASYNC_QUEUED_MAX_SIZE_V2 = join(TRACE_ASYNC, ASYNC_QUEUE_MAX_SIZE_KEY);\n\n    public static final String METRIC_SENDER_NAME = join(METRIC_SENDER, APPEND_TYPE_KEY);\n    public static final String METRIC_SENDER_ENABLED = join(METRIC_SENDER, ENABLED_KEY);\n    public static final String METRIC_SENDER_TOPIC = join(METRIC_SENDER, TOPIC_KEY);\n    public static final String METRIC_SENDER_APPENDER = join(METRIC_SENDER, LOG_APPENDER_KEY);\n\n    public static final String METRIC_ASYNC_INTERVAL = join(METRIC_ASYNC, INTERVAL_KEY);\n    public static final String METRIC_ASYNC_QUEUED_MAX_ITEMS = join(METRIC_ASYNC, ASYNC_QUEUE_MAX_ITEMS_KEY);\n    public static final String METRIC_ASYNC_MESSAGE_MAX_BYTES = join(METRIC_ASYNC, ASYNC_MSG_MAX_BYTES_KEY);\n\n    public static final String OUTPUT_SSL_KEYSTORE_TYPE_V2 = join(OUTPUT_SERVERS_SSL, \"keystore.type\");\n    public static final String OUTPUT_KEY_V2 = join(OUTPUT_SERVERS_SSL, \"keystore.key\");\n    public static final String OUTPUT_CERT_V2 = join(OUTPUT_SERVERS_SSL, \"keystore.certificate.chain\");\n    public static final String OUTPUT_TRUST_CERT_V2 = join(OUTPUT_SERVERS_SSL, \"truststore.certificates\");\n    public static final String OUTPUT_TRUST_CERT_TYPE_V2 = join(OUTPUT_SERVERS_SSL, \"truststore.type\");\n    public static final String OUTPUT_ENDPOINT_IDENTIFICATION_ALGORITHM_V2 = join(OUTPUT_SERVERS_SSL, \"endpoint.identification.algorithm\");\n\n    /**\n     * Reporter v1 configuration\n     */\n    public static final String OBSERVABILITY = \"observability\";\n    // ---- lv2 ----\n    public static final String TRACING = join(OBSERVABILITY, \"tracings\");\n    public static final String OUTPUT_SERVER_V1 = join(OBSERVABILITY, \"outputServer\");\n    // ------ lv3 ------\n    public static final String TRACE_OUTPUT_V1 = join(TRACING, ASYNC_KEY);\n    public static final String BOOTSTRAP_SERVERS_V1 = join(OUTPUT_SERVER_V1, \"bootstrapServer\");\n\n    // --------- lv4 ---------\n    public static final String TRACE_OUTPUT_ENABLED_V1 = join(TRACE_OUTPUT_V1, ENABLED_KEY);\n    public static final String TRACE_OUTPUT_TOPIC_V1 = join(TRACE_OUTPUT_V1, TOPIC_KEY);\n    public static final String TRACE_OUTPUT_TARGET_V1 = join(TRACE_OUTPUT_V1, \"target\");\n    public static final String TRACE_OUTPUT_TARGET_ZIPKIN_URL = join(TRACE_OUTPUT_V1, \"target.zipkinUrl\");\n\n    public static final String TRACE_OUTPUT_REPORT_THREAD_V1 = join(TRACE_OUTPUT_V1, ASYNC_THREAD_KEY);\n    public static final String TRACE_OUTPUT_MESSAGE_TIMEOUT_V1 = join(TRACE_OUTPUT_V1, ASYNC_MSG_TIMEOUT_KEY);\n    public static final String TRACE_OUTPUT_QUEUED_MAX_SPANS_V1 = join(TRACE_OUTPUT_V1, \"queuedMaxSpans\");\n    public static final String TRACE_OUTPUT_QUEUED_MAX_SIZE_V1 = join(TRACE_OUTPUT_V1, ASYNC_QUEUE_MAX_SIZE_KEY);\n\n    public static final String GLOBAL_METRIC = \"plugin.observability.global.metric\";\n    public static final String GLOBAL_METRIC_ENABLED = join(GLOBAL_METRIC, ENABLED_KEY);\n    public static final String GLOBAL_METRIC_TOPIC = join(GLOBAL_METRIC, TOPIC_KEY);\n    public static final String GLOBAL_METRIC_APPENDER = join(GLOBAL_METRIC, APPEND_TYPE_KEY);\n\n    public static String join(String... texts) {\n        return String.join(DELIMITER, texts);\n    }\n}\n"
  },
  {
    "path": "config/src/main/java/com/megaease/easeagent/config/yaml/YamlReader.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.config.yaml;\n\nimport org.yaml.snakeyaml.DumperOptions;\nimport org.yaml.snakeyaml.Yaml;\n\nimport java.io.InputStream;\nimport java.util.Collections;\nimport java.util.Deque;\nimport java.util.HashMap;\nimport java.util.LinkedHashMap;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.stream.Collectors;\n\npublic class YamlReader {\n\n    private Map<String, Object> yaml;\n\n    private static final DumperOptions DUMPER_OPTIONS;\n\n    static {\n        DUMPER_OPTIONS = new DumperOptions();\n        DUMPER_OPTIONS.setLineBreak(DumperOptions.LineBreak.getPlatformLineBreak());\n    }\n\n    public YamlReader() {\n        // ignored\n    }\n\n    public YamlReader load(InputStream in) {\n        if (in != null) {\n            yaml = new Yaml(DUMPER_OPTIONS).load(in);\n        }\n        return this;\n    }\n\n    public Map<String, Object> getYaml() {\n        return yaml;\n    }\n\n    public static YamlReader merge(YamlReader target, YamlReader source) {\n        Map<String, Object> targetMap = target.yaml;\n        Map<String, Object> sourceMap = source.yaml;\n\n        merge(targetMap, sourceMap);\n\n        YamlReader result = new YamlReader();\n        result.yaml = new HashMap<>(targetMap);\n        return result;\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    private static void merge(Map<String, Object> target, Map<String, Object> source) {\n        source.forEach((key, value) -> {\n            Object existing = target.get(key);\n            if (value instanceof Map && existing instanceof Map) {\n                Map<String, Object> result = new LinkedHashMap<>((Map<String, Object>) existing);\n                merge(result, (Map<String, Object>) value);\n                target.put(key, result);\n            } else {\n                target.put(key, value);\n            }\n        });\n    }\n\n    public Map<String, String> compress() {\n        if (Objects.isNull(yaml) || yaml.size() == 0) {\n            return Collections.emptyMap();\n        }\n\n        final Deque<String> keyStack = new LinkedList<>();\n        final Map<String, String> resultMap = new HashMap<>();\n\n        compress(yaml, keyStack, resultMap);\n\n        return resultMap;\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    private void compress(Map<?, Object> result, Deque<String> keyStack, Map<String, String> resultMap) {\n        result.forEach((k, v) -> {\n            keyStack.addLast(String.valueOf(k));\n\n            if (v instanceof Map) {\n                compress((Map<?, Object>) v, keyStack, resultMap);\n                keyStack.removeLast();\n                return;\n            }\n\n            if (v instanceof List) {\n                String value = ((List<Object>) v).stream()\n                    .map(String::valueOf)\n                    .collect(Collectors.joining(\",\"));\n\n                resultMap.put(String.join(\".\", keyStack), value);\n                keyStack.removeLast();\n                return;\n            }\n\n            resultMap.put(String.join(\".\", keyStack), String.valueOf(v));\n            keyStack.removeLast();\n        });\n    }\n}\n"
  },
  {
    "path": "config/src/test/java/com/megaease/easeagent/config/CompatibilityConversionTest.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.megaease.easeagent.config;\n\nimport com.megaease.easeagent.plugin.api.ProgressFields;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport org.junit.Test;\n\nimport java.util.Collections;\nimport java.util.Map;\n\nimport static com.megaease.easeagent.config.CompatibilityConversion.REQUEST_NAMESPACE;\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertTrue;\n\npublic class CompatibilityConversionTest {\n\n    @Test\n    public void transform() {\n        Map<String, String> newMap = CompatibilityConversion.transform(Collections.singletonMap(ConfigConst.Observability.METRICS_ENABLED, \"false\"));\n        assertEquals(2, newMap.size());\n        assertTrue(newMap.containsKey(ConfigConst.Plugin.OBSERVABILITY_GLOBAL_METRIC_ENABLED));\n        assertTrue(newMap.containsKey(ConfigConst.Observability.METRICS_ENABLED));\n        newMap = CompatibilityConversion.transform(Collections.singletonMap(ConfigConst.Observability.TRACE_ENABLED, \"false\"));\n        assertEquals(1, newMap.size());\n        assertTrue(newMap.containsKey(ConfigConst.Plugin.OBSERVABILITY_GLOBAL_TRACING_ENABLED));\n//        newMap = CompatibilityConversion.transform(Collections.singletonMap(\"globalCanaryHeaders.serviceHeaders.mesh-app-backend.0\", \"X-canary\"));\n//        assertEquals(1, newMap.size());\n//        assertTrue(newMap.containsKey(ProgressFields.EASEAGENT_PROGRESS_FORWARDED_HEADERS_CONFIG + \".mesh-app-backend.0\"));\n\n        newMap = CompatibilityConversion.transform(Collections.singletonMap(\"observability.outputServer.bootstrapServer\", \"tstatssta\"));\n        assertEquals(1, newMap.size());\n        assertTrue(newMap.containsKey(\"observability.outputServer.bootstrapServer\"));\n\n\n        newMap = CompatibilityConversion.transform(Collections.singletonMap(\"observability.metrics.access.enabled\", \"true\"));\n        assertEquals(1, newMap.size());\n        assertEquals(\"true\", newMap.get(\"plugin.observability.access.metric.enabled\"));\n\n\n        newMap = CompatibilityConversion.transform(Collections.singletonMap(\"observability.metrics.access.interval\", \"30\"));\n        assertEquals(1, newMap.size());\n        assertEquals(\"30\", newMap.get(\"plugin.observability.access.metric.interval\"));\n\n        newMap = CompatibilityConversion.transform(Collections.singletonMap(\"observability.tracings.remoteInvoke.enabled\", \"true\"));\n        assertEquals(1, newMap.size());\n        assertEquals(\"true\", newMap.get(\"plugin.observability.webclient.tracing.enabled\"));\n\n        newMap = CompatibilityConversion.transform(Collections.singletonMap(\"observability.tracings.request.enabled\", \"true\"));\n        assertEquals(REQUEST_NAMESPACE.length, newMap.size());\n        for (String s : REQUEST_NAMESPACE) {\n            assertEquals(\"true\", newMap.get(\"plugin.observability.\" + s + \".tracing.enabled\"));\n        }\n\n        newMap = CompatibilityConversion.transform(Collections.singletonMap(\"observability.metrics.jvmGc.enabled\", \"true\"));\n        assertEquals(1, newMap.size());\n        assertEquals(\"true\", newMap.get(\"observability.metrics.jvmGc.enabled\"));\n\n\n        newMap = CompatibilityConversion.transform(Collections.singletonMap(\"observability.tracings.sampledType\", \"counting\"));\n        assertEquals(1, newMap.size());\n        assertEquals(\"counting\", newMap.get(\"observability.tracings.sampledType\"));\n\n        newMap = CompatibilityConversion.transform(Collections.singletonMap(\"observability.tracings.sampled\", \"100\"));\n        assertEquals(1, newMap.size());\n        assertEquals(\"100\", newMap.get(\"observability.tracings.sampled\"));\n\n\n        newMap = CompatibilityConversion.transform(Collections.singletonMap(\"observability.tracings.output.enabled\", \"true\"));\n        assertEquals(1, newMap.size());\n        assertEquals(\"true\", newMap.get(\"observability.tracings.output.enabled\"));\n\n        newMap = CompatibilityConversion.transform(Collections.singletonMap(\"observability.metrics.jdbcConnection.interval\", \"30\"));\n        assertEquals(1, newMap.size());\n        assertEquals(\"30\", newMap.get(\"plugin.observability.jdbcConnection.metric.interval\"));\n\n        newMap = CompatibilityConversion.transform(Collections.singletonMap(\"observability.metrics.aaaaaaaaaa.interval\", \"30\"));\n        assertEquals(1, newMap.size());\n        assertEquals(\"30\", newMap.get(\"plugin.observability.aaaaaaaaaa.metric.interval\"));\n    }\n}\n"
  },
  {
    "path": "config/src/test/java/com/megaease/easeagent/config/ConfigFactoryTest.java",
    "content": "/*\n * Copyright (c) 2022, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.megaease.easeagent.config;\n\nimport com.megaease.easeagent.plugin.utils.SystemEnv;\nimport org.junit.Test;\nimport org.mockito.MockedStatic;\nimport org.mockito.Mockito;\n\nimport java.io.File;\nimport java.net.URISyntaxException;\n\nimport static com.megaease.easeagent.config.ConfigFactory.AGENT_SERVICE;\nimport static com.megaease.easeagent.config.ConfigFactory.AGENT_SYSTEM;\nimport static org.junit.Assert.assertEquals;\n\npublic class ConfigFactoryTest {\n\n    @Test\n    public void test_yaml() {\n        Configs config = ConfigFactory.loadConfigs(null, this.getClass().getClassLoader());\n        assertEquals(\"test-service\", config.getString(AGENT_SERVICE));\n        assertEquals(\"demo-system\", config.getString(AGENT_SYSTEM));\n    }\n\n    @Test\n    public void test_env() {\n        try (MockedStatic<SystemEnv> mock = Mockito.mockStatic(SystemEnv.class)) {\n            mock.when(() -> SystemEnv.get(ConfigFactory.EASEAGENT_ENV_CONFIG)).thenReturn(\"{\\\"name\\\":\\\"env-service\\\"}\");\n\n            Configs config = ConfigFactory.loadConfigs(null, this.getClass().getClassLoader());\n            assertEquals(\"env-service\", config.getString(AGENT_SERVICE));\n            assertEquals(\"demo-system\", config.getString(AGENT_SYSTEM));\n        }\n    }\n\n\n    @Test\n    public void test_loadConfigs() {\n        try (MockedStatic<SystemEnv> mockSystemEnv = Mockito.mockStatic(SystemEnv.class)) {\n            mockSystemEnv.when(() -> SystemEnv.get(\"EASEAGENT_NAME\")).thenReturn(\"service1\");\n            mockSystemEnv.when(() -> SystemEnv.get(\"EASEAGENT_SYSTEM\")).thenReturn(\"system1\");\n\n            Configs config = ConfigFactory.loadConfigs(null, this.getClass().getClassLoader());\n            assertEquals(\"service1\", config.getString(AGENT_SERVICE));\n            assertEquals(\"system1\", config.getString(AGENT_SYSTEM));\n\n            System.setProperty(\"easeagent.name\", \"service2\");\n            System.setProperty(\"easeagent.system\", \"system2\");\n            config = ConfigFactory.loadConfigs(null, this.getClass().getClassLoader());\n            assertEquals(\"service2\", config.getString(AGENT_SERVICE));\n            assertEquals(\"system2\", config.getString(AGENT_SYSTEM));\n        }\n\n    }\n\n    @Test\n    public void test_loadConfigsFromUserSpec() throws URISyntaxException {\n        String userSpec = new File(this.getClass().getClassLoader().getResource(\"user-spec.properties\").toURI()).getPath();\n\n        try (MockedStatic<SystemEnv> mockSystemEnv = Mockito.mockStatic(SystemEnv.class)) {\n            mockSystemEnv.when(() -> SystemEnv.get(\"EASEAGENT_CONFIG_PATH\")).thenReturn(userSpec);\n            String path = ConfigFactory.getConfigPath();\n            assertEquals(userSpec, path);\n        }\n    }\n\n\n    @Test\n    public void test_loadConfigsFromOtelUserSpec() throws URISyntaxException {\n        String userSpec = new File(this.getClass().getClassLoader().getResource(\"user-spec.properties\").toURI()).getPath();\n\n        try (MockedStatic<SystemEnv> mockSystemEnv = Mockito.mockStatic(SystemEnv.class)) {\n            mockSystemEnv.when(() -> SystemEnv.get(\"OTEL_JAVAAGENT_CONFIGURATION_FILE\")).thenReturn(userSpec);\n            String path = ConfigFactory.getConfigPath();\n            assertEquals(userSpec, path);\n        }\n    }\n}\n"
  },
  {
    "path": "config/src/test/java/com/megaease/easeagent/config/ConfigPropertiesUtilsTest.java",
    "content": "/*\n * Copyright (c) 2022, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.config;\n\nimport org.junit.*;\nimport org.junit.contrib.java.lang.system.EnvironmentVariables;\nimport org.junit.contrib.java.lang.system.RestoreSystemProperties;\n\npublic class ConfigPropertiesUtilsTest {\n\n    @Rule\n    public final EnvironmentVariables environmentVariables\n        = new EnvironmentVariables();\n\n    @Rule\n    public final RestoreSystemProperties restoreSystemProperties\n        = new RestoreSystemProperties();\n\n    @BeforeClass\n    public static void beforeClass() {\n        // EnvironmentVariables and restoreSystemProperties does not work with Java 16\n        Assume.assumeTrue(\n            System.getProperty(\"java.version\").startsWith(\"1.8\")\n                || System.getProperty(\"java.version\").startsWith(\"11\")\n        );\n    }\n\n    @Test\n    public void getString_systemProperty() {\n        environmentVariables.set(\"TEST_PROPERTY_STRING\", \"env\");\n        System.setProperty(\"test.property.string\", \"sys\");\n        Assert.assertEquals(\"sys\", ConfigPropertiesUtils.getString(\"test.property.string\"));\n    }\n\n    @Test\n    public void getString_environmentVariable() {\n        environmentVariables.set(\"TEST_PROPERTY_STRING\", \"env\");\n        Assert.assertEquals(\"env\", ConfigPropertiesUtils.getString(\"test.property.string\"));\n    }\n\n    @Test\n    public void getString_none() {\n        Assert.assertNull(ConfigPropertiesUtils.getString(\"test.property.string.none\"));\n    }\n\n    @Test\n    public void getInt_systemProperty() {\n        environmentVariables.set(\"TEST_PROPERTY_INT\", \"12\");\n        System.setProperty(\"test.property.int\", \"42\");\n        Assert.assertEquals(42, ConfigPropertiesUtils.getInt(\"test.property.int\", -1));\n    }\n\n    @Test\n    public void getInt_environmentVariable() {\n        environmentVariables.set(\"TEST_PROPERTY_INT\", \"12\");\n        Assert.assertEquals(12, ConfigPropertiesUtils.getInt(\"test.property.int\", -1));\n    }\n\n    @Test\n    public void getInt_none() {\n        Assert.assertEquals(-1, ConfigPropertiesUtils.getInt(\"test.property.int\", -1));\n    }\n\n    @Test\n    public void getInt_invalidNumber() {\n        System.setProperty(\"test.property.int\", \"not a number\");\n        Assert.assertEquals(-1, ConfigPropertiesUtils.getInt(\"test.property.int\", -1));\n    }\n\n    @Test\n    public void getBoolean_systemProperty() {\n        environmentVariables.set(\"TEST_PROPERTY_BOOLEAN\", \"false\");\n        System.setProperty(\"test.property.boolean\", \"true\");\n        Assert.assertTrue(ConfigPropertiesUtils.getBoolean(\"test.property.boolean\", false));\n    }\n\n    @Test\n    public void getBoolean_environmentVariable() {\n        environmentVariables.set(\"TEST_PROPERTY_BOOLEAN\", \"true\");\n        Assert.assertTrue(ConfigPropertiesUtils.getBoolean(\"test.property.boolean\", false));\n    }\n\n    @Test\n    public void getBoolean_none() {\n        Assert.assertFalse(ConfigPropertiesUtils.getBoolean(\"test.property.boolean\", false));\n    }\n}\n"
  },
  {
    "path": "config/src/test/java/com/megaease/easeagent/config/ConfigUtilsTest.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.config;\n\nimport com.megaease.easeagent.plugin.api.config.Config;\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport java.io.IOException;\nimport java.util.Collections;\nimport java.util.Map;\n\nimport static org.junit.Assert.assertTrue;\n\npublic class ConfigUtilsTest {\n    @Test\n    public void test_bindProp() throws Exception {\n        Configs configs = new Configs(Collections.singletonMap(\"hello\", \"world\"));\n        String[] rst = new String[]{null};\n        ConfigUtils.bindProp(\"hello\", configs, Config::getString, v -> rst[0] = v);\n        Assert.assertEquals(\"world\", rst[0]);\n        configs.updateConfigs(Collections.singletonMap(\"hello\", \"test\"));\n        Assert.assertEquals(\"test\", rst[0]);\n        configs.updateConfigs(Collections.singletonMap(\"hello\", \"one\"));\n        Assert.assertEquals(\"one\", rst[0]);\n    }\n\n    @Test\n    public void test_json2KVMap() throws Exception {\n        Map<String, String> map = ConfigUtils.json2KVMap(\"{\\n\" +\n            \"  \\\"output\\\": {\\n\" +\n            \"    \\\"servers\\\": \\\"127.0.0.1\\\",\\n\" +\n            \"    \\\"timeout\\\": 1000,\\n\" +\n            \"    \\\"enabled\\\": true,\\n\" +\n            \"    \\\"arr\\\": [\\\"x\\\", { \\\"test\\\": 0 }]\\n\" +\n            \"  },\\n\" +\n            \"  \\\"hello\\\":null,\\n\" +\n            \"  \\\"metrics\\\": {\\n\" +\n            \"    \\\"obj\\\": {\\n\" +\n            \"      \\\"a\\\": 1,\\n\" +\n            \"      \\\"b\\\": \\\"2\\\",\\n\" +\n            \"      \\\"c\\\": false\\n\" +\n            \"    },\\n\" +\n            \"    \\\"request\\\": {\\n\" +\n            \"      \\\"topic\\\": \\\"hello\\\",\\n\" +\n            \"      \\\"enabled\\\": false\\n\" +\n            \"    }\\n\" +\n            \"  }\\n\" +\n            \"}\");\n        Assert.assertEquals(\"127.0.0.1\", map.get(\"output.servers\"));\n        Assert.assertEquals(\"1000\", map.get(\"output.timeout\"));\n        Assert.assertEquals(\"true\", map.get(\"output.enabled\"));\n        Assert.assertEquals(\"x\", map.get(\"output.arr.0\"));\n        Assert.assertEquals(\"0\", map.get(\"output.arr.1.test\"));\n        Assert.assertEquals(\"\", map.get(\"hello\"));\n        Assert.assertEquals(\"1\", map.get(\"metrics.obj.a\"));\n        Assert.assertEquals(\"2\", map.get(\"metrics.obj.b\"));\n        Assert.assertEquals(\"false\", map.get(\"metrics.obj.c\"));\n        Assert.assertEquals(\"hello\", map.get(\"metrics.request.topic\"));\n        Assert.assertEquals(\"false\", map.get(\"metrics.request.enabled\"));\n    }\n\n    @Test\n    public void test_json2KVMap_2() throws IOException {\n        Map<String, String> map = ConfigUtils.json2KVMap(\"{\\\"serviceHeaders\\\":{\\\"mesh-app-backend\\\":[\\\"X-canary\\\"]}}\");\n        Assert.assertEquals(\"X-canary\", map.get(\"serviceHeaders.mesh-app-backend.0\"));\n    }\n\n    @Test\n    public void isGlobal() {\n        Assert.assertTrue(ConfigUtils.isGlobal(\"global\"));\n        Assert.assertFalse(ConfigUtils.isGlobal(\"globaldf\"));\n    }\n\n    @Test\n    public void isPluginConfig() {\n        Assert.assertTrue(ConfigUtils.isPluginConfig(\"plugin.\"));\n        Assert.assertFalse(ConfigUtils.isPluginConfig(\"plugin\"));\n        Assert.assertFalse(ConfigUtils.isPluginConfig(\"plugins.\"));\n        Assert.assertTrue(ConfigUtils.isPluginConfig(\"plugin.observability.kafka.kafka-trace\", \"observability\", \"kafka\", \"kafka-trace\"));\n        Assert.assertFalse(ConfigUtils.isPluginConfig(\"plugin.observability.kafka.kafka-trace\", \"observabilitys\", \"kafka\", \"kafka-trace\"));\n        Assert.assertFalse(ConfigUtils.isPluginConfig(\"plugin.observability.kafka.kafka-trace\", \"observability\", \"kafkas\", \"kafka-trace\"));\n        Assert.assertFalse(ConfigUtils.isPluginConfig(\"plugin.observability.kafka.kafka-trace\", \"observability\", \"kafka\", \"kafka-traces\"));\n    }\n\n    @Test\n    public void pluginProperty() {\n        PluginProperty pluginProperty = ConfigUtils.pluginProperty(\"plugin.observability.kafka.self.enabled\");\n        Assert.assertEquals(\"observability\", pluginProperty.getDomain());\n        Assert.assertEquals(\"kafka\", pluginProperty.getNamespace());\n        Assert.assertEquals(\"self\", pluginProperty.getId());\n        Assert.assertEquals(\"enabled\", pluginProperty.getProperty());\n        pluginProperty = ConfigUtils.pluginProperty(\"plugin.observability.kafka.self.tcp.enabled\");\n        Assert.assertEquals(\"observability\", pluginProperty.getDomain());\n        Assert.assertEquals(\"kafka\", pluginProperty.getNamespace());\n        Assert.assertEquals(\"self\", pluginProperty.getId());\n        Assert.assertEquals(\"tcp.enabled\", pluginProperty.getProperty());\n        try {\n            ConfigUtils.pluginProperty(\"plugin.observability.kafka.self\");\n            assertTrue(\"must be error\", false);\n        } catch (Exception e) {\n            Assert.assertNotNull(e);\n        }\n\n\n    }\n}\n"
  },
  {
    "path": "config/src/test/java/com/megaease/easeagent/config/ConfigsTest.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.config;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.megaease.easeagent.plugin.api.config.ChangeItem;\nimport com.megaease.easeagent.plugin.api.config.Config;\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport java.util.Collections;\nimport java.util.LinkedList;\nimport java.util.List;\n\npublic class ConfigsTest {\n\n    @Test\n    public void test_check_change_count1() throws Exception {\n        final ObjectMapper json = new ObjectMapper();\n        GlobalConfigs configs = new GlobalConfigs(Collections.singletonMap(\"hello\", \"world\"));\n        List<ChangeItem> rst = addListener(configs);\n\n        configs.updateService(\"{}\", null);\n        Assert.assertEquals(0, rst.size());\n\n        configs.updateService(json.writeValueAsString(Collections.singletonMap(\"hello\", \"world\")), null);\n        Assert.assertEquals(0, rst.size());\n\n        configs.updateService(json.writeValueAsString(Collections.singletonMap(\"hello\", \"world2\")), null);\n        Assert.assertEquals(1, rst.size());\n    }\n\n    @Test\n    public void test_check_change_count() {\n        Configs configs = new Configs(Collections.singletonMap(\"hello\", \"world\"));\n        List<ChangeItem> rst = addListener(configs);\n\n        configs.updateConfigs(Collections.emptyMap());\n        Assert.assertEquals(0, rst.size());\n\n        configs.updateConfigs(Collections.singletonMap(\"hello\", \"world\"));\n        Assert.assertEquals(0, rst.size());\n\n        configs.updateConfigs(Collections.singletonMap(\"hello\", \"world2\"));\n        Assert.assertEquals(1, rst.size());\n    }\n\n    @Test\n    public void test_check_old() {\n        Configs configs = new Configs(Collections.singletonMap(\"hello\", \"world\"));\n        List<ChangeItem> rst = addListener(configs);\n        configs.updateConfigs(Collections.singletonMap(\"hello\", \"test\"));\n        ChangeItem first = rst.get(0);\n        Assert.assertEquals(\"hello\", first.getFullName());\n        Assert.assertEquals(\"hello\", first.getName());\n        Assert.assertEquals(\"world\", first.getOldValue());\n        Assert.assertEquals(\"test\", first.getNewValue());\n    }\n\n\n    @Test\n    public void test_check_new() {\n        Configs configs = new Configs(Collections.singletonMap(\"hello\", \"world\"));\n        List<ChangeItem> rst = addListener(configs);\n        configs.updateConfigs(Collections.singletonMap(\"name\", \"666\"));\n        ChangeItem first = rst.get(0);\n        Assert.assertEquals(\"name\", first.getFullName());\n        Assert.assertEquals(\"name\", first.getName());\n        Assert.assertNull(first.getOldValue());\n        Assert.assertEquals(\"666\", first.getNewValue());\n    }\n\n\n    private List<ChangeItem> addListener(Config config) {\n        List<ChangeItem> rst = new LinkedList<>();\n        config.addChangeListener(rst::addAll);\n        return rst;\n    }\n}\n"
  },
  {
    "path": "config/src/test/java/com/megaease/easeagent/config/IPluginConfigConstTest.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.config;\n\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport org.junit.Test;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNull;\n\npublic class IPluginConfigConstTest {\n    @Test\n    public void testExtractHeaderName() {\n        String prefix = \"globalCanaryHeaders.serviceHeaders\";\n        assertEquals(ConfigConst.GlobalCanaryLabels.SERVICE_HEADERS, prefix);\n        assertEquals(\"hello\", ConfigConst.GlobalCanaryLabels.extractHeaderName(prefix + \".test-aaa.0.hello\"));\n        assertEquals(\"world\", ConfigConst.GlobalCanaryLabels.extractHeaderName(prefix + \".test-aaa.1.world\"));\n        assertNull(ConfigConst.GlobalCanaryLabels.extractHeaderName(prefix + \".test-bbb\"));\n        assertNull(ConfigConst.GlobalCanaryLabels.extractHeaderName(prefix + \"test-bbb\"));\n    }\n}\n"
  },
  {
    "path": "config/src/test/java/com/megaease/easeagent/config/JarFileConfigLoaderTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.config;\n\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport org.junit.After;\nimport org.junit.Test;\n\nimport java.io.File;\nimport java.net.URISyntaxException;\n\nimport static org.junit.Assert.*;\n\npublic class JarFileConfigLoaderTest {\n\n    @After\n    public void after() {\n        System.clearProperty(ConfigConst.AGENT_JAR_PATH);\n    }\n\n\n    @Test\n    public void load() throws URISyntaxException {\n        String fileName = \"agent.properties\";\n        assertNull(JarFileConfigLoader.load(null));\n        assertNull(JarFileConfigLoader.load(fileName));\n        String jarPath = new File(this.getClass().getClassLoader().getResource(\"easeagent_config.jar\").toURI()).getPath();\n        System.setProperty(ConfigConst.AGENT_JAR_PATH, jarPath);\n\n        GlobalConfigs config = JarFileConfigLoader.load(fileName);\n        assertNotNull(config);\n        assertEquals(\"demo-jar-service\", config.getString(\"name\"));\n        assertEquals(\"demo-system\", config.getString(\"system\"));\n\n        config = JarFileConfigLoader.load(\"agent.yaml\");\n        assertNotNull(config);\n        assertEquals(\"test-jar-service\", config.getString(\"name\"));\n        assertEquals(\"demo-system\", config.getString(\"system\"));\n\n        config = JarFileConfigLoader.load(\"agentaaaa.yaml\");\n        assertNull(config);\n\n\n        String nullJarPath = new File(\"easeagent_config_null.jar\").getPath();\n        System.setProperty(ConfigConst.AGENT_JAR_PATH, nullJarPath);\n\n        config = JarFileConfigLoader.load(\"agent.yaml\");\n        assertNull(config);\n\n    }\n}\n"
  },
  {
    "path": "config/src/test/java/com/megaease/easeagent/config/OtelSdkConfigsTest.java",
    "content": "/*\n * Copyright (c) 2022, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.config;\n\nimport com.megaease.easeagent.plugin.utils.SystemEnv;\nimport org.junit.After;\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport java.lang.reflect.Field;\nimport java.util.Map;\n\n\npublic class OtelSdkConfigsTest {\n\n    @After\n    public void after() throws NoSuchFieldException, IllegalAccessException {\n        Field field = SystemEnv.class.getDeclaredField(\"ENVIRONMENTS\");\n        field.setAccessible(true);\n        Object env = field.get(null);\n        field.setAccessible(false);\n        Map<String, String> envMap = (Map<String, String>) env;\n        envMap.remove(\"OTEL_RESOURCE_ATTRIBUTES\");\n        envMap.remove(\"OTEL_SERVICE_NAME\");\n        envMap.remove(\"OTEL_SERVICE_NAMESPACE\");\n        System.clearProperty(\"otel.service.name\");\n        System.clearProperty(\"otel.service.namespace\");\n    }\n\n    @Test\n    public void updateEnvCfg() {\n        //value from system env \"OTEL_RESOURCE_ATTRIBUTES\n        String attributes = \"service.name=service1,service.namespace=namespace1\";\n        SystemEnv.set(\"OTEL_RESOURCE_ATTRIBUTES\", attributes);\n        Map<String, String> envCfg = OtelSdkConfigs.updateEnvCfg();\n        Assert.assertEquals(\"service1\", envCfg.get(\"name\"));\n        Assert.assertEquals(\"namespace1\", envCfg.get(\"system\"));\n\n        // override by system env\n        SystemEnv.set(\"OTEL_SERVICE_NAME\", \"service2\");\n        SystemEnv.set(\"OTEL_SERVICE_NAMESPACE\", \"namespace2\");\n        envCfg = OtelSdkConfigs.updateEnvCfg();\n        Assert.assertEquals(\"service2\", envCfg.get(\"name\"));\n        Assert.assertEquals(\"namespace2\", envCfg.get(\"system\"));\n\n        // override by system property\n        System.setProperty(\"otel.service.name\", \"service3\");\n        System.setProperty(\"otel.service.namespace\", \"namespace3\");\n        envCfg = OtelSdkConfigs.updateEnvCfg();\n        Assert.assertEquals(\"service3\", envCfg.get(\"name\"));\n        Assert.assertEquals(\"namespace3\", envCfg.get(\"system\"));\n\n    }\n}\n"
  },
  {
    "path": "config/src/test/java/com/megaease/easeagent/config/PluginConfigManagerTest.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.config;\n\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.config.PluginConfigChangeListener;\nimport org.junit.Test;\n\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.concurrent.atomic.AtomicReference;\n\nimport static org.junit.Assert.*;\n\npublic class PluginConfigManagerTest {\n    public static String DOMAIN = \"testDomain\";\n    public static String NAMESPACE = \"testNamespace\";\n    public static String TEST_TRACE_ID = \"test-trace\";\n    public static String GLOBAL_ID = TEST_TRACE_ID;\n    public static String TEST_METRIC_ID = \"test-metric\";\n    public static String TEST_AAA_ID = \"test-AAA\";\n\n\n    PluginConfigManager build() {\n        return build(PluginSourceConfigTest.buildSource());\n    }\n\n    PluginConfigManager build(Map<String, String> source) {\n        Configs configs = new Configs(source);\n        return PluginConfigManager.builder(configs).build();\n    }\n\n    @Test\n    public void testBuild() {\n        build();\n        assertTrue(true);\n    }\n\n    @Test\n    public void getConfig() {\n        PluginConfigManager pluginConfigManager = build();\n        for (Map.Entry<String, String> entry : PluginSourceConfigTest.buildSource().entrySet()) {\n            assertEquals(pluginConfigManager.getConfig(entry.getKey()), entry.getValue());\n        }\n    }\n\n    private void checkPluginConfigString(PluginConfig pluginConfig, Map<String, String> source) {\n        for (Map.Entry<String, String> entry : source.entrySet()) {\n            assertEquals(pluginConfig.getString(entry.getKey()), entry.getValue());\n        }\n    }\n\n    public static Map<String, String> buildSource() {\n        Map<String, String> source = getSource(\"global\", GLOBAL_ID, PluginConfigTest.globalSource());\n        source.putAll(getSource(\"global\", TEST_AAA_ID, PluginConfigTest.globalSource()));\n        source.putAll(getSource(NAMESPACE, TEST_TRACE_ID, PluginConfigTest.coverSource()));\n        source.put(\"plugin.testDomain.testssss.self.lll\", \"aaa\");\n        source.put(\"plugin.testDomain.testssss.kafka.lll\", \"aaa\");\n        source.put(\"plugin.testDomain.testssss.kafka.lll\", \"aaa\");\n        source.put(\"plugin.testDomain.testssss.kafka.lll\", \"aaa\");\n        return source;\n    }\n\n\n    public static Map<String, String> getSource(String namespace, String id, Map<String, String> properties) {\n        Map<String, String> s = new HashMap<>();\n        for (Map.Entry<String, String> pEntry : properties.entrySet()) {\n            s.put(\"plugin.\" + DOMAIN + \".\" + namespace + \".\" + id + \".\" + pEntry.getKey(), pEntry.getValue());\n        }\n        return s;\n    }\n\n    @Test\n    public void getConfig1() throws InterruptedException {\n        PluginConfigManager pluginConfigManager = build();\n        PluginConfig pluginConfig = pluginConfigManager.getConfig(PluginSourceConfigTest.DOMAIN, \"global\", PluginSourceConfigTest.GLOBAL_ID);\n        checkPluginConfigString(pluginConfig, PluginConfigTest.globalSource());\n        pluginConfig = pluginConfigManager.getConfig(PluginSourceConfigTest.DOMAIN, PluginSourceConfigTest.NAMESPACE, PluginSourceConfigTest.TEST_TRACE_ID);\n        checkPluginConfigString(pluginConfig, PluginConfigTest.globalSource());\n        pluginConfig = pluginConfigManager.getConfig(PluginSourceConfigTest.DOMAIN, PluginSourceConfigTest.NAMESPACE, PluginSourceConfigTest.TEST_METRIC_ID);\n        checkPluginConfigString(pluginConfig, PluginConfigTest.globalSource());\n\n        Map<String, String> source = buildSource();\n        source.putAll(getSource(\"trace\", TEST_TRACE_ID, PluginConfigTest.coverSource()));\n        source.putAll(getSource(\"trace\", GLOBAL_ID, PluginConfigTest.coverSource()));\n        build(source);\n\n        Configs configs = new Configs(buildSource());\n        pluginConfigManager = PluginConfigManager.builder(configs).build();\n        final PluginConfig pluginConfig1 = pluginConfigManager.getConfig(DOMAIN, NAMESPACE, TEST_TRACE_ID);\n        final AtomicReference<IPluginConfig> oldPluginConfig = new AtomicReference<>();\n        final AtomicReference<IPluginConfig> newPluginConfig = new AtomicReference<>();\n        PluginConfigTest.checkAllType(pluginConfig1);\n        pluginConfig1.addChangeListener((oldConfig, newConfig) -> {\n            oldPluginConfig.set(oldConfig);\n            newPluginConfig.set(newConfig);\n        });\n\n        configs.updateConfigs(Collections.singletonMap(String.format(\"plugin.%s.%s.%s.enabled\", DOMAIN, NAMESPACE, TEST_TRACE_ID), \"false\"));\n        assertNotNull(oldPluginConfig.get());\n        assertNotNull(newPluginConfig.get());\n        assertTrue(oldPluginConfig.get() == pluginConfig1);\n        final AtomicReference<PluginConfigChangeListener> oldPluginConfigListener = new AtomicReference<>();\n        final AtomicReference<PluginConfigChangeListener> newPluginConfigListener = new AtomicReference<>();\n        ((PluginConfig) oldPluginConfig.get()).foreachConfigChangeListener(listener -> oldPluginConfigListener.set(listener));\n        ((PluginConfig) newPluginConfig.get()).foreachConfigChangeListener(listener -> newPluginConfigListener.set(listener));\n        assertTrue(oldPluginConfigListener.get() == newPluginConfigListener.get());\n        assertTrue(oldPluginConfig.get().getBoolean(\"enabled\"));\n        assertFalse(newPluginConfig.get().getBoolean(\"enabled\"));\n        PluginConfigTest.checkAllType((PluginConfig) oldPluginConfig.get());\n\n        configs.updateConfigs(Collections.singletonMap(String.format(\"plugin.%s.%s.%s.enabled\", DOMAIN, NAMESPACE, TEST_TRACE_ID), \"true\"));\n        assertFalse(oldPluginConfig.get().getBoolean(\"enabled\"));\n        assertTrue(newPluginConfig.get().getBoolean(\"enabled\"));\n\n\n        configs.updateConfigs(Collections.singletonMap(String.format(\"plugin.%s.global.%s.enabled\", DOMAIN, TEST_TRACE_ID), \"false\"));\n        assertTrue(oldPluginConfig.get().getBoolean(\"enabled\"));\n        assertFalse(newPluginConfig.get().getBoolean(\"enabled\"));\n\n\n        IPluginConfig newConfig = newPluginConfig.get();\n        configs.updateConfigs(Collections.singletonMap(String.format(\"plugin.%s.global.%s.enabled\", DOMAIN, TEST_AAA_ID), \"false\"));\n        assertTrue(newPluginConfig.get() == newConfig);\n\n        configs.updateConfigs(Collections.singletonMap(String.format(\"ssss.%s.global.%s.enabled\", DOMAIN, TEST_AAA_ID), \"false\"));\n    }\n\n}\n"
  },
  {
    "path": "config/src/test/java/com/megaease/easeagent/config/PluginConfigTest.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.config;\n\nimport com.megaease.easeagent.plugin.api.config.PluginConfigChangeListener;\nimport org.junit.Test;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.function.Consumer;\n\nimport static org.junit.Assert.*;\n\npublic class PluginConfigTest {\n\n    public static Map<String, String> globalSource() {\n        Map<String, String> global = new HashMap<>();\n        global.put(\"enabled\", \"true\");\n        global.put(\"tcp.enabled\", \"true\");\n        global.put(\"host\", \"127.0.0.1\");\n        global.put(\"count\", \"127\");\n        global.put(\"double\", \"127.1\");\n        global.put(\"double_1\", \"127.2\");\n        global.put(\"list\", \"a,b,c\");\n        return global;\n    }\n\n    public static Map<String, String> coverSource() {\n        Map<String, String> cover = new HashMap<>();\n        cover.put(\"tcp.enabled\", \"false\");\n        cover.put(\"http.enabled\", \"true\");\n        cover.put(\"host\", \"127.0.0.3\");\n        cover.put(\"count\", \"127\");\n        cover.put(\"double\", \"127.3\");\n        cover.put(\"list\", \"a,b,c\");\n        return cover;\n    }\n\n    PluginConfig build() {\n        String domain = \"testdomain\";\n        String id = \"testid\";\n        String namespace = \"NAMESPACE\";\n        Map<String, String> global = globalSource();\n        Map<String, String> cover = coverSource();\n        return PluginConfig.build(domain, id, global, namespace, cover, null);\n    }\n\n    @Test\n    public void domain() {\n        assertEquals(\"testdomain\", build().domain());\n    }\n\n    @Test\n    public void namespace() {\n        assertEquals(\"NAMESPACE\", build().namespace());\n    }\n\n    @Test\n    public void id() {\n        assertEquals(\"testid\", build().id());\n    }\n\n    @Test\n    public void hasProperty() {\n        checkHasProperty(build());\n\n    }\n\n    public static void checkHasProperty(PluginConfig config) {\n        assertTrue(config.hasProperty(\"enabled\"));\n        assertTrue(config.hasProperty(\"tcp.enabled\"));\n        assertTrue(config.hasProperty(\"http.enabled\"));\n        assertFalse(config.hasProperty(\"http.enabled.cccc\"));\n    }\n\n    @Test\n    public void getString() {\n        checkString(build());\n    }\n\n    public static void checkString(PluginConfig config) {\n        assertEquals(\"true\", config.getString(\"enabled\"));\n        assertEquals(\"false\", config.getString(\"tcp.enabled\"));\n        assertEquals(\"127\", config.getString(\"count\"));\n        assertEquals(\"127.0.0.3\", config.getString(\"host\"));\n        assertEquals(\"true\", config.getString(\"http.enabled\"));\n        assertNull(config.getString(\"http.enabled.sss\"));\n    }\n\n    @Test\n    public void getInt() {\n        checkInt(build());\n    }\n\n    public static void checkInt(PluginConfig config) {\n        assertEquals(127, (int) config.getInt(\"count\"));\n        assertNull(config.getInt(\"enabled\"));\n        assertNull(config.getInt(\"cccccccccccccc\"));\n    }\n\n\n    @Test\n    public void getBoolean() {\n        checkBoolean(build());\n    }\n\n    public static void checkBoolean(PluginConfig config) {\n        assertTrue(config.getBoolean(\"enabled\"));\n        assertFalse(config.getBoolean(\"tcp.enabled\"));\n        assertFalse(config.getBoolean(\"http.enabled\"));\n        assertFalse(config.getBoolean(\"http.enabled.ssss\"));\n    }\n\n    @Test\n    public void getDouble() {\n        checkDouble(build());\n    }\n\n\n    public static void checkDouble(PluginConfig config) {\n        assertTrue(Math.abs(config.getDouble(\"double\") - 127.3) < 0.0001);\n        assertTrue(Math.abs(config.getDouble(\"double_1\") - 127.2) < 0.0001);\n        assertNull(config.getDouble(\"enabled\"));\n    }\n\n    @Test\n    public void getLong() {\n        checkLong(build());\n    }\n\n    public static void checkLong(PluginConfig config) {\n        assertEquals(127l, (long) config.getLong(\"count\"));\n        assertNull(config.getLong(\"enabled\"));\n        assertNull(config.getLong(\"cccccccccccccc\"));\n    }\n\n    @Test\n    public void getStringList() {\n        checkStringList(build());\n    }\n\n\n    public static void checkStringList(PluginConfig config) {\n        List<String> list = config.getStringList(\"list\");\n        assertEquals(3, list.size());\n        assertEquals(\"a\", list.get(0));\n        assertEquals(\"b\", list.get(1));\n        assertEquals(\"c\", list.get(2));\n    }\n\n\n    @Test\n    public void addChangeListener() {\n        PluginConfig config = build();\n        config.addChangeListener((oldConfig, newConfig) -> {\n        });\n        AtomicInteger count = new AtomicInteger(0);\n        config.foreachConfigChangeListener(new Consumer<PluginConfigChangeListener>() {\n            @Override\n            public void accept(PluginConfigChangeListener listener) {\n                count.incrementAndGet();\n            }\n        });\n        assertEquals(1, count.get());\n    }\n\n    @Test\n    public void getConfigChangeListener() {\n        addChangeListener();\n    }\n\n    @Test\n    public void keySet() {\n        checkKeySet(build());\n    }\n\n    public static void checkKeySet(PluginConfig config) {\n        Set<String> set = config.keySet();\n        Map<String, String> source = globalSource();\n        source.putAll(coverSource());\n        assertEquals(set.size(), source.size());\n        for (String s : set) {\n            assertTrue(source.containsKey(s));\n        }\n    }\n\n\n    public static void checkAllType(PluginConfig config) {\n        checkHasProperty(config);\n        checkString(config);\n        checkBoolean(config);\n        checkInt(config);\n        checkLong(config);\n        checkDouble(config);\n        checkStringList(config);\n        checkKeySet(config);\n    }\n\n\n}\n"
  },
  {
    "path": "config/src/test/java/com/megaease/easeagent/config/PluginPropertyTest.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.config;\n\nimport org.junit.Test;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static org.junit.Assert.*;\n\npublic class PluginPropertyTest {\n\n    public PluginProperty build() {\n        return new PluginProperty(\"testdomain\", \"testnamespace\", \"testId\", \"testproperty\");\n    }\n\n    @Test\n    public void getDomain() {\n        assertEquals(\"testdomain\", build().getDomain());\n    }\n\n    @Test\n    public void getNamespace() {\n        assertEquals(\"testnamespace\", build().getNamespace());\n    }\n\n    @Test\n    public void getId() {\n        assertEquals(\"testId\", build().getId());\n    }\n\n    @Test\n    public void getProperty() {\n        assertEquals(\"testproperty\", build().getProperty());\n    }\n\n    @Test\n    public void testEquals() {\n        assertTrue(build().equals(build()));\n        PluginProperty property = new PluginProperty(\"testdomain\", \"testnamespace\", \"testId\", \"testproperty1\");\n        assertFalse(build().equals(property));\n        Map<PluginProperty, String> pluginPropertyStringMap = new HashMap<>();\n        pluginPropertyStringMap.put(build(), \"testValue\");\n        assertTrue(pluginPropertyStringMap.containsKey(build()));\n        assertEquals(\"testValue\", pluginPropertyStringMap.get(build()));\n        assertFalse(pluginPropertyStringMap.containsKey(property));\n    }\n\n    @Test\n    public void testHashCode() {\n        assertEquals(build().hashCode(), build().hashCode());\n        PluginProperty property = new PluginProperty(\"testdomain\", \"testnamespace\", \"testId\", \"testproperty1\");\n        assertNotEquals(build().hashCode(), property.hashCode());\n    }\n}\n"
  },
  {
    "path": "config/src/test/java/com/megaease/easeagent/config/PluginSourceConfigTest.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.config;\n\nimport org.junit.Test;\n\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotNull;\n\npublic class PluginSourceConfigTest {\n\n    public static String DOMAIN = \"testDomain\";\n    public static String NAMESPACE = \"testNamespace\";\n    public static String TEST_TRACE_ID = \"test-trace\";\n    public static String GLOBAL_ID = TEST_TRACE_ID;\n    public static String TEST_METRIC_ID = \"test-metric\";\n    public static String TEST_AAA_ID = \"test-AAA\";\n\n\n    public static Map<String, String> getSource(String namespace, String id) {\n        Map<String, String> properties = PluginConfigTest.globalSource();\n        Map<String, String> s = new HashMap<>();\n        for (Map.Entry<String, String> pEntry : properties.entrySet()) {\n            s.put(\"plugin.\" + DOMAIN + \".\" + namespace + \".\" + id + \".\" + pEntry.getKey(), pEntry.getValue());\n        }\n        return s;\n    }\n\n    public static Map<String, String> getSource(String id) {\n        return getSource(NAMESPACE, id);\n    }\n\n\n    public static Map<String, String> getGlobal() {\n        return getSource(\"global\", GLOBAL_ID);\n    }\n\n    public static Map<String, String> buildSource() {\n        Map<String, String> source = getGlobal();\n        source.putAll(getSource(TEST_METRIC_ID));\n        source.putAll(getSource(TEST_TRACE_ID));\n        source.put(\"plugin.testDomain.testssss.self.lll\", \"aaa\");\n        source.put(\"plugin.testDomain.testssss.kafka.lll\", \"aaa\");\n        source.put(\"plugin.testDomain.testssss.kafka.lll\", \"aaa\");\n        source.put(\"plugin.testDomain.testssss.kafka.lll\", \"aaa\");\n        return source;\n    }\n\n    PluginSourceConfig buildImpl(String namespace, String id) {\n\n        Map<String, String> source = buildSource();\n        return PluginSourceConfig.build(DOMAIN, namespace, id, source);\n    }\n\n    @Test\n    public void build() {\n        PluginSourceConfig config = buildImpl(NAMESPACE, TEST_TRACE_ID);\n        assertNotNull(config);\n\n    }\n\n    @Test\n    public void getSource() {\n        assertEquals(buildImpl(\"global\", GLOBAL_ID).getSource(), getGlobal());\n        assertEquals(Collections.emptyMap(), buildImpl(NAMESPACE, TEST_AAA_ID).getSource());\n        assertEquals(getSource(TEST_TRACE_ID), buildImpl(NAMESPACE, TEST_TRACE_ID).getSource());\n        assertEquals(getSource(TEST_METRIC_ID), buildImpl(NAMESPACE, TEST_METRIC_ID).getSource());\n    }\n\n    @Test\n    public void getDomain() {\n        assertEquals(buildImpl(NAMESPACE, TEST_AAA_ID).getDomain(), DOMAIN);\n        assertEquals(buildImpl(NAMESPACE, TEST_TRACE_ID).getDomain(), DOMAIN);\n        assertEquals(buildImpl(NAMESPACE, TEST_METRIC_ID).getDomain(), DOMAIN);\n    }\n\n    @Test\n    public void getNamespace() {\n        assertEquals(buildImpl(NAMESPACE, TEST_AAA_ID).getNamespace(), NAMESPACE);\n        assertEquals(buildImpl(NAMESPACE, TEST_TRACE_ID).getNamespace(), NAMESPACE);\n        assertEquals(buildImpl(NAMESPACE, TEST_METRIC_ID).getNamespace(), NAMESPACE);\n    }\n\n\n    @Test\n    public void getId() {\n        assertEquals(buildImpl(NAMESPACE, TEST_AAA_ID).getId(), TEST_AAA_ID);\n        assertEquals(buildImpl(NAMESPACE, TEST_TRACE_ID).getId(), TEST_TRACE_ID);\n        assertEquals(buildImpl(NAMESPACE, TEST_METRIC_ID).getId(), TEST_METRIC_ID);\n    }\n\n    @Test\n    public void getProperties() {\n\n        assertEquals(buildImpl(\"global\", GLOBAL_ID).getProperties(), PluginConfigTest.globalSource());\n        assertEquals(buildImpl(NAMESPACE, TEST_TRACE_ID).getProperties(), PluginConfigTest.globalSource());\n        assertEquals(buildImpl(NAMESPACE, TEST_METRIC_ID).getProperties(), PluginConfigTest.globalSource());\n        assertEquals(buildImpl(NAMESPACE, TEST_AAA_ID).getProperties(), new HashMap<>());\n    }\n}\n"
  },
  {
    "path": "config/src/test/java/com/megaease/easeagent/config/ValidateUtilsTest.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.config;\n\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport java.util.Collections;\n\nimport static com.megaease.easeagent.config.ValidateUtils.*;\n\npublic class ValidateUtilsTest {\n    @Test\n    public void test_hasText() throws Exception {\n        Configs configs = new Configs(Collections.emptyMap());\n        try {\n            ValidateUtils.validate(configs, \"hello\", HasText);\n            Assert.fail(\"Never get here.\");\n        } catch (ValidateUtils.ValidException e) {\n            Assert.assertTrue(e.getMessage().contains(\"has no non-empty value\"));\n        }\n    }\n\n    @Test\n    public void test_numberInt() throws Exception {\n        Configs configs = new Configs(Collections.singletonMap(\"hello\", \"test\"));\n        try {\n            ValidateUtils.validate(configs, \"hello\", HasText, NumberInt);\n            Assert.fail(\"Never get here.\");\n        } catch (ValidateUtils.ValidException e) {\n            Assert.assertTrue(e.getMessage().contains(\"has no integer value\"));\n        }\n    }\n\n    @Test\n    public void test_numberInt2() throws Exception {\n        Configs configs = new Configs(Collections.singletonMap(\"hello\", \"100\"));\n        try {\n            ValidateUtils.validate(configs, \"hello\", HasText, NumberInt);\n        } catch (ValidateUtils.ValidException e) {\n            Assert.fail(\"Never get here.\");\n        }\n    }\n\n    @Test\n    public void test_bool() throws Exception {\n        Configs configs = new Configs(Collections.singletonMap(\"hello\", \"test\"));\n        try {\n            ValidateUtils.validate(configs, \"hello\", HasText, Bool);\n            Assert.fail(\"Never get here.\");\n        } catch (ValidateUtils.ValidException e) {\n            Assert.assertTrue(e.getMessage().contains(\"has no boolean value\"));\n        }\n    }\n\n    @Test\n    public void test_bool2() throws Exception {\n        Configs configs = new Configs(Collections.singletonMap(\"hello\", \"true\"));\n        try {\n            ValidateUtils.validate(configs, \"hello\", HasText, Bool);\n        } catch (ValidateUtils.ValidException e) {\n            Assert.fail(\"Never get here.\");\n        }\n    }\n\n}"
  },
  {
    "path": "config/src/test/java/com/megaease/easeagent/config/report/ReportConfigAdapterTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.config.report;\n\nimport com.megaease.easeagent.config.GlobalConfigs;\nimport com.megaease.easeagent.plugin.utils.NoNull;\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport java.util.HashMap;\n\nimport static com.megaease.easeagent.config.report.ReportConfigConst.*;\n\npublic class ReportConfigAdapterTest {\n    @Test\n    public void test_zipkin_target() {\n        HashMap<String, String> cfg = new HashMap<>();\n        String url = \"http://localhost:9411/api/v2/spans\";\n        cfg.put(\"observability.tracings.output.target\", \"zipkin\");\n        cfg.put(\"observability.tracings.output.target.zipkinUrl\", url);\n        GlobalConfigs configs = new GlobalConfigs(cfg);\n\n        String senderName = configs.getString(TRACE_SENDER_NAME);\n        Assert.assertEquals(ZIPKIN_SENDER_NAME, senderName);\n        Assert.assertEquals(url, configs.getString(join(TRACE_SENDER, \"url\")));\n    }\n\n    @Test\n    public void test_console_target() {\n        HashMap<String, String> cfg = new HashMap<>();\n        String url = \"\";\n        cfg.put(\"observability.tracings.output.target\", \"zipkin\");\n        cfg.put(\"observability.tracings.output.target.zipkinUrl\", url);\n        GlobalConfigs configs = new GlobalConfigs(cfg);\n\n        Assert.assertEquals(CONSOLE_SENDER_NAME, configs.getString(TRACE_SENDER_NAME));\n\n        cfg.clear();\n        cfg.put(\"observability.tracings.output.target\", \"system\");\n        cfg.put(\"observability.tracings.output.topic\", \"log-tracing\");\n        configs = new GlobalConfigs(cfg);\n\n        Assert.assertEquals(CONSOLE_SENDER_NAME, configs.getString(TRACE_SENDER_NAME));\n    }\n\n    @Test\n    public void test_kafka_target() {\n        HashMap<String, String> cfg = new HashMap<>();\n        cfg.put(\"observability.tracings.output.target\", \"system\");\n        cfg.put(\"observability.tracings.output.topic\", \"log-tracing\");\n        cfg.put(\"observability.outputServer.bootstrapServer\", \"127.0.0.1:9092\");\n\n        GlobalConfigs configs = new GlobalConfigs(cfg);\n\n        Assert.assertEquals(KAFKA_SENDER_NAME, configs.getString(TRACE_SENDER_NAME));\n    }\n\n    @Test\n    public void test_trace_output() {\n        /*\n         * observability.tracings.output.messageMaxBytes=999900\n         * observability.tracings.output.reportThread=1\n         * observability.tracings.output.queuedMaxSpans=1000\n         * observability.tracings.output.queuedMaxSize=1000000\n         * observability.tracings.output.messageTimeout=1000\n         */\n        HashMap<String, String> cfg = new HashMap<>();\n        cfg.put(\"observability.tracings.output.messageMaxBytes\", \"123\");\n        cfg.put(\"observability.tracings.output.reportThread\", \"2\");\n\n        GlobalConfigs configs = new GlobalConfigs(cfg);\n        long m = configs.getLong(TRACE_ASYNC_MESSAGE_MAX_BYTES_V2);\n        Assert.assertEquals(123L, m);\n        m = configs.getLong(TRACE_ASYNC_REPORT_THREAD_V2);\n        Assert.assertEquals(2L, m);\n\n        // override by v2\n        cfg.clear();\n        cfg.put(\"observability.tracings.output.queuedMaxSpans\", \"123\");\n        cfg.put(TRACE_ASYNC_QUEUED_MAX_SPANS_V2, \"1000\");\n\n        configs = new GlobalConfigs(cfg);\n        m = configs.getLong(TRACE_ASYNC_QUEUED_MAX_SPANS_V2);\n        Assert.assertEquals(1000L, m);\n    }\n\n    @Test\n    public void test_metric_global_v1() {\n        /*\n         * plugin.observability.global.metric.interval=30\n         * plugin.observability.global.metric.topic=application-meter\n         * plugin.observability.global.metric.appendType=kafka\n         */\n        HashMap<String, String> cfg = new HashMap<>();\n        cfg.put(\"plugin.observability.global.metric.interval\", \"30\");\n        cfg.put(\"plugin.observability.global.metric.topic\", \"application-meter\");\n        cfg.put(\"plugin.observability.global.metric.appendType\", \"kafka\");\n\n        GlobalConfigs configs = new GlobalConfigs(cfg);\n        Assert.assertEquals(30L, (long)NoNull.of(configs.getLong(METRIC_ASYNC_INTERVAL), 0L));\n        Assert.assertEquals(\"application-meter\", configs.getString(METRIC_SENDER_TOPIC));\n        Assert.assertEquals(METRIC_KAFKA_SENDER_NAME, configs.getString(METRIC_SENDER_NAME));\n    }\n\n    @Test\n    public void test_metric_async_update() {\n        HashMap<String, String> cfg = new HashMap<>();\n        cfg.put(\"plugin.observability.global.metric.output.messageMaxBytes\", \"100\");\n        cfg.put(\"plugin.observability.access.metric.output.interval\", \"100\");\n        cfg.put(METRIC_ASYNC_MESSAGE_MAX_BYTES, \"1000\");\n        cfg.put(METRIC_ASYNC_INTERVAL, \"200\");\n\n        GlobalConfigs configs = new GlobalConfigs(cfg);\n        Assert.assertEquals(\"1000\", configs.getString(METRIC_ASYNC_MESSAGE_MAX_BYTES));\n        Assert.assertEquals(\"1000\", configs.getString(join(METRIC_V2, \"access\", ASYNC_KEY, ASYNC_MSG_MAX_BYTES_KEY)));\n        Assert.assertEquals(\"100\", configs.getString(join(METRIC_V2, \"access\", ASYNC_KEY, INTERVAL_KEY)));\n\n        HashMap<String, String> changes = new HashMap<>();\n        changes.put(METRIC_ASYNC_MESSAGE_MAX_BYTES, \"2000\");\n        changes.put(METRIC_ASYNC_INTERVAL, \"150\");\n        configs.updateConfigs(changes);\n        Assert.assertEquals(\"2000\", configs.getString(join(METRIC_V2, \"access\", ASYNC_KEY, ASYNC_MSG_MAX_BYTES_KEY)));\n        Assert.assertEquals(\"2000\", configs.getString(METRIC_ASYNC_MESSAGE_MAX_BYTES));\n        Assert.assertEquals(\"100\", configs.getString(join(METRIC_V2, \"access\", ASYNC_KEY, INTERVAL_KEY)));\n    }\n\n    @Test\n    public void test_log_global() {\n        HashMap<String, String> cfg = new HashMap<>();\n        cfg.put(\"plugin.observability.global.log.topic\", \"application-log\");\n        cfg.put(\"plugin.observability.global.log.url\", \"/application-log\");\n        cfg.put(\"plugin.observability.global.log.appendType\", \"kafka\");\n        cfg.put(\"plugin.observability.global.log.output.messageMaxBytes\", \"100\");\n\n        GlobalConfigs configs = new GlobalConfigs(cfg);\n        Assert.assertEquals(\"application-log\", configs.getString(LOG_SENDER_TOPIC));\n        Assert.assertEquals(KAFKA_SENDER_NAME, configs.getString(LOG_SENDER_NAME));\n        Assert.assertEquals(\"100\", configs.getString(LOG_ASYNC_MESSAGE_MAX_BYTES));\n    }\n\n    @Test\n    public void test_access_in_metric() {\n        HashMap<String, String> cfg = new HashMap<>();\n        cfg.put(\"plugin.observability.global.log.topic\", \"application-log\");\n        cfg.put(\"plugin.observability.global.log.url\", \"/application-log\");\n        cfg.put(\"plugin.observability.global.log.appendType\", \"kafka\");\n        cfg.put(\"plugin.observability.global.log.encoder\", \"APP_JSON\");\n        cfg.put(\"plugin.observability.global.log.output.messageMaxBytes\", \"100\");\n        cfg.put(\"plugin.observability.access.log.topic\", \"access-log\");\n        cfg.put(\"plugin.observability.access.log.encoder\", \"LOG_ACCESS_JSON\");\n        cfg.put(\"plugin.observability.access.metric.encoder\", \"ACCESS_JSON\");\n\n        GlobalConfigs configs = new GlobalConfigs(cfg);\n        Assert.assertEquals(\"application-log\", configs.getString(LOG_SENDER_TOPIC));\n        Assert.assertEquals(\"access-log\", configs.getString(LOG_ACCESS_SENDER_TOPIC));\n        Assert.assertEquals(\"ACCESS_JSON\", configs.getString(LOG_ACCESS_ENCODER));\n        Assert.assertEquals(KAFKA_SENDER_NAME, configs.getString(LOG_ACCESS_SENDER_NAME));\n        Assert.assertEquals(\"100\", configs.getString(LOG_ASYNC_MESSAGE_MAX_BYTES));\n    }\n\n    @Test\n    public void test_access_log_update() {\n        HashMap<String, String> cfg = new HashMap<>();\n        cfg.put(\"plugin.observability.global.log.topic\", \"application-log\");\n        cfg.put(\"plugin.observability.global.log.url\", \"/application-log\");\n        cfg.put(\"plugin.observability.global.log.appendType\", \"kafka\");\n        cfg.put(\"plugin.observability.global.log.encoder\", \"APP_JSON\");\n        cfg.put(\"plugin.observability.global.log.output.messageMaxBytes\", \"100\");\n        cfg.put(\"plugin.observability.access.log.topic\", \"access-log\");\n        cfg.put(\"plugin.observability.access.log.encoder\", \"LOG_ACCESS_JSON\");\n\n        GlobalConfigs configs = new GlobalConfigs(cfg);\n        Assert.assertEquals(\"application-log\", configs.getString(LOG_SENDER_TOPIC));\n        Assert.assertEquals(\"access-log\", configs.getString(LOG_ACCESS_SENDER_TOPIC));\n        Assert.assertEquals(\"LOG_ACCESS_JSON\", configs.getString(LOG_ACCESS_ENCODER));\n        Assert.assertEquals(KAFKA_SENDER_NAME, configs.getString(LOG_ACCESS_SENDER_NAME));\n        Assert.assertEquals(\"100\", configs.getString(LOG_ASYNC_MESSAGE_MAX_BYTES));\n\n        // update\n        HashMap<String, String> changes = new HashMap<>();\n        changes.put(\"plugin.observability.access.log.appendType\", CONSOLE_SENDER_NAME);\n        configs.updateConfigs(changes);\n        Assert.assertEquals(CONSOLE_SENDER_NAME, configs.getString(LOG_ACCESS_SENDER_NAME));\n\n        changes.put(\"plugin.observability.access.metric.appendType\", KAFKA_SENDER_NAME);\n        changes.put(\"plugin.observability.access.log.appendType\", CONSOLE_SENDER_NAME);\n        configs.updateConfigs(changes);\n        Assert.assertEquals(KAFKA_SENDER_NAME, configs.getString(LOG_ACCESS_SENDER_NAME));\n    }\n\n    @Test\n    public void test_log_global_async() {\n        HashMap<String, String> cfg = new HashMap<>();\n        cfg.put(\"plugin.observability.global.log.topic\", \"application-log\");\n        cfg.put(\"plugin.observability.global.log.url\", \"/application-log\");\n        cfg.put(\"plugin.observability.global.log.appendType\", \"kafka\");\n        cfg.put(\"plugin.observability.global.log.output.messageMaxBytes\", \"100\");\n        cfg.put(\"plugin.observability.access.log.topic\", \"access-log\");\n        cfg.put(\"plugin.observability.access.log.encoder\", \"LOG_ACCESS_JSON\");\n\n        cfg.put(LOG_ASYNC_MESSAGE_MAX_BYTES, \"1000\");\n        cfg.put(LOG_ASYNC_QUEUED_MAX_LOGS, \"200\");\n\n        GlobalConfigs configs = new GlobalConfigs(cfg);\n        Assert.assertEquals(\"application-log\", configs.getString(LOG_SENDER_TOPIC));\n        Assert.assertEquals(\"access-log\", configs.getString(LOG_ACCESS_SENDER_TOPIC));\n        Assert.assertEquals(\"LOG_ACCESS_JSON\", configs.getString(LOG_ACCESS_ENCODER));\n        Assert.assertEquals(KAFKA_SENDER_NAME, configs.getString(LOG_ACCESS_SENDER_NAME));\n\n        Assert.assertEquals(\"1000\", configs.getString(LOG_ASYNC_MESSAGE_MAX_BYTES));\n        Assert.assertEquals(\"1000\", configs.getString(join(LOG_ACCESS, ASYNC_KEY, ASYNC_MSG_MAX_BYTES_KEY)));\n    }\n\n    @Test\n    public void test_log_async_update() {\n        HashMap<String, String> cfg = new HashMap<>();\n        cfg.put(\"plugin.observability.global.log.output.messageMaxBytes\", \"100\");\n        cfg.put(\"plugin.observability.access.log.output.queuedMaxLogs\", \"100\");\n        cfg.put(LOG_ASYNC_MESSAGE_MAX_BYTES, \"1000\");\n        cfg.put(LOG_ASYNC_QUEUED_MAX_LOGS, \"200\");\n\n        GlobalConfigs configs = new GlobalConfigs(cfg);\n        Assert.assertEquals(\"1000\", configs.getString(LOG_ASYNC_MESSAGE_MAX_BYTES));\n        Assert.assertEquals(\"1000\", configs.getString(join(LOG_ACCESS, ASYNC_KEY, ASYNC_MSG_MAX_BYTES_KEY)));\n        Assert.assertEquals(\"100\", configs.getString(join(LOG_ACCESS, ASYNC_KEY, ASYNC_QUEUE_MAX_LOGS_KEY)));\n\n        HashMap<String, String> changes = new HashMap<>();\n        changes.put(LOG_ASYNC_MESSAGE_MAX_BYTES, \"2000\");\n        changes.put(LOG_ASYNC_QUEUED_MAX_LOGS, \"150\");\n        configs.updateConfigs(changes);\n        Assert.assertEquals(\"2000\", configs.getString(join(LOG_ACCESS, ASYNC_KEY, ASYNC_MSG_MAX_BYTES_KEY)));\n        Assert.assertEquals(\"2000\", configs.getString(LOG_ASYNC_MESSAGE_MAX_BYTES));\n        Assert.assertEquals(\"100\", configs.getString(join(LOG_ACCESS, ASYNC_KEY, ASYNC_QUEUE_MAX_LOGS_KEY)));\n    }\n}\n"
  },
  {
    "path": "config/src/test/java/com/megaease/easeagent/config/yaml/YamlReaderTest.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.config.yaml;\n\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Properties;\n\npublic class YamlReaderTest {\n\n    private static final String CONFIG_FILE_YAML = \"agent.yaml\";\n    private static final String CONFIG_FILE_PROPERTIES = \"agent.properties\";\n\n    @Test\n    public void testCompress() {\n        InputStream inputYaml = Thread.currentThread().getContextClassLoader().getResourceAsStream(CONFIG_FILE_YAML);\n        Map<String, String> yamlMap = new YamlReader().load(inputYaml).compress();\n\n        InputStream inputProperties = YamlReaderTest.class.getClassLoader().getResourceAsStream(CONFIG_FILE_PROPERTIES);\n        Map<String, String> yamlProperties = extractPropsMap(inputProperties);\n\n        yamlMap.forEach((k, v) -> {\n            Assert.assertTrue(yamlProperties.containsKey(k));\n            if (!k.equals(\"name\")) {\n                Assert.assertEquals(v, yamlProperties.get(k));\n            }\n        });\n\n        yamlProperties.forEach((k, v) -> {\n            Assert.assertTrue(yamlMap.containsKey(k));\n            if (!k.equals(\"name\")) {\n                Assert.assertEquals(v, yamlMap.get(k));\n            }\n        });\n    }\n\n    private static Map<String, String> extractPropsMap(InputStream in) {\n        Properties properties = new Properties();\n        try {\n            properties.load(in);\n            HashMap<String, String> map = new HashMap<>();\n            for (String one : properties.stringPropertyNames()) {\n                map.put(one, properties.getProperty(one));\n            }\n            return map;\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n        return Collections.emptyMap();\n    }\n\n}\n"
  },
  {
    "path": "config/src/test/resources/agent.properties",
    "content": "name=demo-service\nsystem=demo-system\n\nuser.list=a,b\n\n### http server\n# When the enabled value = false, agent will not start the http server\n# You can use -Deaseagent.server.enabled=[true | false] to override.\neaseagent.server.enabled=true\n# http server port. You can use -Deaseagent.server.port=[port] to override.\neaseagent.server.port=9900\n# Enable health/readiness\neaseagent.health.readiness.enabled=true\n# forwarded headers page\n# Pass-through headers from the root process all the way to the end\n# format: easeagent.progress.forwarded.headers={headerName}\n#easeagent.progress.forwarded.headers=X-Forwarded-For\n#easeagent.progress.forwarded.headers=X-Location,X-Mesh-Service-Canary,X-Phone-Os\n###\n### default tracings reporter configuration\n###\n# sampledType:\n## counting: percentage sampling, sampled limit 0.01 to 1, 1 is always sample, 0 is never sample, 0.1 is ten samples per hundred\n## rate_limiting: traces per second, sampled >= 0, 0 is never sample, 10 is max 10 traces per second\n## boundary: percentage sampling by traceId, sampled limit 0.0001 to 1, 1 is always sample, 0 is never sample\n##           if sampled=0.001, when (traceId^random)%10000<=(0.001*10000) sampled\n## sampledType must be used with sampled, otherwise the default value is used Sampler.ALWAYS_SAMPLE\nobservability.tracings.sampledType=\nobservability.tracings.sampled=1\n# get header from response headers then tag to tracing span\n# format: observability.tracings.tag.response.headers.{key}={value}\n# support ease mesh\n# X-EG-Circuit-Breaker\n# X-EG-Retryer\n# X-EG-Rate-Limiter\n# X-EG-Time-Limiter\nobservability.tracings.tag.response.headers.eg.0=X-EG-Circuit-Breaker\nobservability.tracings.tag.response.headers.eg.1=X-EG-Retryer\nobservability.tracings.tag.response.headers.eg.2=X-EG-Rate-Limiter\nobservability.tracings.tag.response.headers.eg.3=X-EG-Time-Limiter\n\n# -------------------- plugin global config ---------------------\nplugin.observability.global.tracing.enabled=true\nplugin.observability.global.metric.enabled=true\nplugin.observability.global.metric.interval=30\nplugin.observability.global.metric.topic=application-meter\n# plugin.observability.global.metric.appendType=console\n## output by http\n#plugin.observability.global.metric.appendType=http\n# add service name to header enabled by name for easemesh\nplugin.integrability.global.addServiceNameHead.enabled=true\n# redirect the middleware address when env has address, see: com.megaease.easeagent.plugin.api.middleware.RedirectProcessor\n# about redirect: jdbc, kafka, rabbitmq, redis,\nplugin.integrability.global.redirect.enabled=true\n# forwarded headers enabled.\n# headers see config: easeagent.progress.forwarded.headers.???=???\nplugin.integrability.global.forwarded.enabled=true\nplugin.hook.global.foundation.enabled=true\n\n\n# ----------------------------------------------\n# if the plugin configuration is consistent with the global namespace,\n# do not add configuration items not commented out in this default configuration file.\n# otherwise, they can not be overridden by Global configuration in user's configuration file.\n\n#\n# -------------------- jvm  ---------------------\n# plugin.observability.jvmGc.metric.enabled=true\n# plugin.observability.jvmGc.metric.interval=30\nplugin.observability.jvmGc.metric.topic=platform-metrics\nplugin.observability.jvmGc.metric.url=/platform-metrics\n# plugin.observability.jvmGc.metric.appendType=kafka\n# plugin.observability.jvmMemory.metric.enabled=true\n# plugin.observability.jvmMemory.metric.interval=30\nplugin.observability.jvmMemory.metric.topic=platform-metrics\nplugin.observability.jvmMemory.metric.url=/platform-metrics\n# plugin.observability.jvmMemory.metric.appendType=kafka\n#\n# -------------------- async ---------------------\n# plugin.observability.async.tracing.enabled=true\n#\n# -------------------- elasticsearch redirect ---------------------\n# plugin.integrability.elasticsearch.redirect.enabled=true\n# plugin.observability.elasticsearch.tracing.enabled=true\n# elasticsearch metric\n# plugin.observability.elasticsearch.metric.enabled=true\n# plugin.observability.elasticsearch.metric.interval=30\nplugin.observability.elasticsearch.metric.topic=platform-metrics\nplugin.observability.elasticsearch.metric.url=/platform-metrics\n# plugin.observability.elasticsearch.metric.appendType=kafka\n#\n# -------------------- httpServlet ---------------------\n# plugin.observability.httpServlet.tracing.enabled=true\n# plugin.observability.httpServlet.metric.enabled=true\n# plugin.observability.httpServlet.metric.interval=30\nplugin.observability.httpServlet.metric.topic=application-metrics\nplugin.observability.httpServlet.metric.url=/application-metrics\n# plugin.observability.httpServlet.metric.appendType=kafka\n#\n# -------------------- jdbc ---------------------\n## jdbc tracing\n# plugin.observability.jdbc.tracing.enabled=true\n# jdbcStatement metric\n# plugin.observability.jdbcStatement.metric.enabled=true\n# plugin.observability.jdbcStatement.metric.interval=30\nplugin.observability.jdbcStatement.metric.topic=application-metrics\nplugin.observability.jdbcStatement.metric.url=/application-metrics\n# plugin.observability.jdbcStatement.metric.appendType=kafka\n## jdbcConnection metric\n# plugin.observability.jdbcConnection.metric.enabled=true\n# plugin.observability.jdbcConnection.metric.interval=30\nplugin.observability.jdbcConnection.metric.topic=application-metrics\nplugin.observability.jdbcConnection.metric.url=/application-metrics\n# plugin.observability.jdbcConnection.metric.appendType=kafka\n## sql compress\n## compress.enabled=true, can use md5Dictionary to compress\n## compress.enabled=false, use original sql\nplugin.observability.jdbc.sql.compress.enabled=true\n\n## md5Dictionary metric\n# plugin.observability.md5Dictionary.metric.enabled=true\n# plugin.observability.md5Dictionary.metric.interval=30\nplugin.observability.md5Dictionary.metric.topic=application-metrics\nplugin.observability.md5Dictionary.metric.url=/application-metrics\n# plugin.observability.md5Dictionary.metric.appendType=kafka\n## jdbc redirect\n# plugin.integrability.jdbc.redirect.enabled=true\n#\n# -------------------- kafka ---------------------\n# kafka tracing\n# plugin.observability.kafka.tracing.enabled=true\n# kafka metric\n# plugin.observability.kafka.metric.enabled=true\n# plugin.observability.kafka.metric.interval=30\nplugin.observability.kafka.metric.topic=platform-metrics\nplugin.observability.kafka.metric.url=/platform-metrics\n# plugin.observability.kafka.metric.appendType=kafka\n# kafka redirect\n# plugin.integrability.kafka.redirect.enabled=true\n#\n# -------------------- rabbitmq ---------------------\n# rabbitmq tracing\n# plugin.observability.rabbitmq.tracing.enabled=true\n# rabbitmq metric\n# plugin.observability.rabbitmq.metric.enabled=true\n# plugin.observability.rabbitmq.metric.interval=30\nplugin.observability.rabbitmq.metric.topic=platform-metrics\nplugin.observability.rabbitmq.metric.url=/platform-metrics\n# plugin.observability.rabbitmq.metric.appendType=kafka\n# rabbitmq redirect\n# plugin.integrability.rabbitmq.redirect.enabled=true\n#\n# -------------------- redis ---------------------\n# redis tracing\n# plugin.observability.redis.tracing.enabled=true\n# redis metric\n# plugin.observability.redis.metric.enabled=true\n# plugin.observability.redis.metric.interval=30\nplugin.observability.redis.metric.topic=platform-metrics\nplugin.observability.redis.metric.url=/platform-metrics\n# plugin.observability.redis.metric.appendType=kafka\n# redis redirect\n# plugin.integrability.redis.redirect.enabled=true\n#\n# -------------------- springGateway ---------------------\n# springGateway tracing\n# plugin.observability.springGateway.tracing.enabled=true\n# springGateway metric\n# plugin.observability.springGateway.metric.enabled=true\n# plugin.observability.springGateway.metric.interval=30\nplugin.observability.springGateway.metric.topic=application-metrics\nplugin.observability.springGateway.metric.url=/application-metrics\n# plugin.observability.springGateway.metric.appendType=kafka\n#\n# -------------------- request ---------------------\n## httpclient tracing：httpclient and httpclient5\n# plugin.observability.httpclient.tracing.enabled=true\n## okHttp tracing\n# plugin.observability.okHttp.tracing.enabled=true\n## webclient tracing\n# plugin.observability.webclient.tracing.enabled=true\n## feignClient tracing\n# plugin.observability.feignClient.tracing.enabled=true\n## restTemplate tracing\n# plugin.observability.restTemplate.tracing.enabled=true\n#\n# -------------------- access ---------------------\n## access: servlet and spring gateway\n# plugin.observability.access.metric.enabled=true\n# plugin.observability.access.metric.interval=30\nplugin.observability.access.metric.topic=application-log\nplugin.observability.access.metric.url=/application-log\n# plugin.observability.access.metric.appendType=kafka\n#\n# -------------------- service name ---------------------\n## add service name to header by name for easemesh. default name: X-Mesh-RPC-Service\n# plugin.integrability.serviceName.addServiceNameHead.propagate.head=X-Mesh-RPC-Service\n#\n# -------------------- mongodb ---------------------\n## mongodb tracing\n# plugin.observability.mongodb.tracing.enabled=true\n## mongodb metric\n# plugin.observability.mongodb.metric.enabled=true\n# plugin.observability.mongodb.metric.interval=30\nplugin.observability.mongodb.metric.topic=platform-metrics\nplugin.observability.mongodb.metric.url=/platform-metrics\n# plugin.observability.mongodb.metric.appendType=kafka\n## mongodb redirect\n# plugin.integrability.mongodb.redirect.enabled=true\n## mongodb foundation\n# plugin.hook.mongodb.foundation.enabled=true\n\n# -------------- output ------------------\n## http/kafka/zipkin server host and port for tracing and metric\n###### example ######\n## http: [http|https]://127.0.0.1:8080/report\n## kafka: 192.168.1.2:9092, 192.168.1.3:9092, 192.168.1.3:9092\n## zipkin: [http|https]://127.0.0.1:8080/zipkin\n\nreporter.outputServer.bootstrapServer=127.0.0.1:9092\nreporter.outputServer.appendType=console\nreporter.outputServer.timeout=1000\n\n## enabled=false: disable output tracing and metric\n## enabled=true: output tracing and metric\nreporter.outputServer.enabled=true\n\n## username and password for http basic auth\nreporter.outputServer.username=\nreporter.outputServer.password=\n## enable=false: disable mtls\n## enable=true: enable tls\n## key, cert, ca_cert is enabled when tls.enable=true\nreporter.outputServer.tls.enable=false\nreporter.outputServer.tls.key=\nreporter.outputServer.tls.cert=\nreporter.outputServer.tls.ca_cert=\n\n\n# --- redefine to output properties\nreporter.log.output.messageMaxBytes=999900\nreporter.log.output.reportThread=1\nreporter.log.output.queuedMaxSpans=1000\nreporter.log.output.queuedMaxSize=1000000\nreporter.log.output.messageTimeout=1000\n\n## sender.appendType config\n## [http] send to http server\n## [kafka] send to kafka\n## [console] send to console\n## reporter.log.sender.appendType=console\n## enabled=true:\n# reporter.log.sender.enabled=true\n# reporter.log.sender.url=/application-log\n\n\n## sender.appendType config\n## [http] send to http server\n## [kafka] send to kafka\n## [console] send to console\n# reporter.tracing.sender.appendType=http\n# reporter.tracing.sender.appendType=console\n\n## enabled=true:\nreporter.tracing.sender.enabled=true\n\n## url is only used in http\n## append to outputServer.bootstrapServer\n###### example ######\n## reporter.outputServer.bootstrapServer=http://127.0.0.1:8080/report\n## reporter.tracing.sender.url=/tracing\n## final output url: http://127.0.0.1:8080/report/tracing\n## if url is start with [http|https], url override reporter.outputServer.bootstrapServer\n###### example ######\n## reporter.outputServer.bootstrapServer=http://127.0.0.1:8080/report\n## reporter.tracing.sender.url=http://127.0.0.10:9090/tracing\n## final output url: http://127.0.0.10:9090/tracing\nreporter.tracing.sender.url=/application-tracing-log\n\n## topic for kafka use\nreporter.tracing.sender.topic=log-tracing\n\nreporter.tracing.encoder=SpanJsonEncoder\n\n# --- redefine to output properties\nreporter.tracing.output.messageMaxBytes=999900\nreporter.tracing.output.reportThread=1\nreporter.tracing.output.queuedMaxSpans=1000\nreporter.tracing.output.queuedMaxSize=1000000\nreporter.tracing.output.messageTimeout=1000\n\n## sender.appendType config\n## [http] send to http server\n## [metricKafka] send to kafka\n## [console] send to console\n#reporter.metric.sender.appendType=http\n#reporter.metric.sender.appendType=console\n\n## url is only used in http\n## append to outputServer.bootstrapServer\n###### example ######\n## reporter.outputServer.bootstrapServer=http://127.0.0.1:8080/report\n## reporter.metric.sender.url=/metric\n## final output url: http://127.0.0.1:8080/report/metric\n## if url is start with [http|https], url override reporter.outputServer.bootstrapServer\n###### example ######\n## reporter.outputServer.bootstrapServer=http://127.0.0.1:8080/report\n## reporter.metric.sender.url=http://127.0.0.10:9090/metric\n## final output url: http://127.0.0.10:9090/metric\n#reporter.metric.sender.url=/metrics\n\n## topic for kafka use\nreporter.metric.sender.topic=application-meter\n\n#reporter.metric.encoder=MetricJsonEncoder\nreporter.metric.output.interval=30\n"
  },
  {
    "path": "config/src/test/resources/agent.yaml",
    "content": "name: test-service\nsystem: demo-system\n\nuser:\n    list:\n        - \"a\"\n        - \"b\"\n\n### http server\neaseagent:\n    server:\n        # When the enabled value = false, agent will not start the http server\n        # You can use -Deaseagent.server.enabled=[true | false] to override.\n        enabled: true\n        # http server port. You can use -Deaseagent.server.port=[port] to override.\n        port: 9900\n    health:\n        readiness:\n            # Enable health/readiness\n            enabled: true\n#  progress:\n#    forwarded:\n#      headers: X-Forwarded-For\n#      headers: X-Location,X-Mesh-Service-Canary,X-Phone-Os\n\n###\n### default tracings reporter configuration\n###\n# sampledType:\n## counting: percentage sampling, sampled limit 0.01 to 1, 1 is always sample, 0 is never sample, 0.1 is ten samples per hundred\n## rate_limiting: traces per second, sampled >= 0, 0 is never sample, 10 is max 10 traces per second\n## boundary: percentage sampling by traceId, sampled limit 0.0001 to 1, 1 is always sample, 0 is never sample\n##           if sampled=0.001, when (traceId^random)%10000<=(0.001*10000) sampled\n## sampledType must be used with sampled, otherwise the default value is used Sampler.ALWAYS_SAMPLE\n#\n# get header from response headers then tag to tracing span\n# format: observability.tracings.tag.response.headers.{key}={value}\n# support ease mesh\n# X-EG-Circuit-Breaker\n# X-EG-Retryer\n# X-EG-Rate-Limiter\n# X-EG-Time-Limiter\nobservability:\n    tracings:\n        sampledType: \"\"\n        sampled: 1\n        tag:\n            response:\n                headers:\n                    eg:\n                        0: X-EG-Circuit-Breaker\n                        1: X-EG-Retryer\n                        2: X-EG-Rate-Limiter\n                        3: X-EG-Time-Limiter\n\nplugin:\n    hook:\n        global:\n            foundation:\n                enabled: true\n    integrability:\n        global:\n            # add service name to header enabled by name for easemesh\n            addServiceNameHead:\n                enabled: true\n            # forwarded headers enabled.\n            # headers see config: easeagent.progress.forwarded.headers.???=???\n            forwarded:\n                enabled: true\n            # redirect the middleware address when env has address, see: com.megaease.easeagent.plugin.api.middleware.RedirectProcessor\n            # about redirect: jdbc, kafka, rabbitmq, redis,\n            redirect:\n                enabled: true\n        # -------------------- service name ---------------------\n        ## add service name to header by name for easemesh. default name: X-Mesh-RPC-Service\n    #    serviceName:\n    #      addServiceNameHead:\n    #        propagate:\n    #          head: X-Mesh-RPC-Service\n    observability:\n        # -------------------- async ---------------------\n        #    async:\n        #      tracing:\n        #        enabled: true\n        # -------------------- access ---------------------\n        access:\n            metric:\n                #        enabled: true\n                #        interval: 30\n                topic: application-log\n                url: /application-log\n        #        appendType: kafka\n        # -------------------- elasticsearch redirect ---------------------\n        elasticsearch:\n            metric:\n                #        enabled: true\n                #        interval: 30\n                topic: platform-metrics\n                url: /platform-metrics\n        #        appendType: kafka\n        # -------------------- plugin global config ---------------------\n        global:\n            metric:\n                enabled: true\n                interval: 30\n                topic: application-meter\n                ## output by http\n            #        appendType: console\n            #        appendType: http\n            tracing:\n                enabled: true\n        # -------------------- httpServlet ---------------------\n        httpServlet:\n            metric:\n                #        enabled: true\n                #        interval: 30\n                topic: application-metrics\n                url: /application-metrics\n        #        appendType: kafka\n        #      tracing:\n        #        enabled: true\n        # -------------------- request ---------------------\n        ## httpclient tracing, httpclient and httpclient5\n        #    httpclient:\n        #      tracing:\n        #        enabled: true\n        ## okHttp tracing\n        #    okHttp:\n        #      tracing:\n        #        enabled: true\n        ## webclient tracing\n        #    webclient:\n        #      tracing:\n        #        enabled: true\n        ## feignClient tracing\n        #    feignClient:\n        #      tracing:\n        #        enabled: true\n        ## restTemplate tracing\n        #    restTemplate:\n        #      tracing:\n        #        enabled: true\n        # -------------------- jdbc ---------------------\n        jdbc:\n            #      tracing:\n            #        enabled: true\n            ## sql compress\n            ## compress.enabled=true, can use md5Dictionary to compress\n            ## compress.enabled=false, use original sql\n            sql:\n                compress:\n                    enabled: true\n            ## jdbc redirect\n        #      redirect:\n        #        enabled: true\n        jdbcConnection:\n            metric:\n                #        enabled: true\n                #        interval: 30\n                topic: application-metrics\n                url: /application-metrics\n        #        appendType: kafka\n        jdbcStatement:\n            metric:\n                #        enabled: true\n                #        interval: 30\n                topic: application-metrics\n                url: /application-metrics\n        #        appendType: kafka\n        # ----------------------------------------------\n        # if the plugin configuration is consistent with the global namespace,\n        # do not add configuration items not commented out in this default configuration file.\n        # otherwise, they can not be overridden by Global configuration in user's configuration file.\n        # -------------------- jvm  ---------------------\n        jvmGc:\n            metric:\n                #        enabled: true\n                #        interval: 30\n                topic: platform-metrics\n                url: /platform-metrics\n        #        appendType: kafka\n        jvmMemory:\n            metric:\n                #        enabled: true\n                #        interval: 30\n                topic: platform-metrics\n                url: /platform-metrics\n        #        appendType: kafka\n        # -------------------- kafka ---------------------\n        kafka:\n            # kafka tracing\n            #      tracing:\n            #        enabled: true\n            # kafka metric\n            metric:\n                #        enabled: true\n                #        interval: 30\n                topic: platform-metrics\n                url: /platform-metrics\n            #        appendType: kafka\n            # kafka redirect\n        #      redirect:\n        #        enabled: true\n        ## md5Dictionary metric\n        md5Dictionary:\n            metric:\n                #        enabled: true\n                #        interval: 30\n                topic: application-metrics\n                url: /application-metrics\n        #        appendType: kafka\n        # -------------------- mongodb ---------------------\n        mongodb:\n            ## mongodb tracing\n            #      tracing:\n            #        enabled: true\n            ## mongodb metric\n            metric:\n                #        enabled: true\n                #        interval: 30\n                topic: platform-metrics\n                url: /platform-metrics\n            #        appendType: kafka\n            ## mongodb redirect\n            #      redirect:\n            #        enabled: true\n            ## mongodb foundation\n        #      foundation:\n        #        enabled: true\n        # -------------------- rabbitmq ---------------------\n        rabbitmq:\n            # rabbitmq tracing\n            #      tracing:\n            #        enabled: true\n            # rabbitmq metric\n            metric:\n                #        enabled: true\n                #        interval: 30\n                topic: platform-metrics\n                url: /platform-metrics\n            #        appendType: kafka\n            # rabbitmq redirect\n        #      redirect:\n        #        enabled: true\n        # -------------------- redis ---------------------\n        redis:\n            # redis tracing\n            #      tracing:\n            #        enabled: true\n            # redis metric\n            metric:\n                #        enabled: true\n                #        interval: 30\n                topic: platform-metrics\n                url: /platform-metrics\n            # redis redirect\n        #      redirect:\n        #        enabled: true\n        # -------------------- springGateway ---------------------\n        springGateway:\n            # springGateway tracing\n            #      tracing:\n            #        enabled: true\n            metric:\n                #        enabled: true\n                #        interval: 30\n                topic: application-metrics\n                url: /application-metrics\n#        appendType: kafka\n\n# -------------- output ------------------\n## http/kafka/zipkin server host and port for tracing and metric\n###### example ######\n## http: [http|https]://127.0.0.1:8080/report\n## kafka: 192.168.1.2:9092, 192.168.1.3:9092, 192.168.1.3:9092\n## zipkin: [http|https]://127.0.0.1:8080/zipkin\n\nreporter:\n    outputServer:\n        appendType: console\n        bootstrapServer: 127.0.0.1:9092\n        ## enabled=false: disable output tracing and metric\n        ## enabled=true: output tracing and metric\n        enabled: true\n        ## username and password for http basic auth\n        username: ''\n        password: ''\n        timeout: 1000\n        ## enable=false: disable mtls\n        ## enable=true: enable tls\n        ## key, cert, ca_cert is enabled when tls.enable=true\n        tls:\n            enable: false\n            key: ''\n            cert: ''\n            ca_cert: ''\n    # --- redefine to output properties\n    log:\n        output:\n            messageMaxBytes: 999900\n            messageTimeout: 1000\n            queuedMaxSize: 1000000\n            queuedMaxSpans: 1000\n            reportThread: 1\n        ## sender.appendType config\n        ## [http] send to http server\n        ## [kafka] send to kafka\n        ## [console] send to console\n    #    sender:\n    #      enabled: true\n    #      url: /application-log\n    #      appendType: console\n    metric:\n        ## reporter.metric.encoder=MetricJsonEncoder\n        output:\n            interval: 30\n        ## topic for kafka use\n        sender:\n            topic: application-meter\n    tracing:\n        encoder: SpanJsonEncoder\n        # --- redefine to output properties\n        output:\n            messageMaxBytes: 999900\n            messageTimeout: 1000\n            queuedMaxSize: 1000000\n            queuedMaxSpans: 1000\n            reportThread: 1\n        ## sender.appendType config\n        ## [http] send to http server\n        ## [kafka] send to kafka\n        ## [console] send to console\n        sender:\n            enabled: true\n            ## topic for kafka use\n            topic: log-tracing\n            url: /application-tracing-log\n#      appendType: http\n#      appendType: console\n"
  },
  {
    "path": "config/src/test/resources/user-spec.properties",
    "content": "name=user-spec\nsystem=system-spec\n"
  },
  {
    "path": "config/src/test/resources/user-spec2.properties",
    "content": "name=user-spec2\nsystem=system-spec2\n"
  },
  {
    "path": "config/src/test/resources/user.properties",
    "content": "name=demo-user\nsystem=demo-system\n\n## topic for kafka use\nreporter.metric.sender.topic=application-meter\n\n#reporter.metric.encoder=MetricJsonEncoder\nreporter.metric.output.interval=30\n"
  },
  {
    "path": "context/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright (c) 2021, MegaEase\n  ~ All rights reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~   http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>easeagent</artifactId>\n        <groupId>com.megaease.easeagent</groupId>\n        <version>2.3.0</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>context</artifactId>\n\n\n    <dependencies>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>plugin-api</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>config</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>log4j2-api</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>log4j2-mock</artifactId>\n            <version>${project.version}</version>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>config-mock</artifactId>\n            <version>${project.version}</version>\n            <scope>test</scope>\n        </dependency>\n    </dependencies>\n</project>\n"
  },
  {
    "path": "context/src/main/java/com/megaease/easeagent/context/AsyncContextImpl.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.context;\n\nimport com.megaease.easeagent.plugin.api.Cleaner;\nimport com.megaease.easeagent.plugin.api.InitializeContext;\nimport com.megaease.easeagent.plugin.api.context.AsyncContext;\nimport com.megaease.easeagent.plugin.api.trace.SpanContext;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.function.Supplier;\n\npublic class AsyncContextImpl implements AsyncContext {\n    private final SpanContext spanContext;\n    private final Map<Object, Object> context;\n    private final Supplier<InitializeContext> supplier;\n\n    private AsyncContextImpl(SpanContext spanContext, Map<Object, Object> context, Supplier<InitializeContext> supplier) {\n        this.spanContext = Objects.requireNonNull(spanContext, \"spanContext must not be null\");\n        this.context = Objects.requireNonNull(context, \"context must not be null\");\n        this.supplier = Objects.requireNonNull(supplier, \"supplier must not be null\");\n    }\n\n    public static AsyncContextImpl build(SpanContext spanContext,\n                                         Supplier<InitializeContext> supplier,\n                                         Map<Object, Object> context) {\n        Map<Object, Object> contextMap = context == null ? new HashMap<>() : new HashMap<>(context);\n        return new AsyncContextImpl(spanContext, contextMap, supplier);\n    }\n\n    @Override\n    public boolean isNoop() {\n        return false;\n    }\n\n    @Override\n    public SpanContext getSpanContext() {\n        return spanContext;\n    }\n\n    @Override\n    public Cleaner importToCurrent() {\n        return supplier.get().importAsync(this);\n    }\n\n    @Override\n    public Map<Object, Object> getAll() {\n        return context;\n    }\n\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public <T> T get(Object o) {\n        return (T) this.context.get(o);\n    }\n\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public <V> V put(Object key, V value) {\n        return (V) this.context.put(key, value);\n    }\n}\n"
  },
  {
    "path": "context/src/main/java/com/megaease/easeagent/context/ContextManager.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.context;\n\nimport com.megaease.easeagent.config.Configs;\nimport com.megaease.easeagent.config.PluginConfigManager;\nimport com.megaease.easeagent.context.log.LoggerFactoryImpl;\nimport com.megaease.easeagent.context.log.LoggerMdc;\nimport com.megaease.easeagent.log4j2.Logger;\nimport com.megaease.easeagent.log4j2.LoggerFactory;\nimport com.megaease.easeagent.plugin.api.InitializeContext;\nimport com.megaease.easeagent.plugin.api.Reporter;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.context.IContextManager;\nimport com.megaease.easeagent.plugin.api.logging.ILoggerFactory;\nimport com.megaease.easeagent.plugin.api.logging.Mdc;\nimport com.megaease.easeagent.plugin.api.metric.MetricProvider;\nimport com.megaease.easeagent.plugin.api.metric.MetricRegistry;\nimport com.megaease.easeagent.plugin.api.metric.MetricRegistrySupplier;\nimport com.megaease.easeagent.plugin.api.metric.name.NameFactory;\nimport com.megaease.easeagent.plugin.api.metric.name.Tags;\nimport com.megaease.easeagent.plugin.api.trace.ITracing;\nimport com.megaease.easeagent.plugin.api.trace.TracingProvider;\nimport com.megaease.easeagent.plugin.api.trace.TracingSupplier;\nimport com.megaease.easeagent.plugin.bridge.*;\nimport com.megaease.easeagent.plugin.utils.NoNull;\n\nimport javax.annotation.Nonnull;\nimport java.util.function.Supplier;\n\npublic class ContextManager implements IContextManager {\n    private static final Logger LOGGER = LoggerFactory.getLogger(ContextManager.class.getName());\n    private static final ThreadLocal<SessionContext> LOCAL_SESSION_CONTEXT = ThreadLocal.withInitial(SessionContext::new);\n    private final PluginConfigManager pluginConfigManager;\n    private final Supplier<InitializeContext> sessionContextSupplier;\n    private final GlobalContext globalContext;\n    private volatile TracingSupplier tracingSupplier = (supplier) -> null;\n    private volatile MetricRegistrySupplier metric = NoOpMetrics.NO_OP_METRIC_SUPPLIER;\n\n\n    private ContextManager(@Nonnull Configs conf, @Nonnull PluginConfigManager pluginConfigManager, @Nonnull ILoggerFactory loggerFactory, @Nonnull Mdc mdc) {\n        this.pluginConfigManager = pluginConfigManager;\n        this.sessionContextSupplier = new SessionContextSupplier();\n        this.globalContext = new GlobalContext(conf, new MetricRegistrySupplierImpl(), loggerFactory, mdc);\n    }\n\n    public static ContextManager build(Configs conf) {\n        LOGGER.info(\"build context manager.\");\n        ProgressFieldsManager.init(conf);\n        PluginConfigManager pluginConfigManager = PluginConfigManager.builder(conf).build();\n        LoggerFactoryImpl loggerFactory = LoggerFactoryImpl.build();\n        ILoggerFactory iLoggerFactory = NoOpLoggerFactory.INSTANCE;\n        Mdc mdc = NoOpLoggerFactory.NO_OP_MDC_INSTANCE;\n        if (loggerFactory != null) {\n            iLoggerFactory = loggerFactory;\n            mdc = new LoggerMdc(loggerFactory.factory().mdc());\n        }\n        ContextManager contextManager = new ContextManager(conf, pluginConfigManager, iLoggerFactory, mdc);\n        EaseAgent.loggerFactory = contextManager.globalContext.getLoggerFactory();\n        EaseAgent.loggerMdc = contextManager.globalContext.getMdc();\n        EaseAgent.initializeContextSupplier = contextManager;\n        EaseAgent.metricRegistrySupplier = contextManager.globalContext.getMetric();\n        EaseAgent.configFactory = contextManager.pluginConfigManager;\n        return contextManager;\n    }\n\n    @Override\n    public InitializeContext getContext() {\n        return this.sessionContextSupplier.get();\n    }\n\n    public void setTracing(@Nonnull TracingProvider tracing) {\n        LOGGER.info(\"set tracing supplier function.\");\n        this.tracingSupplier = tracing.tracingSupplier();\n    }\n\n    public void setMetric(@Nonnull MetricProvider metricProvider) {\n        LOGGER.info(\"set metric supplier function.\");\n        this.metric = metricProvider.metricSupplier();\n    }\n\n    private class SessionContextSupplier implements Supplier<InitializeContext> {\n        @Override\n        public InitializeContext get() {\n            SessionContext context = LOCAL_SESSION_CONTEXT.get();\n            ITracing tracing = context.getTracing();\n            if (tracing == null || tracing.isNoop()) {\n                context.setCurrentTracing(NoNull.of(tracingSupplier.get(this), NoOpTracer.NO_OP_TRACING));\n            }\n            if (context.getSupplier() == null) {\n                context.setSupplier(this);\n            }\n            return context;\n        }\n    }\n\n    public class MetricRegistrySupplierImpl implements MetricRegistrySupplier {\n\n        @Override\n        public MetricRegistry newMetricRegistry(IPluginConfig config, NameFactory nameFactory, Tags tags) {\n            return NoNull.of(metric.newMetricRegistry(config, nameFactory, tags), NoOpMetrics.NO_OP_METRIC);\n        }\n\n        @Override\n        public Reporter reporter(IPluginConfig config) {\n            return NoNull.of(metric.reporter(config), NoOpReporter.NO_OP_REPORTER);\n        }\n    }\n}\n"
  },
  {
    "path": "context/src/main/java/com/megaease/easeagent/context/GlobalContext.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.context;\n\nimport com.megaease.easeagent.config.Configs;\nimport com.megaease.easeagent.plugin.api.logging.ILoggerFactory;\nimport com.megaease.easeagent.plugin.api.logging.Mdc;\nimport com.megaease.easeagent.plugin.api.metric.MetricRegistrySupplier;\n\nimport javax.annotation.Nonnull;\n\npublic class GlobalContext {\n    private final Configs conf;\n    private final MetricRegistrySupplier metric;\n    private final ILoggerFactory loggerFactory;\n    private final Mdc mdc;\n\n    public GlobalContext(@Nonnull Configs conf, @Nonnull MetricRegistrySupplier metric, @Nonnull ILoggerFactory loggerFactory, @Nonnull Mdc mdc) {\n        this.conf = conf;\n        this.metric = metric;\n        this.loggerFactory = loggerFactory;\n        this.mdc = mdc;\n    }\n\n\n    public Configs getConf() {\n        return conf;\n    }\n\n    public Mdc getMdc() {\n        return mdc;\n    }\n\n\n    public ILoggerFactory getLoggerFactory() {\n        return loggerFactory;\n    }\n\n    public MetricRegistrySupplier getMetric() {\n        return metric;\n    }\n}\n"
  },
  {
    "path": "context/src/main/java/com/megaease/easeagent/context/ProgressFieldsManager.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.context;\n\nimport com.megaease.easeagent.config.Configs;\nimport com.megaease.easeagent.plugin.api.ProgressFields;\nimport com.megaease.easeagent.plugin.api.config.ChangeItem;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.function.Consumer;\n\npublic class ProgressFieldsManager {\n\n    private ProgressFieldsManager() {\n    }\n\n    public static void init(Configs configs) {\n        Consumer<Map<String, String>> changeListener = ProgressFields.changeListener();\n        changeListener.accept(configs.getConfigs());\n        configs.addChangeListener(list -> {\n            Map<String, String> map = new HashMap<>();\n            for (ChangeItem changeItem : list) {\n                String key = changeItem.getFullName();\n                if (ProgressFields.isProgressFields(key)) {\n                    map.put(key, changeItem.getNewValue());\n                }\n                changeListener.accept(map);\n            }\n        });\n    }\n}\n"
  },
  {
    "path": "context/src/main/java/com/megaease/easeagent/context/RetBound.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.context;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\npublic class RetBound  {\n    int size;\n    Map<String, Object> local;\n\n    RetBound(int size) {\n        this.size = size;\n    }\n\n    public int size() {\n        return this.size;\n    }\n\n    public Object get(String key) {\n        if (local == null) {\n            return null;\n        }\n        return local.get(key);\n    }\n\n    public void put(String key, Object value) {\n        if (local == null) {\n            this.local = new HashMap<>();\n        }\n        this.local.put(key, value);\n    }\n}\n"
  },
  {
    "path": "context/src/main/java/com/megaease/easeagent/context/SessionContext.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.context;\n\nimport com.megaease.easeagent.log4j2.Logger;\nimport com.megaease.easeagent.log4j2.LoggerFactory;\nimport com.megaease.easeagent.plugin.api.Cleaner;\nimport com.megaease.easeagent.plugin.api.InitializeContext;\nimport com.megaease.easeagent.plugin.api.ProgressFields;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.context.AsyncContext;\nimport com.megaease.easeagent.plugin.api.context.RequestContext;\nimport com.megaease.easeagent.plugin.api.trace.*;\nimport com.megaease.easeagent.plugin.bridge.NoOpCleaner;\nimport com.megaease.easeagent.plugin.bridge.NoOpIPluginConfig;\nimport com.megaease.easeagent.plugin.bridge.NoOpTracer;\nimport com.megaease.easeagent.plugin.field.NullObject;\nimport com.megaease.easeagent.plugin.utils.NoNull;\n\nimport java.util.*;\nimport java.util.function.Supplier;\n\n@SuppressWarnings(\"unused, unchecked\")\npublic class SessionContext implements InitializeContext {\n    private static final Logger LOGGER = LoggerFactory.getLogger(SessionContext.class);\n    private static final Setter NOOP_SETTER = (name, value) -> {\n    };\n    private ITracing tracing = NoOpTracer.NO_OP_TRACING;\n\n    private Supplier<InitializeContext> supplier;\n    private final Deque<IPluginConfig> configs = new ArrayDeque<>();\n    private final Deque<Object> retStack = new ArrayDeque<>();\n    private final Deque<RetBound> retBound = new ArrayDeque<>();\n\n    private final Map<Object, Object> context = new HashMap<>();\n    private final Map<Object, Integer> entered = new HashMap<>();\n    private boolean hasCleaner = false;\n\n    @Override\n    public boolean isNoop() {\n        return false;\n    }\n\n    public Supplier<InitializeContext> getSupplier() {\n        return supplier;\n    }\n\n    public void setSupplier(Supplier<InitializeContext> supplier) {\n        this.supplier = supplier;\n    }\n\n    @Override\n    public Tracing currentTracing() {\n        return NoNull.of(tracing, NoOpTracer.NO_OP_TRACING);\n    }\n\n    @Override\n    public <V> V get(Object key) {\n        return change(context.get(key));\n    }\n\n    @Override\n    public <V> V remove(Object key) {\n        return change(context.remove(key));\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    private <V> V change(Object o) {\n        return o == null ? null : (V) o;\n    }\n\n    @Override\n    public <V> V put(Object key, V value) {\n        context.put(key, value);\n        return value;\n    }\n\n    @Override\n    public <V> V putLocal(String key, V value) {\n        assert this.retBound.peek() != null;\n        this.retBound.peek().put(key, value);\n        return value;\n    }\n\n    @Override\n    public <V> V getLocal(String key) {\n        assert this.retBound.peek() != null;\n        return change(this.retBound.peek().get(key));\n    }\n\n    @Override\n    public IPluginConfig getConfig() {\n        if (configs.isEmpty()) {\n            LOGGER.warn(\"context.configs was empty.\");\n            return NoOpIPluginConfig.INSTANCE;\n        }\n        return configs.peek();\n    }\n\n    @Override\n    public void pushConfig(IPluginConfig config) {\n        configs.push(config);\n    }\n\n    @Override\n    public IPluginConfig popConfig() {\n        if (configs.isEmpty()) {\n            LOGGER.warn(\"context.configs was empty.\");\n            return NoOpIPluginConfig.INSTANCE;\n        }\n        return configs.pop();\n    }\n\n    @Override\n    public int enter(Object key) {\n        Integer count = entered.get(key);\n        if (count == null) {\n            count = 1;\n        } else {\n            count++;\n        }\n        entered.put(key, count);\n        return count;\n    }\n\n    @Override\n    public int exit(Object key) {\n        Integer count = entered.get(key);\n        if (count == null) {\n            return 0;\n        }\n        entered.put(key, count - 1);\n        return count;\n    }\n\n    @Override\n    public AsyncContext exportAsync() {\n        return AsyncContextImpl.build(tracing.exportAsync(), supplier, context);\n    }\n\n    @Override\n    public Cleaner importAsync(AsyncContext snapshot) {\n        Scope scope = tracing.importAsync(snapshot.getSpanContext());\n        context.putAll(snapshot.getAll());\n        if (hasCleaner) {\n            return new AsyncCleaner(scope, false);\n        } else {\n            hasCleaner = true;\n            return new AsyncCleaner(scope, true);\n        }\n    }\n\n    @Override\n    public RequestContext clientRequest(Request request) {\n        return tracing.clientRequest(request);\n    }\n\n    @Override\n    public RequestContext serverReceive(Request request) {\n        return tracing.serverReceive(request);\n    }\n\n    @Override\n    public Span consumerSpan(MessagingRequest request) {\n        return tracing.consumerSpan(request);\n    }\n\n    @Override\n    public Span producerSpan(MessagingRequest request) {\n        return tracing.producerSpan(request);\n    }\n\n    @Override\n    public Span nextSpan() {\n        return tracing.nextSpan();\n    }\n\n    /**\n     * called by framework to maintain stack\n     */\n    @Override\n    @SuppressWarnings(\"ConstantConditions\")\n    public void popToBound() {\n        while (this.retStack.size() > this.retBound.peek().size()) {\n            this.retStack.pop();\n        }\n    }\n\n    /**\n     * called by framework to maintain stack\n     */\n    public void pushRetBound() {\n        this.retBound.push(new RetBound(this.retStack.size()));\n    }\n\n    /**\n     * called by framework to maintain stack\n     */\n    public void popRetBound() {\n        this.retBound.pop();\n    }\n\n    @Override\n    public <T> void push(T obj) {\n        if (obj == null) {\n            this.retStack.push(NullObject.NULL);\n        } else {\n            this.retStack.push(obj);\n        }\n    }\n\n    @Override\n    @SuppressWarnings(\"ConstantConditions\")\n    public <T> T pop() {\n        if (this.retStack.size() <= this.retBound.peek().size()) {\n            return null;\n        }\n        Object o = this.retStack.pop();\n        if (o == NullObject.NULL) {\n            return null;\n        }\n        return change(o);\n    }\n\n    @Override\n    public <T> T peek() {\n        if (this.retStack.isEmpty()) {\n            return null;\n        }\n        Object o = this.retStack.pop();\n        if (o == NullObject.NULL) {\n            return null;\n        }\n        return change(o);\n    }\n\n    @Override\n    public Runnable wrap(Runnable task) {\n        return new CurrentContextRunnable(exportAsync(), task);\n    }\n\n    @Override\n    public boolean isWrapped(Runnable task) {\n        return task instanceof CurrentContextRunnable;\n    }\n\n    @Override\n    public boolean isNecessaryKeys(String key) {\n        return tracing.propagationKeys().contains(key);\n    }\n\n    @Override\n    public void consumerInject(Span span, MessagingRequest request) {\n        Injector<MessagingRequest> injector = tracing.messagingTracing().consumerInjector();\n        injector.inject(span, request);\n    }\n\n    @Override\n    public void producerInject(Span span, MessagingRequest request) {\n        Injector<MessagingRequest> injector = tracing.messagingTracing().producerInjector();\n        injector.inject(span, request);\n    }\n\n    @Override\n    public void injectForwardedHeaders(Setter setter) {\n        Set<String> fields = ProgressFields.getForwardedHeaders();\n        if (fields.isEmpty()) {\n            return;\n        }\n        for (String field : fields) {\n            Object o = context.get(field);\n            if ((o instanceof String)) {\n                setter.setHeader(field, (String) o);\n            }\n        }\n    }\n\n    @Override\n    public Cleaner importForwardedHeaders(Getter getter) {\n        return importForwardedHeaders(getter, NOOP_SETTER);\n    }\n\n    private Cleaner importForwardedHeaders(Getter getter, Setter setter) {\n        Set<String> fields = ProgressFields.getForwardedHeaders();\n        if (fields.isEmpty()) {\n            return NoOpCleaner.INSTANCE;\n        }\n        List<String> fieldArr = new ArrayList<>(fields.size());\n        for (String field : fields) {\n            String o = getter.header(field);\n            if (o == null) {\n                continue;\n            }\n            fieldArr.add(field);\n            this.context.put(field, o);\n            setter.setHeader(field, o);\n        }\n        if (fieldArr.isEmpty()) {\n            return NoOpCleaner.INSTANCE;\n        }\n        return new FieldCleaner(fieldArr);\n    }\n\n\n    public ITracing getTracing() {\n        return tracing;\n    }\n\n    @Override\n    public void setCurrentTracing(ITracing tracing) {\n        this.tracing = NoNull.of(tracing, NoOpTracer.NO_OP_TRACING);\n    }\n\n    @Override\n    public void clear() {\n        if (!this.configs.isEmpty()) {\n            this.configs.clear();\n        }\n        if (!this.retStack.isEmpty()) {\n            this.retStack.clear();\n        }\n        if (!this.retBound.isEmpty()) {\n            this.retBound.clear();\n        }\n        if (!this.context.isEmpty()) {\n            this.context.clear();\n        }\n        if (!this.entered.isEmpty()) {\n            this.entered.clear();\n        }\n        this.hasCleaner = false;\n    }\n\n    public static class CurrentContextRunnable implements Runnable {\n        private final AsyncContext asyncContext;\n        private final Runnable task;\n\n        public CurrentContextRunnable(AsyncContext asyncContext, Runnable task) {\n            this.asyncContext = asyncContext;\n            this.task = task;\n        }\n\n        @Override\n        public void run() {\n            try (Cleaner cleaner = asyncContext.importToCurrent()) {\n                task.run();\n            }\n        }\n    }\n\n    private class FieldCleaner implements Cleaner {\n        private final List<String> fields;\n\n        public FieldCleaner(List<String> fields) {\n            this.fields = fields;\n        }\n\n        @Override\n        public void close() {\n            for (String field : fields) {\n                context.remove(field);\n            }\n        }\n    }\n\n    public class AsyncCleaner implements Cleaner {\n        private final Scope scope;\n        private final boolean clearContext;\n\n        public AsyncCleaner(Scope scope, boolean clearContext) {\n            this.scope = scope;\n            this.clearContext = clearContext;\n        }\n\n        @Override\n        public void close() {\n            this.scope.close();\n            if (clearContext) {\n                SessionContext.this.clear();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "context/src/main/java/com/megaease/easeagent/context/log/LoggerFactoryImpl.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.context.log;\n\nimport com.megaease.easeagent.log4j2.LoggerFactory;\nimport com.megaease.easeagent.log4j2.api.AgentLoggerFactory;\nimport com.megaease.easeagent.plugin.api.logging.ILoggerFactory;\nimport com.megaease.easeagent.plugin.api.logging.Logger;\n\nimport javax.annotation.Nonnull;\n\npublic class LoggerFactoryImpl implements ILoggerFactory {\n    private final AgentLoggerFactory<LoggerImpl> loggerFactory;\n\n    private LoggerFactoryImpl(@Nonnull AgentLoggerFactory<LoggerImpl> loggerFactory) {\n        this.loggerFactory = loggerFactory;\n    }\n\n    @Override\n    public Logger getLogger(String name) {\n        return loggerFactory.getLogger(name);\n    }\n\n    public AgentLoggerFactory<LoggerImpl> factory() {\n        return loggerFactory;\n    }\n\n    public static LoggerFactoryImpl build() {\n        AgentLoggerFactory<LoggerImpl> loggerFactory = LoggerFactory.newFactory(LoggerImpl.LOGGER_SUPPLIER, LoggerImpl.class);\n        if (loggerFactory == null) {\n            return null;\n        }\n\n        return new LoggerFactoryImpl(loggerFactory);\n    }\n}\n"
  },
  {
    "path": "context/src/main/java/com/megaease/easeagent/context/log/LoggerImpl.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.context.log;\n\nimport com.megaease.easeagent.log4j2.api.AgentLogger;\n\nimport java.util.function.Function;\nimport java.util.logging.Logger;\n\npublic class LoggerImpl extends AgentLogger implements com.megaease.easeagent.plugin.api.logging.Logger {\n    public static final Function<Logger, LoggerImpl> LOGGER_SUPPLIER = LoggerImpl::new;\n\n    public LoggerImpl(Logger logger) {\n        super(logger);\n    }\n}\n"
  },
  {
    "path": "context/src/main/java/com/megaease/easeagent/context/log/LoggerMdc.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.context.log;\n\nimport com.megaease.easeagent.plugin.api.logging.Mdc;\n\npublic class LoggerMdc implements Mdc {\n    private final com.megaease.easeagent.log4j2.api.Mdc mdc;\n\n    public LoggerMdc(com.megaease.easeagent.log4j2.api.Mdc mdc) {\n        this.mdc = mdc;\n    }\n\n    @Override\n    public void put(String key, String value) {\n        mdc.put(key, value);\n    }\n\n    @Override\n    public void remove(String key) {\n        mdc.remove(key);\n    }\n\n    @Override\n    public String get(String key) {\n        return mdc.get(key);\n    }\n}\n"
  },
  {
    "path": "context/src/test/java/com/megaease/easeagent/context/AsyncContextImplTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.context;\n\nimport com.megaease.easeagent.plugin.api.Cleaner;\nimport com.megaease.easeagent.plugin.bridge.NoOpTracer;\nimport org.junit.Test;\n\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static org.junit.Assert.*;\n\npublic class AsyncContextImplTest {\n\n    @Test\n    public void build() {\n        try {\n            AsyncContextImpl.build(null, null, null);\n            assertTrue(\"must be throw error\", false);\n        } catch (Exception e) {\n            assertNotNull(e);\n        }\n        try {\n            AsyncContextImpl.build(NoOpTracer.NO_OP_SPAN_CONTEXT, null, null);\n            assertTrue(\"must be throw error\", false);\n        } catch (Exception e) {\n            assertNotNull(e);\n        }\n\n        AsyncContextImpl asyncContext = AsyncContextImpl.build(NoOpTracer.NO_OP_SPAN_CONTEXT, () -> null, null);\n        assertNotNull(asyncContext.getAll());\n    }\n\n    @Test\n    public void isNoop() {\n        AsyncContextImpl asyncContext = AsyncContextImpl.build(NoOpTracer.NO_OP_SPAN_CONTEXT, () -> null, null);\n        assertFalse(asyncContext.isNoop());\n    }\n\n    @Test\n    public void getSpanContext() {\n        AsyncContextImpl asyncContext = AsyncContextImpl.build(NoOpTracer.NO_OP_SPAN_CONTEXT, () -> null, null);\n        assertTrue(asyncContext.getSpanContext().isNoop());\n    }\n\n    @Test\n    public void importToCurrent() {\n        String name = \"test_name\";\n        String value = \"test_value\";\n        SessionContext sessionContext = new SessionContext();\n        AsyncContextImpl asyncContext = AsyncContextImpl.build(NoOpTracer.NO_OP_SPAN_CONTEXT, () -> sessionContext, null);\n        asyncContext.put(name, value);\n        assertNull(sessionContext.get(name));\n        try (Cleaner cleaner = asyncContext.importToCurrent()) {\n            assertEquals(value, sessionContext.get(name));\n        }\n        assertNull(sessionContext.get(name));\n\n        AsyncContextImpl asyncContext2 = AsyncContextImpl.build(NoOpTracer.NO_OP_SPAN_CONTEXT, () -> sessionContext, Collections.singletonMap(name, value));\n\n        assertNull(sessionContext.get(name));\n        try (Cleaner cleaner = asyncContext2.importToCurrent()) {\n            assertEquals(value, sessionContext.get(name));\n        }\n        assertNull(sessionContext.get(name));\n\n    }\n\n    @Test\n    public void getAll() {\n        String name = \"test_name\";\n        String value = \"test_value\";\n        AsyncContextImpl asyncContext = AsyncContextImpl.build(NoOpTracer.NO_OP_SPAN_CONTEXT, () -> null, Collections.singletonMap(name, value));\n        assertNotNull(asyncContext.getAll());\n        assertEquals(value, asyncContext.getAll().get(name));\n    }\n\n    @Test\n    public void get() {\n        String name = \"test_name\";\n        String value = \"test_value\";\n        AsyncContextImpl asyncContext = AsyncContextImpl.build(NoOpTracer.NO_OP_SPAN_CONTEXT, () -> null, Collections.singletonMap(name, value));\n        String v = asyncContext.get(name);\n        assertNotNull(v);\n        assertEquals(v, value);\n        assertNull(asyncContext.get(name + \"test\"));\n\n    }\n\n    @Test\n    public void put() {\n        Map<Object, Object> context = new HashMap<>();\n        String name = \"test_name\";\n        String value = \"test_value\";\n        String name2 = name + \"2\";\n        context.put(name, value);\n        AsyncContextImpl asyncContext = AsyncContextImpl.build(NoOpTracer.NO_OP_SPAN_CONTEXT, () -> null, context);\n        asyncContext.put(name2, value);\n        assertNull(context.get(name2));\n        assertEquals(value, asyncContext.get(name));\n        assertEquals(value, asyncContext.get(name2));\n\n    }\n}\n"
  },
  {
    "path": "context/src/test/java/com/megaease/easeagent/context/ContextManagerTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.context;\n\nimport com.megaease.easeagent.mock.config.MockConfig;\nimport com.megaease.easeagent.plugin.api.Reporter;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.metric.MetricRegistry;\nimport com.megaease.easeagent.plugin.api.metric.MetricRegistrySupplier;\nimport com.megaease.easeagent.plugin.api.metric.name.NameFactory;\nimport com.megaease.easeagent.plugin.api.metric.name.Tags;\nimport org.junit.Test;\n\nimport static org.junit.Assert.assertNotNull;\n\npublic class ContextManagerTest {\n\n    @Test\n    public void build() {\n        ContextManager contextManager = ContextManager.build(MockConfig.getCONFIGS());\n        assertNotNull(contextManager);\n        contextManager.setTracing(() -> contextSupplier -> null);\n    }\n\n    @Test\n    public void setTracing() {\n        ContextManager contextManager = ContextManager.build(MockConfig.getCONFIGS());\n        assertNotNull(contextManager);\n        contextManager.setTracing(() -> contextSupplier -> null);\n    }\n\n    @Test\n    public void setMetric() {\n        ContextManager contextManager = ContextManager.build(MockConfig.getCONFIGS());\n        assertNotNull(contextManager);\n        contextManager.setMetric(() -> new MetricRegistrySupplier() {\n            @Override\n            public MetricRegistry newMetricRegistry(IPluginConfig config, NameFactory nameFactory, Tags tags) {\n                return null;\n            }\n\n            @Override\n            public Reporter reporter(IPluginConfig config) {\n                return null;\n            }\n        });\n    }\n}\n"
  },
  {
    "path": "context/src/test/java/com/megaease/easeagent/context/GlobalContextTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.context;\n\nimport com.megaease.easeagent.config.Configs;\nimport com.megaease.easeagent.context.log.LoggerFactoryImpl;\nimport com.megaease.easeagent.context.log.LoggerMdc;\nimport com.megaease.easeagent.plugin.bridge.NoOpMetrics;\nimport org.junit.Before;\nimport org.junit.Test;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static org.junit.Assert.assertNotNull;\n\npublic class GlobalContextTest {\n    GlobalContext globalContext;\n\n    @Before\n    public void before() {\n        Map<String, String> initConfigs = new HashMap<>();\n        initConfigs.put(\"name\", \"demo-service\");\n        initConfigs.put(\"system\", \"demo-system\");\n        Configs configs = new Configs(initConfigs);\n        LoggerFactoryImpl loggerFactory = LoggerFactoryImpl.build();\n        globalContext = new GlobalContext(configs, NoOpMetrics.NO_OP_METRIC_SUPPLIER, loggerFactory, new LoggerMdc(loggerFactory.factory().mdc()));\n    }\n\n\n    @Test\n    public void getConf() {\n        assertNotNull(globalContext.getConf());\n    }\n\n    @Test\n    public void getMdc() {\n        assertNotNull(globalContext.getMdc());\n    }\n\n    @Test\n    public void getLoggerFactory() {\n        assertNotNull(globalContext.getLoggerFactory());\n    }\n\n    @Test\n    public void getMetric() {\n        assertNotNull(globalContext.getMetric());\n    }\n}\n"
  },
  {
    "path": "context/src/test/java/com/megaease/easeagent/context/ProgressFieldsManagerTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.context;\n\nimport com.megaease.easeagent.config.Configs;\nimport com.megaease.easeagent.plugin.api.ProgressFields;\nimport org.junit.Test;\n\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Set;\n\nimport static com.megaease.easeagent.plugin.api.ProgressFields.EASEAGENT_PROGRESS_FORWARDED_HEADERS_CONFIG;\nimport static org.junit.Assert.assertFalse;\nimport static org.junit.Assert.assertTrue;\n\npublic class ProgressFieldsManagerTest {\n\n    @Test\n    public void init() {\n        HashMap<String, String> source = new HashMap<>();\n        source.put(\"plugin.observability.global.metrics.enabled\", \"true\");\n        source.put(EASEAGENT_PROGRESS_FORWARDED_HEADERS_CONFIG, \"aaa,bbb,ccc\");\n        Configs configs = new Configs(source);\n        ProgressFieldsManager.init(configs);\n        Set<String> fields = ProgressFields.getForwardedHeaders();\n        assertFalse(fields.isEmpty());\n    }\n\n    @Test\n    public void isEmpty() {\n        assertTrue(true);\n        assertTrue(ProgressFields.isEmpty(new String[0]));\n        assertFalse(ProgressFields.isEmpty(new String[1]));\n    }\n\n    @Test\n    public void getFields() {\n        HashMap<String, String> source = new HashMap<>();\n        source.put(\"plugin.observability.global.metrics.enabled\", \"true\");\n        source.put(EASEAGENT_PROGRESS_FORWARDED_HEADERS_CONFIG, \"a,b,c\");\n        Configs configs = new Configs(source);\n        ProgressFieldsManager.init(configs);\n        Set<String> fields = ProgressFields.getForwardedHeaders();\n        assertFalse(fields.isEmpty());\n        assertTrue(fields.contains(\"a\"));\n        assertTrue(fields.contains(\"b\"));\n        assertTrue(fields.contains(\"c\"));\n\n        configs.updateConfigs(Collections.singletonMap(EASEAGENT_PROGRESS_FORWARDED_HEADERS_CONFIG, \"a\"));\n        configs.updateConfigs(Collections.singletonMap(EASEAGENT_PROGRESS_FORWARDED_HEADERS_CONFIG, \"c\"));\n        fields = ProgressFields.getForwardedHeaders();\n        assertFalse(fields.isEmpty());\n        assertFalse(fields.contains(\"a\"));\n        assertFalse(fields.contains(\"b\"));\n        assertTrue(fields.contains(\"c\"));\n\n    }\n}\n"
  },
  {
    "path": "context/src/test/java/com/megaease/easeagent/context/RetBoundTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.context;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\npublic class RetBoundTest {\n\n    @Test\n    public void size() {\n        assertEquals(100, new RetBound(100).size());\n    }\n\n    @Test\n    public void get() {\n        RetBound retBound = new RetBound(1);\n        Object o = new Object();\n        Object o2 = new Object();\n        retBound.put(\"a\", o);\n        assertEquals(o, retBound.get(\"a\"));\n        assertNotEquals(o2, retBound.get(\"a\"));\n    }\n\n    @Test\n    public void put() {\n        get();\n    }\n}\n"
  },
  {
    "path": "context/src/test/java/com/megaease/easeagent/context/SessionContextTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.context;\n\nimport com.megaease.easeagent.config.PluginConfig;\nimport com.megaease.easeagent.config.PluginConfigManager;\nimport com.megaease.easeagent.mock.config.MockConfig;\nimport com.megaease.easeagent.plugin.api.Cleaner;\nimport com.megaease.easeagent.plugin.api.InitializeContext;\nimport com.megaease.easeagent.plugin.api.ProgressFields;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.context.AsyncContext;\nimport com.megaease.easeagent.plugin.api.context.RequestContext;\nimport com.megaease.easeagent.plugin.api.trace.*;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.bridge.NoOpIPluginConfig;\nimport com.megaease.easeagent.plugin.bridge.NoOpTracer;\nimport org.junit.Before;\nimport org.junit.Test;\n\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.function.Supplier;\n\nimport static com.megaease.easeagent.plugin.api.ProgressFields.EASEAGENT_PROGRESS_FORWARDED_HEADERS_CONFIG;\nimport static org.junit.Assert.*;\n\npublic class SessionContextTest {\n\n    @Before\n    public void before() {\n        ContextManager contextManager = ContextManager.build(MockConfig.getCONFIGS());\n        assertNotNull(contextManager);\n    }\n\n    @Test\n    public void isNoop() {\n        SessionContext sessionContext = new SessionContext();\n        assertFalse(sessionContext.isNoop());\n    }\n\n    @Test\n    public void getSupplier() {\n        SessionContext sessionContext = new SessionContext();\n        assertNull(sessionContext.getSupplier());\n        sessionContext.setSupplier(() -> null);\n        assertNotNull(sessionContext.getSupplier());\n    }\n\n    @Test\n    public void setSupplier() {\n        getSupplier();\n    }\n\n    @Test\n    public void currentTracing() {\n        SessionContext sessionContext = new SessionContext();\n        assertTrue(sessionContext.currentTracing().isNoop());\n        sessionContext.setCurrentTracing(new MockITracing());\n        assertNotNull(sessionContext.currentTracing());\n        assertFalse(sessionContext.currentTracing().isNoop());\n    }\n\n    @Test\n    public void get() {\n        String name = \"test_name\";\n        String value = \"test_value\";\n        SessionContext sessionContext = new SessionContext();\n        assertNull(sessionContext.get(name));\n        sessionContext.put(name, value);\n        assertEquals(value, sessionContext.get(name));\n        sessionContext.remove(name);\n        assertNull(sessionContext.get(name));\n    }\n\n    @Test\n    public void remove() {\n        get();\n    }\n\n    @Test\n    public void put() {\n        get();\n    }\n\n    @Test\n    public void getLocal() {\n        //Deprecated\n    }\n\n    @Test\n    public void putLocal() {\n        //Deprecated\n    }\n\n\n    @Test\n    public void getConfig() {\n        SessionContext sessionContext = new SessionContext();\n        assertNotNull(sessionContext.getConfig());\n        IPluginConfig iPluginConfig = sessionContext.getConfig();\n        assertEquals(NoOpIPluginConfig.INSTANCE.domain(), iPluginConfig.domain());\n        assertEquals(NoOpIPluginConfig.INSTANCE.namespace(), iPluginConfig.namespace());\n        assertEquals(NoOpIPluginConfig.INSTANCE.id(), iPluginConfig.id());\n\n        sessionContext.pushConfig(NoOpIPluginConfig.INSTANCE);\n        assertNotNull(sessionContext.getConfig());\n        PluginConfigManager pluginConfigManager = PluginConfigManager.builder(MockConfig.getCONFIGS()).build();\n        String domain = \"observability\";\n        String namespace = \"test_config\";\n        String id = \"test\";\n        PluginConfig config = pluginConfigManager.getConfig(domain, namespace, id);\n        sessionContext.pushConfig(config);\n\n        iPluginConfig = sessionContext.getConfig();\n        assertEquals(domain, iPluginConfig.domain());\n        assertEquals(namespace, iPluginConfig.namespace());\n        assertEquals(id, iPluginConfig.id());\n\n        IPluginConfig oppConfig = sessionContext.popConfig();\n        assertEquals(domain, oppConfig.domain());\n        assertEquals(namespace, oppConfig.namespace());\n        assertEquals(id, oppConfig.id());\n\n        iPluginConfig = sessionContext.getConfig();\n        assertEquals(NoOpIPluginConfig.INSTANCE.domain(), iPluginConfig.domain());\n        assertEquals(NoOpIPluginConfig.INSTANCE.namespace(), iPluginConfig.namespace());\n        assertEquals(NoOpIPluginConfig.INSTANCE.id(), iPluginConfig.id());\n\n    }\n\n    @Test\n    public void pushConfig() {\n        getConfig();\n    }\n\n    @Test\n    public void popConfig() {\n        getConfig();\n    }\n\n    @Test\n    public void enter() {\n        SessionContext sessionContext = new SessionContext();\n        Object key1 = new Object();\n        Object key2 = new Object();\n        assertEquals(1, sessionContext.enter(key1));\n        assertEquals(1, sessionContext.enter(key2));\n        assertEquals(2, sessionContext.enter(key1));\n        assertEquals(2, sessionContext.enter(key2));\n        assertEquals(2, sessionContext.exit(key1));\n        assertEquals(2, sessionContext.exit(key2));\n        assertEquals(1, sessionContext.exit(key1));\n        assertEquals(1, sessionContext.exit(key2));\n\n\n        assertTrue(sessionContext.enter(key1, 1));\n        assertTrue(sessionContext.enter(key2, 1));\n        assertTrue(sessionContext.enter(key1, 2));\n        assertTrue(sessionContext.enter(key2, 2));\n        assertTrue(sessionContext.exit(key1, 2));\n        assertTrue(sessionContext.exit(key2, 2));\n        assertTrue(sessionContext.exit(key1, 1));\n        assertTrue(sessionContext.exit(key2, 1));\n\n    }\n\n    @Test\n    public void exit() {\n        enter();\n    }\n\n    @Test\n    public void exportAsync() {\n        SessionContext sessionContext = new SessionContext();\n        sessionContext.setSupplier(() -> EaseAgent.initializeContextSupplier.getContext());\n        MockITracing iTracing = new MockITracing();\n        sessionContext.setCurrentTracing(iTracing);\n        String name = \"test_name\";\n        String value = \"test_value\";\n        assertNull(sessionContext.get(name));\n        sessionContext.put(name, value);\n        assertEquals(value, sessionContext.get(name));\n        AsyncContext asyncContext = sessionContext.exportAsync();\n        assertTrue(asyncContext.getSpanContext().isNoop());\n        assertEquals(1, iTracing.exportAsyncCount.get());\n\n        assertEquals(value, asyncContext.get(name));\n        assertFalse(asyncContext.isNoop());\n        SessionContext sessionContext2 = new SessionContext();\n        sessionContext2.setCurrentTracing(iTracing);\n        assertNull(sessionContext2.get(name));\n        try (Cleaner ignored = sessionContext2.importAsync(asyncContext)) {\n            assertEquals(value, sessionContext2.get(name));\n            assertEquals(1, iTracing.importAsyncCount.get());\n            try (Cleaner ignored1 = sessionContext2.importAsync(asyncContext)) {\n                assertEquals(2, iTracing.importAsyncCount.get());\n            }\n            assertEquals(value, sessionContext2.get(name));\n        }\n\n    }\n\n    @Test\n    public void importAsync() {\n        exportAsync();\n    }\n\n    @Test\n    public void clientRequest() {\n        SessionContext sessionContext = new SessionContext();\n        MockITracing iTracing = new MockITracing();\n        sessionContext.setCurrentTracing(iTracing);\n        sessionContext.clientRequest(new EmptyRequest());\n        assertEquals(1, iTracing.clientRequestCount.get());\n    }\n\n    @Test\n    public void serverReceive() {\n        SessionContext sessionContext = new SessionContext();\n        MockITracing iTracing = new MockITracing();\n        sessionContext.setCurrentTracing(iTracing);\n        sessionContext.serverReceive(new EmptyRequest());\n        assertEquals(1, iTracing.serverReceiveCount.get());\n\n    }\n\n    @Test\n    public void consumerSpan() {\n        SessionContext sessionContext = new SessionContext();\n        MockITracing iTracing = new MockITracing();\n        sessionContext.setCurrentTracing(iTracing);\n        sessionContext.consumerSpan(new EmptyRequest());\n        assertEquals(1, iTracing.consumerSpanCount.get());\n\n    }\n\n    @Test\n    public void producerSpan() {\n        SessionContext sessionContext = new SessionContext();\n        MockITracing iTracing = new MockITracing();\n        sessionContext.setCurrentTracing(iTracing);\n        sessionContext.producerSpan(new EmptyRequest());\n        assertEquals(1, iTracing.producerSpanCount.get());\n    }\n\n    @Test\n    public void nextSpan() {\n        SessionContext sessionContext = new SessionContext();\n        MockITracing iTracing = new MockITracing();\n        sessionContext.setCurrentTracing(iTracing);\n        sessionContext.nextSpan();\n        assertEquals(1, iTracing.nextSpanCount.get());\n\n    }\n\n    @Test\n    public void popToBound() {\n        //Deprecated\n    }\n\n    @Test\n    public void pushRetBound() {\n        //Deprecated\n    }\n\n    @Test\n    public void popRetBound() {\n        //Deprecated\n    }\n\n    @Test\n    public void push() {\n        //Deprecated\n    }\n\n    @Test\n    public void pop() {\n        //Deprecated\n    }\n\n    @Test\n    public void peek() {\n        //Deprecated\n    }\n\n    @Test\n    public void wrap() throws InterruptedException {\n        SessionContext sessionContext = new SessionContext();\n        MockITracing iTracing = new MockITracing();\n        sessionContext.setCurrentTracing(iTracing);\n        String name = \"test_name\";\n        String value = \"test_value\";\n        sessionContext.put(name, value);\n\n        AtomicReference<SessionContext> lastSessionContext = new AtomicReference<>();\n        Supplier<InitializeContext> newContextSupplier = () -> {\n            SessionContext sessionContext1 = new SessionContext();\n            sessionContext1.setCurrentTracing(iTracing);\n            lastSessionContext.set(sessionContext1);\n            return sessionContext1;\n        };\n        sessionContext.setSupplier(newContextSupplier);\n        Runnable runnable = () -> {\n            assertNotNull(lastSessionContext.get());\n            assertEquals(value, lastSessionContext.get().get(name));\n\n        };\n        runnable = sessionContext.wrap(runnable);\n        assertTrue(sessionContext.isWrapped(runnable));\n        Thread thread = new Thread(runnable);\n        thread.start();\n        thread.join();\n        assertNull(lastSessionContext.get().get(name));\n        assertEquals(1, iTracing.exportAsyncCount.get());\n        assertEquals(1, iTracing.importAsyncCount.get());\n    }\n\n    @Test\n    public void isWrapped() throws InterruptedException {\n        wrap();\n    }\n\n    @Test\n    public void isNecessaryKeys() {\n        SessionContext sessionContext = new SessionContext();\n        MockITracing iTracing = new MockITracing();\n        iTracing.setPropagationKeys(Collections.singletonList(\"b3\"));\n        sessionContext.setCurrentTracing(iTracing);\n        assertTrue(sessionContext.isNecessaryKeys(\"b3\"));\n        assertFalse(sessionContext.isNecessaryKeys(\"aaa\"));\n\n    }\n\n    @Test\n    public void consumerInject() {\n        SessionContext sessionContext = new SessionContext();\n        MockITracing iTracing = new MockITracing();\n        sessionContext.setCurrentTracing(iTracing);\n        sessionContext.consumerInject(NoOpTracer.NO_OP_SPAN, new EmptyRequest());\n        assertEquals(1, iTracing.mockMessagingTracing.consumerInjectorCount.get());\n\n    }\n\n    @Test\n    public void producerInject() {\n        SessionContext sessionContext = new SessionContext();\n        MockITracing iTracing = new MockITracing();\n        sessionContext.setCurrentTracing(iTracing);\n        sessionContext.producerInject(NoOpTracer.NO_OP_SPAN, new EmptyRequest());\n        assertEquals(1, iTracing.mockMessagingTracing.producerInjectorCount.get());\n\n    }\n\n    @Test\n    public void injectForwardedHeaders() {\n        String forwarded = \"test_forwarded_value\";\n        String forwardedKey = EASEAGENT_PROGRESS_FORWARDED_HEADERS_CONFIG;\n        String headerValue = \"test_value\";\n        ProgressFields.changeListener().accept(Collections.singletonMap(forwardedKey, forwarded));\n        ProgressFields.changeListener().accept(Collections.singletonMap(forwardedKey + \"2\", forwarded + \"2\"));\n        SessionContext sessionContext = new SessionContext();\n        sessionContext.importForwardedHeaders(name -> {\n            if (name.equals(forwarded)) {\n                return headerValue;\n            }\n            return null;\n        });\n\n\n        Map<String, String> values = new HashMap<>();\n        sessionContext.injectForwardedHeaders(values::put);\n        assertEquals(1, values.size());\n        assertEquals(headerValue, values.get(forwarded));\n\n        ProgressFields.changeListener().accept(Collections.singletonMap(forwardedKey, \"\"));\n        ProgressFields.changeListener().accept(Collections.singletonMap(forwardedKey + \"2\", \"\"));\n    }\n\n    @Test\n    public void getTracing() {\n        SessionContext sessionContext = new SessionContext();\n        MockITracing iTracing = new MockITracing();\n        sessionContext.setCurrentTracing(iTracing);\n        assertNotNull(sessionContext.getTracing());\n        assertTrue(sessionContext.getTracing() instanceof MockITracing);\n    }\n\n    @Test\n    public void setCurrentTracing() {\n        getTracing();\n    }\n\n    @Test\n    public void clear() {\n        String name = \"test_name\";\n        String value = \"test_value\";\n        SessionContext sessionContext = new SessionContext();\n        sessionContext.put(name, value);\n        assertEquals(1, sessionContext.enter(\"test_key\"));\n        sessionContext.clear();\n        assertNull(sessionContext.get(name));\n        assertEquals(1, sessionContext.enter(\"test_key\"));\n        sessionContext.clear();\n    }\n\n\n    public static class EmptyRequest implements MessagingRequest {\n\n        @Override\n        public String operation() {\n            return null;\n        }\n\n        @Override\n        public String channelKind() {\n            return null;\n        }\n\n        @Override\n        public String channelName() {\n            return null;\n        }\n\n        @Override\n        public Object unwrap() {\n            return null;\n        }\n\n        @Override\n        public Span.Kind kind() {\n            return null;\n        }\n\n        @Override\n        public String header(String name) {\n            return null;\n        }\n\n        @Override\n        public String name() {\n            return null;\n        }\n\n        @Override\n        public boolean cacheScope() {\n            return false;\n        }\n\n        @Override\n        public void setHeader(String name, String value) {\n\n        }\n    }\n\n\n    public static class MockITracing extends NoOpTracer.NoopTracing {\n        public AtomicInteger exportAsyncCount = new AtomicInteger();\n        public AtomicInteger importAsyncCount = new AtomicInteger();\n        public AtomicInteger clientRequestCount = new AtomicInteger();\n        public AtomicInteger serverReceiveCount = new AtomicInteger();\n        public AtomicInteger consumerSpanCount = new AtomicInteger();\n        public AtomicInteger producerSpanCount = new AtomicInteger();\n        public AtomicInteger nextSpanCount = new AtomicInteger();\n        public MockMessagingTracing mockMessagingTracing = new MockMessagingTracing();\n        private List<String> propagationKeys = Collections.emptyList();\n\n        public MockITracing setPropagationKeys(List<String> propagationKeys) {\n            this.propagationKeys = propagationKeys;\n            return this;\n        }\n\n        @Override\n        public SpanContext exportAsync() {\n            exportAsyncCount.incrementAndGet();\n            return super.exportAsync();\n        }\n\n        @Override\n        public Scope importAsync(SpanContext snapshot) {\n            importAsyncCount.incrementAndGet();\n            return super.importAsync(snapshot);\n        }\n\n        @Override\n        public RequestContext clientRequest(Request request) {\n            clientRequestCount.incrementAndGet();\n            return super.clientRequest(request);\n        }\n\n        @Override\n        public RequestContext serverReceive(Request request) {\n            serverReceiveCount.incrementAndGet();\n            return super.serverReceive(request);\n        }\n\n        @Override\n        public Span consumerSpan(MessagingRequest request) {\n            consumerSpanCount.incrementAndGet();\n            return super.consumerSpan(request);\n        }\n\n        @Override\n        public Span producerSpan(MessagingRequest request) {\n            producerSpanCount.incrementAndGet();\n            return super.producerSpan(request);\n        }\n\n        @Override\n        public Span nextSpan() {\n            nextSpanCount.incrementAndGet();\n            return super.nextSpan();\n        }\n\n        @Override\n        public List<String> propagationKeys() {\n            return propagationKeys;\n        }\n\n        @Override\n        public MessagingTracing<MessagingRequest> messagingTracing() {\n            return mockMessagingTracing;\n        }\n\n        @Override\n        public boolean isNoop() {\n            return false;\n        }\n    }\n\n\n    public static class MockMessagingTracing extends NoOpTracer.EmptyMessagingTracing {\n        public AtomicInteger producerInjectorCount = new AtomicInteger();\n        public AtomicInteger consumerInjectorCount = new AtomicInteger();\n\n        @Override\n        public Injector<MessagingRequest> producerInjector() {\n            return (span, request) -> producerInjectorCount.incrementAndGet();\n        }\n\n        @Override\n        public Injector<MessagingRequest> consumerInjector() {\n            return (span, request) -> consumerInjectorCount.incrementAndGet();\n        }\n    }\n}\n"
  },
  {
    "path": "context/src/test/java/com/megaease/easeagent/context/log/LoggerFactoryImplTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.context.log;\n\nimport com.megaease.easeagent.log4j2.MDC;\nimport com.megaease.easeagent.log4j2.api.AgentLoggerFactory;\nimport com.megaease.easeagent.plugin.api.logging.Logger;\nimport org.junit.Test;\n\nimport static org.junit.Assert.assertNotNull;\n\npublic class LoggerFactoryImplTest {\n    LoggerFactoryImpl loggerFactory = LoggerFactoryImpl.build();\n\n    @Test\n    public void getLogger() {\n        Logger logger = loggerFactory.getLogger(LoggerFactoryImplTest.class.getName());\n        logger.info(\"aaaa\");\n        MDC.put(\"testMdc\", \"testMdc_value\");\n        logger.info(\"bbbb\");\n        assertNotNull(MDC.get(\"testMdc\"));\n    }\n\n    @Test\n    public void factory() {\n        AgentLoggerFactory<LoggerImpl> agentLoggerFactory = loggerFactory.factory();\n        assertNotNull(agentLoggerFactory);\n        LoggerImpl logger = agentLoggerFactory.getLogger(LoggerFactoryImplTest.class.getName());\n        logger.info(\"aaaa\");\n        agentLoggerFactory.mdc().put(\"newFactory\", \"newFactory\");\n        assertNotNull(MDC.get(\"newFactory\"));\n\n    }\n\n    @Test\n    public void build() {\n        LoggerFactoryImpl loggerFactory = LoggerFactoryImpl.build();\n        assertNotNull(loggerFactory);\n    }\n}\n"
  },
  {
    "path": "context/src/test/java/com/megaease/easeagent/context/log/LoggerImplTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.context.log;\n\nimport com.megaease.easeagent.plugin.api.logging.Logger;\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\npublic class LoggerImplTest {\n    @Test\n    public void test() {\n        LoggerFactoryImpl loggerFactory = LoggerFactoryImpl.build();\n        Logger logger = loggerFactory.getLogger(LoggerFactoryImplTest.class.getName());\n        assertTrue(logger instanceof LoggerImpl);\n    }\n}\n"
  },
  {
    "path": "context/src/test/java/com/megaease/easeagent/context/log/LoggerMdcTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.context.log;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\npublic class LoggerMdcTest {\n    LoggerFactoryImpl loggerFactory = LoggerFactoryImpl.build();\n    LoggerMdc mdc = new LoggerMdc(loggerFactory.factory().mdc());\n\n    @Test\n    public void put() {\n        mdc.put(\"testA\", \"testB\");\n        assertNull(org.slf4j.MDC.get(\"testA\"));\n        assertNotNull(mdc.get(\"testA\"));\n    }\n\n    @Test\n    public void remove() {\n        mdc.put(\"testA\", \"testB\");\n        assertNotNull(mdc.get(\"testA\"));\n        mdc.remove(\"testA\");\n        assertNull(mdc.get(\"testA\"));\n    }\n\n    @Test\n    public void get() {\n        mdc.put(\"testA\", \"testB\");\n        assertNull(org.slf4j.MDC.get(\"testA\"));\n        assertNotNull(mdc.get(\"testA\"));\n        org.slf4j.MDC.put(\"testB\", \"testB\");\n        assertNotNull(org.slf4j.MDC.get(\"testB\"));\n        assertNull(mdc.get(\"testB\"));\n    }\n}\n"
  },
  {
    "path": "context/src/test/resources/log4j2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Configuration status=\"WARN\">\n    <Appenders>\n        <Console name=\"Console\" target=\"SYSTEM_OUT\">\n            <!--<PatternLayout pattern=\"%d{HH:mm:ss.SSS} [%10.10t] %-p %10.10c{1} - %msg%n\"/>-->\n            <PatternLayout pattern=\"%d{HH:mm:ss.SSS} %-5level %class{36}:%L (%X{testMdc}) - %msg%xEx%n\"/>\n        </Console>\n    </Appenders>\n    <Loggers>\n        <Root level=\"INFO\">\n            <AppenderRef ref=\"Console\"/>\n        </Root>\n    </Loggers>\n</Configuration>\n"
  },
  {
    "path": "core/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  Copyright (c) 2017, MegaEase\n  All rights reserved.\n\n  Licensed under the Apache License, Version 2.0 (the \"License\");\n  you may not use this file except in compliance with the License.\n  You may obtain a copy of the License at\n\n      http://www.apache.org/licenses/LICENSE-2.0\n\n  Unless required by applicable law or agreed to in writing, software\n  distributed under the License is distributed on an \"AS IS\" BASIS,\n  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  See the License for the specific language governing permissions and\n  limitations under the License.\n-->\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>easeagent</artifactId>\n        <groupId>com.megaease.easeagent</groupId>\n        <version>2.3.0</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>core</artifactId>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.google.auto.service</groupId>\n            <artifactId>auto-service</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>net.bytebuddy</groupId>\n            <artifactId>byte-buddy</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>com.google.guava</groupId>\n            <artifactId>guava</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>plugin-api</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>context</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>config</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>report</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>httpserver</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>log4j2-api</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.slf4j</groupId>\n            <artifactId>slf4j-api</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.apache.commons</groupId>\n            <artifactId>commons-lang3</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>commons-codec</groupId>\n            <artifactId>commons-codec</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>com.fasterxml.jackson.core</groupId>\n            <artifactId>jackson-core</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>com.fasterxml.jackson.core</groupId>\n            <artifactId>jackson-databind</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>com.j256.simplejmx</groupId>\n            <artifactId>simplejmx</artifactId>\n            <version>1.19</version>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>javax.servlet</groupId>\n            <artifactId>javax.servlet-api</artifactId>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>io.projectreactor</groupId>\n            <artifactId>reactor-core</artifactId>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>net.bytebuddy</groupId>\n            <artifactId>byte-buddy-agent</artifactId>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>log4j2-mock</artifactId>\n            <version>${project.version}</version>\n            <scope>test</scope>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <resources>\n            <resource>\n                <directory>src/main/resources</directory>\n                <filtering>true</filtering>\n            </resource>\n        </resources>\n        <plugins>\n            <plugin>\n                <groupId>org.codehaus.mojo</groupId>\n                <artifactId>cobertura-maven-plugin</artifactId>\n                <configuration>\n                    <check/>\n                    <formats>\n                        <format>html</format>\n                    </formats>\n                    <instrumentation>\n                        <excludes>\n                            <exclude>**/AdviceTo*</exclude>\n                            <exclude>**/Configurable*</exclude>\n                        </excludes>\n                    </instrumentation>\n                </configuration>\n            </plugin>\n        </plugins>\n    </build>\n\n</project>\n"
  },
  {
    "path": "core/src/main/java/com/megaease/easeagent/core/AppendBootstrapClassLoaderSearch.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.core;\n\nimport com.google.common.base.Function;\nimport com.google.common.collect.Maps;\nimport com.google.common.io.CharStreams;\nimport com.google.common.io.Closeables;\nimport com.megaease.easeagent.plugin.AppendBootstrapLoader;\nimport net.bytebuddy.description.type.TypeDescription;\nimport net.bytebuddy.dynamic.ClassFileLocator;\nimport net.bytebuddy.dynamic.loading.ClassInjector;\nimport net.bytebuddy.pool.TypePool;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.lang.instrument.Instrumentation;\nimport java.net.URLConnection;\nimport java.nio.charset.StandardCharsets;\nimport java.security.AccessController;\nimport java.security.PrivilegedAction;\nimport java.util.Map;\nimport java.util.Set;\n\nimport static com.google.common.collect.FluentIterable.from;\nimport static com.google.common.collect.Maps.uniqueIndex;\nimport static java.util.Collections.list;\n\npublic final class AppendBootstrapClassLoaderSearch {\n    private static final File TMP_FILE = new File(\n        AccessController.doPrivileged(\n            new PrivilegedAction<String>() {\n                @Override\n                public String run() {\n                    return System.getProperty(\"java.io.tmpdir\");\n                }\n            })\n    );\n\n    static Set<String> by(Instrumentation inst, ClassInjector.UsingInstrumentation.Target target) throws IOException {\n        final Set<String> names = findClassAnnotatedAutoService(AppendBootstrapLoader.class);\n        ClassInjector.UsingInstrumentation.of(TMP_FILE, target, inst).inject(types(names));\n        return names;\n    }\n\n    private static Map<TypeDescription, byte[]> types(Set<String> names) {\n        final ClassLoader loader = AppendBootstrapClassLoaderSearch.class.getClassLoader();\n        final ClassFileLocator locator = ClassFileLocator.ForClassLoader.of(loader);\n        final TypePool pool = TypePool.Default.of(locator);\n\n        return Maps.transformValues(\n            uniqueIndex(names, input -> pool.describe(input).resolve()),\n            input -> {\n                try {\n                    return locator.locate(input).resolve();\n                } catch (IOException e) {\n                    throw new IllegalStateException(e);\n                }\n            });\n    }\n\n    private static Set<String> findClassAnnotatedAutoService(Class<?> cls) throws IOException {\n        final ClassLoader loader = AppendBootstrapClassLoaderSearch.class.getClassLoader();\n\n        return from(list(loader.getResources(\"META-INF/services/\" + cls.getName())))\n            .transform(input -> {\n                try {\n                    final URLConnection connection = input.openConnection();\n                    final InputStream stream = connection.getInputStream();\n                    return new InputStreamReader(stream, StandardCharsets.UTF_8);\n                } catch (IOException e) {\n                    throw new IllegalStateException(e);\n                }\n            })\n            .transformAndConcat((Function<InputStreamReader, Iterable<String>>) input -> {\n                try {\n                    return CharStreams.readLines(input);\n                } catch (IOException e) {\n                    throw new IllegalStateException(e);\n                } finally {\n                    Closeables.closeQuietly(input);\n                }\n\n            })\n            .toSet();\n    }\n\n    private AppendBootstrapClassLoaderSearch() {\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/megaease/easeagent/core/Bootstrap.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.core;\n\nimport com.megaease.easeagent.config.*;\nimport com.megaease.easeagent.context.ContextManager;\nimport com.megaease.easeagent.core.config.*;\nimport com.megaease.easeagent.core.info.AgentInfoFactory;\nimport com.megaease.easeagent.core.plugin.BaseLoader;\nimport com.megaease.easeagent.core.plugin.BridgeDispatcher;\nimport com.megaease.easeagent.core.plugin.PluginLoader;\nimport com.megaease.easeagent.httpserver.nano.AgentHttpHandlerProvider;\nimport com.megaease.easeagent.httpserver.nano.AgentHttpServer;\nimport com.megaease.easeagent.log4j2.Logger;\nimport com.megaease.easeagent.log4j2.LoggerFactory;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.api.metric.MetricProvider;\nimport com.megaease.easeagent.plugin.api.middleware.RedirectProcessor;\nimport com.megaease.easeagent.plugin.api.trace.TracingProvider;\nimport com.megaease.easeagent.plugin.bean.AgentInitializingBean;\nimport com.megaease.easeagent.plugin.bean.BeanProvider;\nimport com.megaease.easeagent.plugin.bridge.AgentInfo;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.report.AgentReport;\nimport com.megaease.easeagent.plugin.utils.common.StringUtils;\nimport com.megaease.easeagent.report.AgentReportAware;\nimport com.megaease.easeagent.report.DefaultAgentReport;\nimport lombok.SneakyThrows;\nimport net.bytebuddy.agent.builder.AgentBuilder;\nimport net.bytebuddy.description.type.TypeDescription;\nimport net.bytebuddy.dynamic.ClassFileLocator;\nimport net.bytebuddy.dynamic.DynamicType;\nimport net.bytebuddy.dynamic.loading.ClassInjector;\nimport net.bytebuddy.matcher.ElementMatcher;\nimport net.bytebuddy.utility.JavaModule;\n\nimport javax.management.MBeanServer;\nimport javax.management.ObjectName;\nimport java.lang.instrument.Instrumentation;\nimport java.lang.management.ManagementFactory;\nimport java.net.URLClassLoader;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.concurrent.TimeUnit;\n\nimport static net.bytebuddy.matcher.ElementMatchers.*;\n\n@SuppressWarnings(\"unused\")\npublic class Bootstrap {\n    private static final Logger LOGGER = LoggerFactory.getLogger(Bootstrap.class);\n\n    private static final String AGENT_SERVER_PORT_KEY = ConfigFactory.AGENT_SERVER_PORT;\n    private static final String AGENT_SERVER_ENABLED_KEY = ConfigFactory.AGENT_SERVER_ENABLED;\n\n    private static final String AGENT_MIDDLEWARE_UPDATE = \"easeagent.middleware.update\";\n\n    private static final int DEF_AGENT_SERVER_PORT = 9900;\n\n    static final String MX_BEAN_OBJECT_NAME = \"com.megaease.easeagent:type=ConfigManager\";\n\n    private static ContextManager contextManager;\n\n    private Bootstrap() {\n    }\n\n    @SneakyThrows\n    public static void start(String args, Instrumentation inst, String javaAgentJarPath) {\n        long begin = System.nanoTime();\n        System.setProperty(ConfigConst.AGENT_JAR_PATH, javaAgentJarPath);\n\n        // add bootstrap classes\n        Set<String> bootstrapClassSet = AppendBootstrapClassLoaderSearch.by(inst, ClassInjector.UsingInstrumentation.Target.BOOTSTRAP);\n        if (LOGGER.isDebugEnabled()) {\n            LOGGER.debug(\"Injected class: {}\", bootstrapClassSet);\n        }\n\n        // initiate configuration\n        String configPath = ConfigFactory.getConfigPath();\n        if (StringUtils.isEmpty(configPath)) {\n            configPath = args;\n        }\n\n        ClassLoader classLoader = Bootstrap.class.getClassLoader();\n        final AgentInfo agentInfo = AgentInfoFactory.loadAgentInfo(classLoader);\n        EaseAgent.agentInfo = agentInfo;\n        final GlobalConfigs conf = ConfigFactory.loadConfigs(configPath, classLoader);\n        wrapConfig(conf);\n\n        // loader check\n        GlobalAgentHolder.setAgentClassLoader((URLClassLoader) Bootstrap.class.getClassLoader());\n        EaseAgent.agentClassloader = GlobalAgentHolder::getAgentClassLoader;\n\n        // init Context/API\n        contextManager = ContextManager.build(conf);\n        EaseAgent.dispatcher = new BridgeDispatcher();\n\n        // initInnerHttpServer\n        initHttpServer(conf);\n\n        // redirection\n        RedirectProcessor.INSTANCE.init();\n\n        // reporter\n        final AgentReport agentReport = DefaultAgentReport.create(conf);\n        GlobalAgentHolder.setAgentReport(agentReport);\n        EaseAgent.agentReport = agentReport;\n\n        // load plugins\n        AgentBuilder builder = getAgentBuilder(conf, false);\n        builder = PluginLoader.load(builder, conf);\n\n        // provider & beans\n        loadProvider(conf, agentReport);\n\n        long installBegin = System.currentTimeMillis();\n        builder.installOn(inst);\n        LOGGER.info(\"installBegin use time: {}ms\", (System.currentTimeMillis() - installBegin));\n\n        LOGGER.info(\"Initialization has took {}ns\", TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - begin));\n    }\n\n    private static void initHttpServer(Configs conf) {\n        // inner httpserver\n        Integer port = conf.getInt(AGENT_SERVER_PORT_KEY);\n        if (port == null) {\n            port = DEF_AGENT_SERVER_PORT;\n        }\n        String portStr = System.getProperty(AGENT_SERVER_PORT_KEY, String.valueOf(port));\n        port = Integer.parseInt(portStr);\n\n        AgentHttpServer agentHttpServer = new AgentHttpServer(port);\n\n        boolean httpServerEnabled = conf.getBoolean(AGENT_SERVER_ENABLED_KEY);\n        if (httpServerEnabled) {\n            agentHttpServer.startServer();\n            LOGGER.info(\"start agent http server on port:{}\", port);\n        }\n        GlobalAgentHolder.setAgentHttpServer(agentHttpServer);\n\n        // add httpHandler\n        agentHttpServer.addHttpRoute(new ServiceUpdateAgentHttpHandler());\n        agentHttpServer.addHttpRoute(new CanaryUpdateAgentHttpHandler());\n        agentHttpServer.addHttpRoute(new CanaryListUpdateAgentHttpHandler());\n        agentHttpServer.addHttpRoute(new PluginPropertyHttpHandler());\n        agentHttpServer.addHttpRoute(new PluginPropertiesHttpHandler());\n    }\n\n    private static void loadProvider(final Configs conf, final AgentReport agentReport) {\n        List<BeanProvider> providers = BaseLoader.loadOrdered(BeanProvider.class);\n        providers.forEach(input -> provider(input, conf, agentReport));\n    }\n\n    private static void provider(final BeanProvider beanProvider, final Configs conf, final AgentReport agentReport) {\n        if (beanProvider instanceof ConfigAware) {\n            ((ConfigAware) beanProvider).setConfig(conf);\n        }\n        if (beanProvider instanceof AgentReportAware) {\n            ((AgentReportAware) beanProvider).setAgentReport(agentReport);\n        }\n\n        if (beanProvider instanceof AgentHttpHandlerProvider) {\n            GlobalAgentHolder.getAgentHttpServer()\n                .addHttpRoutes(((AgentHttpHandlerProvider) beanProvider).getAgentHttpHandlers());\n        }\n        if (beanProvider instanceof AgentInitializingBean) {\n            ((AgentInitializingBean) beanProvider).afterPropertiesSet();\n        }\n        if (beanProvider instanceof TracingProvider) {\n            TracingProvider tracingProvider = (TracingProvider) beanProvider;\n            contextManager.setTracing(tracingProvider);\n        }\n        if (beanProvider instanceof MetricProvider) {\n            contextManager.setMetric((MetricProvider) beanProvider);\n        }\n    }\n\n    public static AgentBuilder getAgentBuilder(Configs config, boolean test) {\n        // config may use to add some classes to be ignored in future\n        long buildBegin = System.currentTimeMillis();\n        AgentBuilder builder = new AgentBuilder.Default()\n            .with(LISTENER)\n            .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)\n            .with(AgentBuilder.InitializationStrategy.NoOp.INSTANCE)\n            .with(AgentBuilder.TypeStrategy.Default.REDEFINE)\n            .with(AgentBuilder.LocationStrategy.ForClassLoader.STRONG\n                .withFallbackTo(ClassFileLocator.ForClassLoader.ofSystemLoader()));\n        AgentBuilder.Ignored ignore = builder.ignore(isSynthetic())\n            .or(nameStartsWith(\"sun.\").and(not(nameStartsWith(\"sun.net.www.protocol.http\")) ))\n            .or(nameStartsWith(\"com.sun.\"))\n            .or(nameStartsWith(\"brave.\"))\n            .or(nameStartsWith(\"zipkin2.\"))\n            .or(nameStartsWith(\"com.fasterxml\"))\n            .or(nameStartsWith(\"org.apache.logging\")\n                .and(not(hasSuperClass(named(\"org.apache.logging.log4j.spi.AbstractLogger\")))))\n            .or(nameStartsWith(\"kotlin.\"))\n            .or(nameStartsWith(\"javax.\"))\n            .or(nameStartsWith(\"net.bytebuddy.\"))\n            .or(nameStartsWith(\"com\\\\.sun\\\\.proxy\\\\.\\\\$Proxy.+\"))\n            .or(nameStartsWith(\"java\\\\.lang\\\\.invoke\\\\.BoundMethodHandle\\\\$Species_L.+\"))\n            .or(nameStartsWith(\"org.junit.\"))\n            .or(nameStartsWith(\"junit.\"))\n            .or(nameStartsWith(\"com.intellij.\"));\n\n        // config used here to avoid warning of unused\n        if (!test && config != null) {\n            builder = ignore\n                .or(nameStartsWith(\"com.megaease.easeagent.\"));\n        } else {\n            builder = ignore;\n        }\n        LOGGER.info(\"AgentBuilder use time: {}\", (System.currentTimeMillis() - buildBegin));\n        return builder;\n    }\n\n    @SneakyThrows\n    static void registerMBeans(ConfigManagerMXBean conf) {\n        long begin = System.currentTimeMillis();\n        MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();\n        ObjectName mxBeanName = new ObjectName(MX_BEAN_OBJECT_NAME);\n        ClassLoader customClassLoader = Thread.currentThread().getContextClassLoader();\n        mbs.registerMBean(conf, mxBeanName);\n        LOGGER.info(\"Register {} as MBean {}, use time: {}\",\n            conf.getClass().getName(), mxBeanName, (System.currentTimeMillis() - begin));\n    }\n\n    private static ElementMatcher<ClassLoader> protectedLoaders() {\n        return isBootstrapClassLoader().or(is(Bootstrap.class.getClassLoader()));\n    }\n\n    private static void wrapConfig(GlobalConfigs configs) {\n        WrappedConfigManager wrappedConfigManager = new WrappedConfigManager(Bootstrap.class.getClassLoader(), configs);\n        registerMBeans(wrappedConfigManager);\n        GlobalAgentHolder.setWrappedConfigManager(wrappedConfigManager);\n    }\n\n    private static final AgentBuilder.Listener LISTENER = new AgentBuilder.Listener() {\n        @Override\n        public void onDiscovery(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded) {\n            // ignored\n        }\n\n        @Override\n        public void onTransformation(TypeDescription td, ClassLoader ld, JavaModule m, boolean loaded, DynamicType dt) {\n            LOGGER.debug(\"onTransformation: {} loaded: {} from classLoader {}\", td, loaded, ld);\n        }\n\n        @Override\n        public void onIgnored(TypeDescription td, ClassLoader ld, JavaModule m, boolean loaded) {\n            // ignored\n        }\n\n        @Override\n        public void onError(String name, ClassLoader ld, JavaModule m, boolean loaded, Throwable error) {\n            LOGGER.debug(\"Just for Debug-log, transform ends exceptionally, \" +\n                    \"which is sometimes normal and sometimes there is an error: {} error:{} loaded: {} from classLoader {}\",\n                name, error, loaded, ld);\n        }\n\n        @Override\n        public void onComplete(String name, ClassLoader ld, JavaModule m, boolean loaded) {\n            // ignored\n        }\n    };\n}\n"
  },
  {
    "path": "core/src/main/java/com/megaease/easeagent/core/GlobalAgentHolder.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.core;\n\nimport com.megaease.easeagent.config.WrappedConfigManager;\nimport com.megaease.easeagent.httpserver.nano.AgentHttpServer;\nimport com.megaease.easeagent.plugin.report.AgentReport;\n\nimport java.net.URLClassLoader;\n\npublic class GlobalAgentHolder {\n    private static WrappedConfigManager wrappedConfigManager;\n    private static AgentHttpServer agentHttpServer;\n    private static AgentReport agentReport;\n    private static URLClassLoader agentLoader;\n\n    private GlobalAgentHolder() {}\n\n    public static void setWrappedConfigManager(WrappedConfigManager config) {\n        wrappedConfigManager = config;\n    }\n\n    public static WrappedConfigManager getWrappedConfigManager() {\n        return wrappedConfigManager;\n    }\n\n    public static void setAgentHttpServer(AgentHttpServer server) {\n        agentHttpServer = server;\n    }\n\n    public static AgentHttpServer getAgentHttpServer() {\n        return agentHttpServer;\n    }\n\n    public static void setAgentReport(AgentReport report) {\n        agentReport = report;\n    }\n\n    public static AgentReport getAgentReport() {\n        return agentReport;\n    }\n\n    public static void setAgentClassLoader(URLClassLoader loader) {\n        agentLoader = loader;\n    }\n\n    public static URLClassLoader getAgentClassLoader() {\n        return agentLoader;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/megaease/easeagent/core/config/CanaryListUpdateAgentHttpHandler.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.core.config;\n\nimport com.megaease.easeagent.core.GlobalAgentHolder;\nimport com.megaease.easeagent.httpserver.nano.AgentHttpServer;\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.response.Response;\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.response.Status;\nimport com.megaease.easeagent.log4j2.Logger;\nimport com.megaease.easeagent.log4j2.LoggerFactory;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.atomic.AtomicInteger;\n\npublic class CanaryListUpdateAgentHttpHandler extends ConfigsUpdateAgentHttpHandler {\n    private static final Logger LOGGER = LoggerFactory.getLogger(CanaryListUpdateAgentHttpHandler.class);\n    public static final AtomicInteger LAST_COUNT = new AtomicInteger(0);\n\n    public CanaryListUpdateAgentHttpHandler() {\n        this.mxBeanConfig = GlobalAgentHolder.getWrappedConfigManager();\n    }\n\n    @Override\n    public String getPath() {\n        return \"/config-global-transmission\";\n    }\n\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public Response processJsonConfig(Map<String, Object> map, Map<String, String> urlParams) {\n        LOGGER.info(\"call /config-global-transmission. configs: {}\", map);\n        synchronized (LAST_COUNT) {\n            List<String> headers = (List<String>) map.get(\"headers\");\n            Map<String, String> config = new HashMap<>();\n            for (int i = 0; i < headers.size(); i++) {\n                config.put(\"easeagent.progress.forwarded.headers.global.transmission.\" + i, headers.get(i));\n            }\n            int last = LAST_COUNT.get();\n            if (headers.size() < last) {\n                for (int i = headers.size(); i < last; i++) {\n                    config.put(\"easeagent.progress.forwarded.headers.global.transmission.\" + i, \"\");\n                }\n            }\n            LAST_COUNT.set(headers.size());\n            this.mxBeanConfig.updateConfigs(config);\n        }\n        return Response.newFixedLengthResponse(Status.OK, AgentHttpServer.JSON_TYPE, (String) null);\n    }\n\n    @Override\n    public Response processConfig(Map<String, String> config, Map<String, String> urlParams, String version) {\n        this.mxBeanConfig.updateCanary2(config, version);\n        return null;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/megaease/easeagent/core/config/CanaryUpdateAgentHttpHandler.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.core.config;\n\nimport com.megaease.easeagent.core.GlobalAgentHolder;\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.response.Response;\n\nimport java.util.Map;\n\npublic class CanaryUpdateAgentHttpHandler extends ConfigsUpdateAgentHttpHandler {\n    public CanaryUpdateAgentHttpHandler() {\n        this.mxBeanConfig = GlobalAgentHolder.getWrappedConfigManager();\n    }\n\n    @Override\n    public String getPath() {\n        return \"/config-canary\";\n    }\n\n    @Override\n    public Response processConfig(Map<String, String> config, Map<String, String> urlParams, String version) {\n        this.mxBeanConfig.updateCanary2(config, version);\n        return null;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/megaease/easeagent/core/config/ConfigsUpdateAgentHttpHandler.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.core.config;\n\nimport com.megaease.easeagent.config.ConfigManagerMXBean;\nimport com.megaease.easeagent.httpserver.nano.AgentHttpHandler;\nimport com.megaease.easeagent.httpserver.nano.AgentHttpServer;\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.IHTTPSession;\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.response.Response;\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.response.Status;\nimport com.megaease.easeagent.httpserver.nanohttpd.router.RouterNanoHTTPD;\nimport com.megaease.easeagent.plugin.utils.common.JsonUtil;\nimport lombok.SneakyThrows;\nimport org.apache.commons.lang3.StringUtils;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\npublic abstract class ConfigsUpdateAgentHttpHandler extends AgentHttpHandler {\n    public abstract Response processConfig(Map<String, String> config, Map<String, String> urlParams, String version);\n\n    protected ConfigManagerMXBean mxBeanConfig;\n\n    static Map<String, String> toConfigMap(Map<String, Object> map) {\n        Map<String, String> config = new HashMap<>(Math.max(map.size(), 8));\n        map.forEach((s, o) -> config.put(s, o.toString()));\n        return config;\n    }\n\n    @SneakyThrows\n    @Override\n    public Response process(RouterNanoHTTPD.UriResource uriResource, Map<String, String> urlParams, IHTTPSession session) {\n        String body = this.buildRequestBody(session);\n        if (StringUtils.isEmpty(body)) {\n            return Response.newFixedLengthResponse(Status.BAD_REQUEST, AgentHttpServer.JSON_TYPE, (String) null);\n        }\n        Map<String, Object> map = JsonUtil.toMap(body);\n        if (map == null) {\n            return Response.newFixedLengthResponse(Status.BAD_REQUEST, AgentHttpServer.JSON_TYPE, (String) null);\n        }\n        return processJsonConfig(map, urlParams);\n    }\n\n    public Response processJsonConfig(Map<String, Object> map, Map<String, String> urlParams) {\n//        String version = (String) map.remove(\"version\");\n//        if (version == null) {\n//            return Response.newFixedLengthResponse(Status.BAD_REQUEST, AgentHttpServer.JSON_TYPE, (String) null);\n//        }\n        Map<String, String> config = toConfigMap(map);\n        Response response = processConfig(config, urlParams, null);\n        if (response != null) {\n            return response;\n        }\n        return Response.newFixedLengthResponse(Status.OK, AgentHttpServer.JSON_TYPE, (String) null);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/megaease/easeagent/core/config/PluginPropertiesHttpHandler.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.core.config;\n\nimport com.megaease.easeagent.config.ConfigUtils;\nimport com.megaease.easeagent.core.GlobalAgentHolder;\nimport com.megaease.easeagent.httpserver.nano.AgentHttpServer;\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.response.Response;\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.response.Status;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Objects;\n\npublic class PluginPropertiesHttpHandler extends ConfigsUpdateAgentHttpHandler {\n    public PluginPropertiesHttpHandler() {\n        this.mxBeanConfig = GlobalAgentHolder.getWrappedConfigManager();\n    }\n\n    @Override\n    public String getPath() {\n        return \"/plugins/domains/:domain/namespaces/:namespace/:id/properties\";\n    }\n\n    @Override\n    public Response processConfig(Map<String, String> config, Map<String, String> urlParams, String version) {\n        try {\n            String domain = ConfigUtils.requireNonEmpty(urlParams.get(\"domain\"), \"urlParams.domain must not be null and empty.\");\n            String namespace = ConfigUtils.requireNonEmpty(urlParams.get(\"namespace\"), \"urlParams.namespace must not be null and empty.\");\n            String id = ConfigUtils.requireNonEmpty(urlParams.get(\"id\"), \"urlParams.id must not be null and empty.\");\n            Map<String, String> changeConfig = new HashMap<>();\n            for (Map.Entry<String, String> propertyEntry : config.entrySet()) {\n                String property = ConfigUtils.buildPluginProperty(domain,\n                    namespace,\n                    id,\n                    ConfigUtils.requireNonEmpty(propertyEntry.getKey(), \"body.key must not be null and empty.\"));\n                String value = Objects.requireNonNull(propertyEntry.getValue(), String.format(\"body.%s must not be null.\", propertyEntry.getKey()));\n                changeConfig.put(property, value);\n            }\n            this.mxBeanConfig.updateService2(changeConfig, version);\n            return null;\n        } catch (Exception e) {\n            return Response.newFixedLengthResponse(Status.BAD_REQUEST, AgentHttpServer.JSON_TYPE, e.getMessage());\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/megaease/easeagent/core/config/PluginPropertyHttpHandler.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.core.config;\n\nimport com.megaease.easeagent.config.ConfigManagerMXBean;\nimport com.megaease.easeagent.config.ConfigUtils;\nimport com.megaease.easeagent.core.GlobalAgentHolder;\nimport com.megaease.easeagent.httpserver.nano.AgentHttpHandler;\nimport com.megaease.easeagent.httpserver.nano.AgentHttpServer;\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.IHTTPSession;\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.response.Response;\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.response.Status;\nimport com.megaease.easeagent.httpserver.nanohttpd.router.RouterNanoHTTPD;\nimport lombok.SneakyThrows;\n\nimport java.util.Collections;\nimport java.util.Map;\nimport java.util.Objects;\n\npublic class PluginPropertyHttpHandler extends AgentHttpHandler {\n    ConfigManagerMXBean mxBeanConfig;\n\n    public PluginPropertyHttpHandler() {\n        this.mxBeanConfig = GlobalAgentHolder.getWrappedConfigManager();\n        methods = Collections.singleton(\n            com.megaease.easeagent.httpserver.nanohttpd.protocols.http.request.Method.GET);\n    }\n\n    @Override\n    public String getPath() {\n        return \"/plugins/domains/:domain/namespaces/:namespace/:id/properties/:property/:value/:version\";\n    }\n\n    @SneakyThrows\n    @Override\n    public Response process(RouterNanoHTTPD.UriResource uriResource, Map<String, String> urlParams, IHTTPSession session) {\n        String version = urlParams.get(\"version\");\n        if (version == null) {\n            return Response.newFixedLengthResponse(Status.BAD_REQUEST, AgentHttpServer.JSON_TYPE, (String) null);\n        }\n        try {\n            String property = ConfigUtils.buildPluginProperty(\n                ConfigUtils.requireNonEmpty(urlParams.get(\"domain\"), \"urlParams.domain must not be null and empty.\"),\n                ConfigUtils.requireNonEmpty(urlParams.get(\"namespace\"), \"urlParams.namespace must not be null and empty.\"),\n                ConfigUtils.requireNonEmpty(urlParams.get(\"id\"), \"urlParams.id must not be null and empty.\"),\n                ConfigUtils.requireNonEmpty(urlParams.get(\"property\"), \"urlParams.property must not be null and empty.\"));\n            String value = Objects.requireNonNull(urlParams.get(\"value\"), \"urlParams.value must not be null.\");\n            this.mxBeanConfig.updateService2(Collections.singletonMap(property, value), version);\n            return Response.newFixedLengthResponse(Status.OK, AgentHttpServer.JSON_TYPE, (String) null);\n        } catch (Exception e) {\n            return Response.newFixedLengthResponse(Status.BAD_REQUEST, AgentHttpServer.JSON_TYPE, e.getMessage());\n        }\n\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/megaease/easeagent/core/config/ServiceUpdateAgentHttpHandler.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.core.config;\n\nimport com.megaease.easeagent.config.CompatibilityConversion;\nimport com.megaease.easeagent.core.GlobalAgentHolder;\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.response.Response;\n\nimport java.util.Map;\n\npublic class ServiceUpdateAgentHttpHandler extends ConfigsUpdateAgentHttpHandler {\n    public ServiceUpdateAgentHttpHandler() {\n        this.mxBeanConfig = GlobalAgentHolder.getWrappedConfigManager();\n    }\n\n    @Override\n    public String getPath() {\n        return \"/config\";\n    }\n\n    @Override\n    public Response processConfig(Map<String, String> config, Map<String, String> urlParams, String version) {\n        this.mxBeanConfig.updateService2(CompatibilityConversion.transform(config), version);\n        return null;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/megaease/easeagent/core/health/HealthProvider.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.core.health;\n\nimport com.megaease.easeagent.plugin.api.config.Config;\nimport com.megaease.easeagent.config.ConfigAware;\nimport com.megaease.easeagent.httpserver.nano.AgentHttpHandler;\nimport com.megaease.easeagent.httpserver.nano.AgentHttpHandlerProvider;\nimport com.megaease.easeagent.httpserver.nano.AgentHttpServer;\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.IHTTPSession;\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.response.IStatus;\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.response.Response;\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.response.Status;\nimport com.megaease.easeagent.httpserver.nanohttpd.router.RouterNanoHTTPD;\nimport com.megaease.easeagent.plugin.bean.BeanProvider;\nimport com.megaease.easeagent.plugin.bean.AgentInitializingBean;\nimport com.megaease.easeagent.plugin.api.health.AgentHealth;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\n\npublic class HealthProvider implements AgentHttpHandlerProvider, ConfigAware, AgentInitializingBean, BeanProvider {\n    private static final String EASEAGENT_HEALTH_READINESS_ENABLED = \"easeagent.health.readiness.enabled\";\n\n    private Config config;\n\n    @Override\n    public List<AgentHttpHandler> getAgentHttpHandlers() {\n        List<AgentHttpHandler> list = new ArrayList<>();\n        list.add(new HealthAgentHttpHandler());\n        list.add(new LivenessAgentHttpHandler());\n        list.add(new ReadinessAgentHttpHandler());\n        return list;\n    }\n\n    @Override\n    public void setConfig(Config config) {\n        this.config = config;\n    }\n\n    @Override\n    public void afterPropertiesSet() {\n        AgentHealth.setReadinessEnabled(this.config.getBoolean(EASEAGENT_HEALTH_READINESS_ENABLED));\n    }\n\n\n    public static class HealthAgentHttpHandler extends AgentHttpHandler {\n\n        @Override\n        public String getPath() {\n            return \"/health\";\n        }\n\n        @Override\n        public Response process(RouterNanoHTTPD.UriResource uriResource, Map<String, String> urlParams, IHTTPSession session) {\n            return Response.newFixedLengthResponse(Status.OK, AgentHttpServer.JSON_TYPE, (String) null);\n        }\n    }\n\n    public static class LivenessAgentHttpHandler extends HealthAgentHttpHandler {\n\n        @Override\n        public String getPath() {\n            return \"/health/liveness\";\n        }\n\n    }\n\n    public static class ReadinessAgentHttpHandler extends HealthAgentHttpHandler {\n\n        @Override\n        public String getPath() {\n            return \"/health/readiness\";\n        }\n\n        @Override\n        public Response process(RouterNanoHTTPD.UriResource uriResource, Map<String, String> urlParams, IHTTPSession session) {\n            if (AgentHealth.INSTANCE.isReadinessEnabled()) {\n                if (AgentHealth.INSTANCE.isReady()) {\n                    return Response.newFixedLengthResponse(Status.OK, AgentHttpServer.JSON_TYPE, (String) null);\n                }\n\n                return Response.newFixedLengthResponse(HStatus.SERVICE_UNAVAILABLE,\n                    AgentHttpServer.JSON_TYPE, (String) null);\n            }\n            return super.process(uriResource, urlParams, session);\n        }\n    }\n\n    enum HStatus implements IStatus {\n\n        /**\n         * service unavailable\n         */\n        SERVICE_UNAVAILABLE(503, \"Service Unavailable\"),\n        ;\n        private final int requestStatus;\n\n        private final String description;\n\n        HStatus(int requestStatus, String description) {\n            this.requestStatus = requestStatus;\n            this.description = description;\n        }\n\n        @Override\n        public String getDescription() {\n            return description;\n        }\n\n        @Override\n        public int getRequestStatus() {\n            return requestStatus;\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/megaease/easeagent/core/info/AgentInfoFactory.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.core.info;\n\nimport com.megaease.easeagent.log4j2.Logger;\nimport com.megaease.easeagent.log4j2.LoggerFactory;\nimport com.megaease.easeagent.plugin.bridge.AgentInfo;\n\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\n\npublic class AgentInfoFactory {\n    private static final Logger LOGGER = LoggerFactory.getLogger(AgentInfoFactory.class);\n    public static final String AGENT_TYPE = \"EaseAgent\";\n    private static final String VERSION_FILE = \"version.txt\";\n\n\n    public static AgentInfo loadAgentInfo(ClassLoader classLoader) {\n        return new AgentInfo(AGENT_TYPE, loadVersion(classLoader, VERSION_FILE));\n    }\n\n\n    private static String loadVersion(ClassLoader classLoader, String file) {\n        try (InputStream in = classLoader.getResourceAsStream(file)) {\n            BufferedReader reader = new BufferedReader(new InputStreamReader(in));\n            String version = reader.readLine();\n            reader.close();\n            return version;\n        } catch (IOException e) {\n            LOGGER.warn(\"Load config file:{} by classloader:{} failure: {}\", file, classLoader.toString(), e);\n        }\n        return \"\";\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/megaease/easeagent/core/info/AgentInfoProvider.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.core.info;\n\nimport com.megaease.easeagent.core.utils.JsonUtil;\nimport com.megaease.easeagent.httpserver.nano.AgentHttpHandler;\nimport com.megaease.easeagent.httpserver.nano.AgentHttpHandlerProvider;\nimport com.megaease.easeagent.httpserver.nano.AgentHttpServer;\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.IHTTPSession;\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.response.Response;\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.response.Status;\nimport com.megaease.easeagent.httpserver.nanohttpd.router.RouterNanoHTTPD;\nimport com.megaease.easeagent.plugin.bean.BeanProvider;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\n\npublic class AgentInfoProvider implements AgentHttpHandlerProvider, BeanProvider {\n\n    @Override\n    public List<AgentHttpHandler> getAgentHttpHandlers() {\n        List<AgentHttpHandler> list = new ArrayList<>();\n        list.add(new AgentInfoHttpHandler());\n        return list;\n    }\n\n    public static class AgentInfoHttpHandler extends AgentHttpHandler {\n\n        @Override\n        public String getPath() {\n            return \"/agent-info\";\n        }\n\n        @Override\n        public Response process(RouterNanoHTTPD.UriResource uriResource, Map<String, String> urlParams, IHTTPSession session) {\n            return Response.newFixedLengthResponse(Status.OK, AgentHttpServer.JSON_TYPE, JsonUtil.toJson(EaseAgent.getAgentInfo()));\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/megaease/easeagent/core/plugin/BaseLoader.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.core.plugin;\n\nimport com.megaease.easeagent.plugin.Ordered;\nimport com.megaease.easeagent.plugin.api.logging.Logger;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\n\nimport java.util.ArrayList;\nimport java.util.Comparator;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.ServiceLoader;\n\npublic class BaseLoader {\n    private static final Logger logger = EaseAgent.loggerFactory.getLogger(BaseLoader.class);\n\n    public static <T> List<T> load(Class<T> serviceClass) {\n        List<T> result = new ArrayList<>();\n        java.util.ServiceLoader<T> services = ServiceLoader.load(serviceClass);\n        for (Iterator<T> it = services.iterator(); it.hasNext(); ) {\n            try {\n                result.add(it.next());\n            } catch (UnsupportedClassVersionError e) {\n                logger.info(\"Unable to load class: {}\", e.getMessage());\n                logger.info(\"Please check the plugin compile Java version configuration,\"\n                    + \" and it should not latter than current JVM runtime\");\n            }\n        }\n        return result;\n    }\n\n    public static <T extends Ordered> List<T> loadOrdered(Class<T> serviceClass) {\n        List<T> result = load(serviceClass);\n        result.sort(Comparator.comparing(Ordered::order));\n        return result;\n    }\n\n    private BaseLoader() {}\n}\n"
  },
  {
    "path": "core/src/main/java/com/megaease/easeagent/core/plugin/BridgeDispatcher.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.core.plugin;\n\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.InitializeContext;\nimport com.megaease.easeagent.plugin.api.dispatcher.IDispatcher;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\n\npublic class BridgeDispatcher implements IDispatcher {\n    @Override\n    public void enter(int chainIndex, MethodInfo info) {\n        InitializeContext context = EaseAgent.initializeContextSupplier\n            .getContext();\n        if (context.isNoop()) {\n            return;\n        }\n        Dispatcher.enter(chainIndex, info, context);\n    }\n\n    @Override\n    public Object exit(int chainIndex, MethodInfo methodInfo,\n                     Context context, Object result, Throwable e) {\n        if (context.isNoop() || !(context instanceof InitializeContext)) {\n            return result;\n        }\n        InitializeContext iContext = (InitializeContext)context;\n        methodInfo.throwable(e);\n        methodInfo.retValue(result);\n        Dispatcher.exit(chainIndex, methodInfo, iContext);\n        if (methodInfo.isChanged()) {\n            result = methodInfo.getRetValue();\n        }\n\n        return result;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/megaease/easeagent/core/plugin/CommonInlineAdvice.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.core.plugin;\n\nimport com.megaease.easeagent.core.plugin.annotation.Index;\nimport com.megaease.easeagent.core.plugin.transformer.advice.AgentAdvice.NoExceptionHandler;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.api.InitializeContext;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport net.bytebuddy.asm.Advice;\nimport net.bytebuddy.implementation.bytecode.assign.Assigner;\n\n/**\n * uniform interceptor entrance\n * get interceptor chain thought index generated when transform\n */\n// suppress all warnings for the code at these warnings is intentionally written this way\n@SuppressWarnings(\"all\")\npublic class CommonInlineAdvice {\n    private static final String CONTEXT = \"easeagent_context\";\n    private static final String POS = \"easeagent_pos\";\n\n    @Advice.OnMethodEnter(suppress = NoExceptionHandler.class)\n    public static MethodInfo enter(@Index int index,\n                                   @Advice.This(optional = true) Object invoker,\n                                   @Advice.Origin(\"#t\") String type,\n                                   @Advice.Origin(\"#m\") String method,\n                                   @Advice.AllArguments(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object[] args,\n                                   @Advice.Local(CONTEXT) InitializeContext context) {\n        context = EaseAgent.initializeContextSupplier.getContext();\n        if (context.isNoop()) {\n            return null;\n        }\n\n        MethodInfo methodInfo = MethodInfo.builder()\n            .invoker(invoker)\n            .type(type)\n            .method(method)\n            .args(args)\n            .build();\n        Dispatcher.enter(index, methodInfo, context);\n        if (methodInfo.isChanged()) {\n            args = methodInfo.getArgs();\n        }\n\n        return methodInfo;\n    }\n\n    @Advice.OnMethodExit(onThrowable = Exception.class, suppress = NoExceptionHandler.class)\n    // @Advice.OnMethodExit(suppress = NoExceptionHandler.class)\n    public static void exit(@Index int index,\n                            @Advice.Enter MethodInfo methodInfo,\n                            @Advice.Return(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object result,\n                            @Advice.Thrown(readOnly = false, typing = Assigner.Typing.DYNAMIC) Throwable throwable,\n                            @Advice.Local(CONTEXT) InitializeContext context) {\n        if (context.isNoop()) {\n            return;\n        }\n        methodInfo.throwable(throwable);\n        methodInfo.retValue(result);\n        Dispatcher.exit(index, methodInfo, context);\n        if (methodInfo.isChanged()) {\n            result = methodInfo.getRetValue();\n        }\n    }\n\n    @Advice.OnMethodExit(suppress = NoExceptionHandler.class)\n    public static void exit(@Index int index,\n                            @Advice.This(optional = true) Object invoker,\n                            @Advice.Enter MethodInfo methodInfo,\n                            @Advice.Return(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object result,\n                            @Advice.Local(CONTEXT) InitializeContext context) {\n        if (context.isNoop()) {\n            return;\n        }\n        methodInfo.setInvoker(invoker);\n        methodInfo.retValue(result);\n        Dispatcher.exit(index, methodInfo, context);\n        if (methodInfo.isChanged()) {\n            result = methodInfo.getRetValue();\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/megaease/easeagent/core/plugin/Dispatcher.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.core.plugin;\n\nimport com.google.auto.service.AutoService;\nimport com.megaease.easeagent.core.utils.AgentArray;\nimport com.megaease.easeagent.core.utils.ContextUtils;\nimport com.megaease.easeagent.plugin.AppendBootstrapLoader;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.api.InitializeContext;\nimport com.megaease.easeagent.plugin.interceptor.AgentInterceptorChain;\n\n@AutoService(AppendBootstrapLoader.class)\npublic final class Dispatcher {\n\n    private Dispatcher() {\n    }\n\n    static AgentArray<AgentInterceptorChain> chains = new AgentArray<>();\n\n    /**\n     * for chains only modified during related class loading process,\n     * so it doesn't need to consider updating process\n     * otherwise, chain should store in context, avoiding changed during enter and exit\n     */\n    public static void enter(int index, MethodInfo info, InitializeContext ctx) {\n        AgentInterceptorChain chain = chains.getUncheck(index);\n        int pos = 0;\n        ContextUtils.setBeginTime(ctx);\n        chain.doBefore(info, pos, ctx);\n    }\n\n    public static Object exit(int index, MethodInfo info, InitializeContext ctx) {\n        AgentInterceptorChain chain = chains.getUncheck(index);\n        int pos = chain.size() - 1;\n        ContextUtils.setEndTime(ctx);\n        return chain.doAfter(info, pos, ctx);\n    }\n\n    public static AgentInterceptorChain register(int index, AgentInterceptorChain chain) {\n        return chains.putIfAbsent(index, chain);\n    }\n\n    // for interceptor\n    public static AgentInterceptorChain getChain(int index) {\n        return chains.get(index);\n    }\n\n    public static boolean updateChain(int index, AgentInterceptorChain chain) {\n        return chains.replace(index, chain) != null;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/megaease/easeagent/core/plugin/PluginLoader.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.core.plugin;\n\nimport com.megaease.easeagent.config.ConfigUtils;\nimport com.megaease.easeagent.config.Configs;\nimport com.megaease.easeagent.core.plugin.matcher.ClassTransformation;\nimport com.megaease.easeagent.core.plugin.matcher.MethodTransformation;\nimport com.megaease.easeagent.core.plugin.registry.PluginRegistry;\nimport com.megaease.easeagent.core.plugin.transformer.CompoundPluginTransformer;\nimport com.megaease.easeagent.core.plugin.transformer.DynamicFieldTransformer;\nimport com.megaease.easeagent.core.plugin.transformer.ForAdviceTransformer;\nimport com.megaease.easeagent.core.plugin.transformer.TypeFieldTransformer;\nimport com.megaease.easeagent.log4j2.Logger;\nimport com.megaease.easeagent.log4j2.LoggerFactory;\nimport com.megaease.easeagent.plugin.AgentPlugin;\nimport com.megaease.easeagent.plugin.CodeVersion;\nimport com.megaease.easeagent.plugin.Ordered;\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.field.AgentDynamicFieldAccessor;\nimport com.megaease.easeagent.plugin.interceptor.InterceptorProvider;\nimport com.megaease.easeagent.plugin.utils.common.StringUtils;\nimport net.bytebuddy.agent.builder.AgentBuilder;\n\nimport java.util.*;\nimport java.util.stream.Collectors;\nimport java.util.stream.StreamSupport;\n\npublic class PluginLoader {\n\n    private PluginLoader() {\n    }\n\n    static Logger log = LoggerFactory.getLogger(PluginLoader.class);\n\n    public static AgentBuilder load(AgentBuilder ab, Configs conf) {\n        pluginLoad();\n        pointsLoad(conf);\n        providerLoad();\n        Set<ClassTransformation> sortedTransformations = classTransformationLoad();\n\n        for (ClassTransformation transformation : sortedTransformations) {\n            ab = ab.type(transformation.getClassMatcher(), transformation.getClassloaderMatcher())\n                .transform(compound(transformation.isHasDynamicField(), transformation.getMethodTransformations(), transformation.getTypeFieldAccessor()));\n        }\n        return ab;\n    }\n\n    public static void providerLoad() {\n        for (InterceptorProvider provider : BaseLoader.load(InterceptorProvider.class)) {\n            String pointsClassName = PluginRegistry.getPointsClassName(provider.getAdviceTo());\n            Points points = PluginRegistry.getPoints(pointsClassName);\n            if (points == null) {\n                log.debug(\"Unload provider:{}, can not found Points<{}>\", provider.getClass().getName(), pointsClassName);\n                continue;\n            } else {\n                log.debug(\"Loading provider:{}\", provider.getClass().getName());\n            }\n\n            try {\n                log.debug(\"provider for:{} at {}\",\n                    provider.getPluginClassName(), provider.getAdviceTo());\n                PluginRegistry.register(provider);\n            } catch (Exception | LinkageError e) {\n                log.error(\n                    \"Unable to load provider in [class {}]\",\n                    provider.getClass().getName(),\n                    e);\n            }\n        }\n    }\n\n    public static Set<ClassTransformation> classTransformationLoad() {\n        Collection<Points> points = PluginRegistry.getPoints();\n        return points.stream().map(point -> {\n                try {\n                    return PluginRegistry.registerClassTransformation(point);\n                } catch (Exception e) {\n                    log.error(\n                        \"Unable to load classTransformation in [class {}]\",\n                        point.getClass().getName(),\n                        e);\n                    return null;\n                }\n            }).filter(Objects::nonNull)\n            .sorted(Comparator.comparing(Ordered::order))\n            .collect(Collectors.toCollection(LinkedHashSet::new));\n    }\n\n    public static void pluginLoad() {\n        for (AgentPlugin plugin : BaseLoader.loadOrdered(AgentPlugin.class)) {\n            log.info(\n                \"Loading plugin {}:{} [class {}]\",\n                plugin.getDomain(),\n                plugin.getNamespace(),\n                plugin.getClass().getName());\n\n            try {\n                PluginRegistry.register(plugin);\n            } catch (Exception | LinkageError e) {\n                log.error(\n                    \"Unable to load extension {}:{} [class {}]\",\n                    plugin.getDomain(),\n                    plugin.getNamespace(),\n                    plugin.getClass().getName(),\n                    e);\n            }\n        }\n    }\n\n    public static void pointsLoad(Configs conf) {\n        for (Points points : BaseLoader.load(Points.class)) {\n            if (!isCodeVersion(points, conf)) {\n                continue;\n            } else {\n                log.info(\"Loading points [class Points<{}>]\", points.getClass().getName());\n            }\n\n            try {\n                PluginRegistry.register(points);\n            } catch (Exception | LinkageError e) {\n                log.error(\n                    \"Unable to load extension [class {}]\",\n                    points.getClass().getName(),\n                    e);\n            }\n        }\n    }\n\n    public static boolean isCodeVersion(Points points, Configs conf) {\n        CodeVersion codeVersion = points.codeVersions();\n        if (codeVersion.isEmpty()) {\n            return true;\n        }\n        String versionKey = ConfigUtils.buildCodeVersionKey(codeVersion.getKey());\n        Set<String> versions = new HashSet<>(conf.getStringList(versionKey));\n        if (versions.isEmpty()) {\n            versions = Points.DEFAULT_VERSIONS;\n        }\n        Set<String> pointVersions = codeVersion.getVersions();\n        for (String version : versions) {\n            if (pointVersions.contains(version)) {\n                return true;\n            }\n        }\n        log.info(\"Unload points [class Points<{}>], the config [{}={}] is not in Points.codeVersions()=[{}:{}]\",\n            points.getClass().getCanonicalName(), versionKey, String.join(\",\", versions),\n            codeVersion.getKey(), String.join(\",\", codeVersion.getVersions()));\n        return false;\n    }\n\n\n    /**\n     * @param methodTransformations method matchers under a special classMatcher\n     * @return transform\n     */\n    public static AgentBuilder.Transformer compound(boolean hasDynamicField,\n                                                    Iterable<MethodTransformation> methodTransformations, String typeFieldAccessor) {\n        List<AgentBuilder.Transformer> agentTransformers = StreamSupport\n            .stream(methodTransformations.spliterator(), false)\n            .map(ForAdviceTransformer::new)\n            .collect(Collectors.toList());\n\n        if (hasDynamicField) {\n            agentTransformers.add(new DynamicFieldTransformer(AgentDynamicFieldAccessor.DYNAMIC_FIELD_NAME));\n        }\n\n        if (StringUtils.hasText(typeFieldAccessor)) {\n            agentTransformers.add(new TypeFieldTransformer(typeFieldAccessor));\n        }\n\n        return new CompoundPluginTransformer(agentTransformers);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/megaease/easeagent/core/plugin/annotation/EaseAgentInstrumented.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.core.plugin.annotation;\n\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\n\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface EaseAgentInstrumented {\n    int value() default 0;\n}\n"
  },
  {
    "path": "core/src/main/java/com/megaease/easeagent/core/plugin/annotation/Index.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.core.plugin.annotation;\n\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\n\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface Index {\n}\n"
  },
  {
    "path": "core/src/main/java/com/megaease/easeagent/core/plugin/interceptor/InterceptorPluginDecorator.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.core.plugin.interceptor;\n\nimport com.megaease.easeagent.log4j2.Logger;\nimport com.megaease.easeagent.log4j2.LoggerFactory;\nimport com.megaease.easeagent.plugin.AgentPlugin;\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.InitializeContext;\nimport com.megaease.easeagent.plugin.api.config.AutoRefreshPluginConfigImpl;\nimport com.megaease.easeagent.plugin.api.config.AutoRefreshPluginConfigRegistry;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.bridge.NoOpIPluginConfig;\n\nimport java.util.function.Supplier;\n\npublic class InterceptorPluginDecorator implements Interceptor {\n    private static final Logger LOGGER = LoggerFactory.getLogger(InterceptorPluginDecorator.class);\n    private final Interceptor interceptor;\n    private final AgentPlugin plugin;\n    private final AutoRefreshPluginConfigImpl config;\n\n    public InterceptorPluginDecorator(Interceptor interceptor, AgentPlugin plugin) {\n        this.interceptor = interceptor;\n        this.plugin = plugin;\n        this.config = AutoRefreshPluginConfigRegistry.getOrCreate(plugin.getDomain(), plugin.getNamespace(), interceptor.getType());\n    }\n\n    public IPluginConfig getConfig() {\n        return this.config.getConfig();\n    }\n\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n        IPluginConfig cfg = this.config.getConfig();\n        InitializeContext innerContext = (InitializeContext) context;\n        innerContext.pushConfig(cfg);\n        if (cfg == null || cfg.enabled() || cfg instanceof NoOpIPluginConfig) {\n            innerContext.pushRetBound();\n            this.interceptor.before(methodInfo, context);\n        } else if (LOGGER.isDebugEnabled()) {\n            LOGGER.debug(\"plugin.{}.{}.{} is not enabled\", config.domain(), config.namespace(), config.id());\n        }\n    }\n\n    @Override\n    public void after(MethodInfo methodInfo, Context context) {\n        IPluginConfig cfg = context.getConfig();\n        InitializeContext innerContext = (InitializeContext) context;\n        try {\n            if (cfg == null || cfg.enabled() || cfg instanceof NoOpIPluginConfig) {\n                try {\n                    this.interceptor.after(methodInfo, context);\n                } finally {\n                    innerContext.popToBound();\n                    innerContext.popRetBound();\n                }\n            }\n        } finally {\n            innerContext.popConfig();\n        }\n    }\n\n    @Override\n    public String getType() {\n        return this.interceptor.getType();\n    }\n\n    @Override\n    public void init(IPluginConfig config, String type, String method, String methodDescriptor) {\n        this.interceptor.init(config, type, method, methodDescriptor);\n    }\n\n    @Override\n    public void init(IPluginConfig config, int uniqueIndex) {\n        this.interceptor.init(config, uniqueIndex);\n    }\n\n    @Override\n    public int order() {\n        int pluginOrder = this.plugin.order();\n        int interceptorOrder = this.interceptor.order();\n        return (interceptorOrder << 8) + pluginOrder;\n    }\n\n    public static Supplier<Interceptor> getInterceptorSupplier(final AgentPlugin plugin, final Supplier<Interceptor> supplier) {\n        return () -> new InterceptorPluginDecorator(supplier.get(), plugin);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/megaease/easeagent/core/plugin/interceptor/ProviderChain.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.core.plugin.interceptor;\n\nimport com.megaease.easeagent.plugin.interceptor.InterceptorProvider;\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.function.Supplier;\nimport java.util.stream.Collectors;\n\npublic class ProviderChain {\n    private final List<InterceptorProvider> providers;\n\n    ProviderChain(List<InterceptorProvider> providers) {\n        this.providers = providers;\n    }\n\n    public List<Supplier<Interceptor>> getSupplierChain() {\n        return this.providers.stream()\n            .map(InterceptorProvider::getInterceptorProvider)\n            .collect(Collectors.toCollection(ArrayList::new));\n    }\n\n    public static Builder builder() {\n        return new Builder();\n    }\n\n    @SuppressWarnings(\"all\")\n    public static class Builder {\n        private List<InterceptorProvider> providers = new ArrayList<>();\n\n        Builder() {\n        }\n\n        public Builder providers(List<InterceptorProvider> providers) {\n            this.providers = providers;\n            return this;\n        }\n\n        public Builder addProvider(InterceptorProvider supplier) {\n            this.providers.add(supplier);\n            return this;\n        }\n\n        public ProviderChain build() {\n            return new ProviderChain(providers);\n        }\n\n        @Override\n        public String toString() {\n            return \"ProviderChain.Builder(providers=\" + this.providers + \")\";\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/megaease/easeagent/core/plugin/interceptor/ProviderPluginDecorator.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.core.plugin.interceptor;\n\nimport com.megaease.easeagent.plugin.AgentPlugin;\nimport com.megaease.easeagent.plugin.interceptor.InterceptorProvider;\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\n\nimport java.util.function.Supplier;\n\npublic class ProviderPluginDecorator implements InterceptorProvider {\n    private final AgentPlugin plugin;\n    private final InterceptorProvider provider;\n\n    public ProviderPluginDecorator(AgentPlugin plugin, InterceptorProvider provider) {\n        this.plugin = plugin;\n        this.provider = provider;\n    }\n\n    @Override\n    public Supplier<Interceptor> getInterceptorProvider() {\n        return () -> {\n            Supplier<Interceptor> origin = ProviderPluginDecorator.this.provider.getInterceptorProvider();\n            return new InterceptorPluginDecorator(origin.get(), this.plugin);\n        };\n    }\n\n    @Override\n    public String getAdviceTo() {\n        return this.provider.getAdviceTo();\n    }\n\n    @Override\n    public String getPluginClassName() {\n        return this.provider.getPluginClassName();\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/megaease/easeagent/core/plugin/matcher/ClassLoaderMatcherConvert.java",
    "content": "/*\n * Copyright (c) 2022, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.core.plugin.matcher;\n\nimport com.megaease.easeagent.core.Bootstrap;\nimport com.megaease.easeagent.log4j2.FinalClassloaderSupplier;\nimport com.megaease.easeagent.plugin.matcher.loader.IClassLoaderMatcher;\nimport com.megaease.easeagent.plugin.matcher.loader.NegateClassLoaderMatcher;\nimport net.bytebuddy.matcher.ElementMatcher;\n\nimport static com.megaease.easeagent.plugin.matcher.loader.ClassLoaderMatcher.*;\nimport static net.bytebuddy.matcher.ElementMatchers.*;\n\npublic class ClassLoaderMatcherConvert implements Converter<IClassLoaderMatcher, ElementMatcher<ClassLoader>> {\n    public static final ClassLoaderMatcherConvert INSTANCE = new ClassLoaderMatcherConvert();\n\n  private static final ElementMatcher<ClassLoader> agentLoaderMatcher = is(Bootstrap.class.getClassLoader())\n          .or(is(FinalClassloaderSupplier.CLASSLOADER));\n\n    @Override\n    public ElementMatcher<ClassLoader> convert(IClassLoaderMatcher source) {\n        boolean negate;\n        ElementMatcher<ClassLoader> matcher;\n        if (source instanceof NegateClassLoaderMatcher) {\n            negate = true;\n            source = source.negate();\n        } else {\n            negate = false;\n        }\n\n        if (ALL.equals(source)) {\n            matcher = any();\n        } else {\n            switch (source.getClassLoaderName()) {\n                case BOOTSTRAP_NAME:\n                    matcher = isBootstrapClassLoader();\n                    break;\n                case EXTERNAL_NAME:\n                    matcher = isExtensionClassLoader();\n                    break;\n                case SYSTEM_NAME:\n                    matcher = isSystemClassLoader();\n                    break;\n                case AGENT_NAME:\n                    matcher = agentLoaderMatcher;\n                    break;\n                default:\n                    matcher = new NameMatcher(source.getClassLoaderName());\n                    break;\n            }\n        }\n\n        if (negate) {\n            return not(matcher);\n        } else {\n            return matcher;\n        }\n    }\n\n    static class NameMatcher implements ElementMatcher<ClassLoader> {\n        final String className;\n\n        public NameMatcher(String name) {\n            this.className = name;\n        }\n\n        @Override\n        public boolean matches(ClassLoader target) {\n            if (target == null) {\n                return this.className == null;\n            }\n            return this.className.equals(target.getClass().getCanonicalName());\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/megaease/easeagent/core/plugin/matcher/ClassMatcherConvert.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.core.plugin.matcher;\n\nimport com.megaease.easeagent.plugin.asm.Modifier;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.operator.AndClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.operator.NegateClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.operator.OrClassMatcher;\nimport net.bytebuddy.description.type.TypeDescription;\nimport net.bytebuddy.matcher.ElementMatcher.Junction;\nimport net.bytebuddy.matcher.NegatingMatcher;\n\nimport static net.bytebuddy.matcher.ElementMatchers.*;\n\npublic class ClassMatcherConvert\n    implements Converter<IClassMatcher, Junction<TypeDescription>> {\n    public static final ClassMatcherConvert INSTANCE = new ClassMatcherConvert();\n\n    @Override\n    public Junction<TypeDescription> convert(IClassMatcher source) {\n        if (source == null) {\n            return null;\n        }\n\n        if (source instanceof AndClassMatcher) {\n            AndClassMatcher andMatcher = (AndClassMatcher) source;\n            Junction<TypeDescription> leftMatcher = this.convert(andMatcher.getLeft());\n            Junction<TypeDescription> rightMatcher = this.convert(andMatcher.getRight());\n            return leftMatcher.and(rightMatcher);\n        } else if (source instanceof OrClassMatcher) {\n            OrClassMatcher andMatcher = (OrClassMatcher) source;\n            Junction<TypeDescription> leftMatcher = this.convert(andMatcher.getLeft());\n            Junction<TypeDescription> rightMatcher = this.convert(andMatcher.getRight());\n            return leftMatcher.or(rightMatcher);\n        } else if (source instanceof NegateClassMatcher) {\n            NegateClassMatcher matcher = (NegateClassMatcher) source;\n            Junction<TypeDescription> notMatcher = this.convert(matcher.getMatcher());\n            return new NegatingMatcher<>(notMatcher);\n        }\n\n        if (!(source instanceof ClassMatcher)) {\n            return null;\n        }\n\n        return this.convert((ClassMatcher) source);\n    }\n\n    private Junction<TypeDescription> convert(ClassMatcher matcher) {\n        Junction<TypeDescription> c;\n        switch (matcher.getMatchType()) {\n            case NAMED:\n                c = named(matcher.getName());\n                break;\n            case SUPER_CLASS:\n            case INTERFACE:\n                c = hasSuperType(named(matcher.getName()));\n                break;\n            case ANNOTATION:\n                c = isAnnotatedWith(named(matcher.getName()));\n                break;\n            default:\n                return null;\n        }\n\n        Junction<TypeDescription> mc = fromModifier(matcher.getModifier(), false);\n        if (mc != null) {\n            c = c.and(mc);\n        }\n        mc = fromModifier(matcher.getNotModifier(), true);\n        if (mc != null) {\n            c = c.and(mc);\n        }\n\n        // TODO: classloader matcher\n\n        return c;\n    }\n\n    Junction<TypeDescription> fromModifier(int modifier, boolean not) {\n        Junction<TypeDescription> mc = null;\n        if ((modifier & ClassMatcher.MODIFIER_MASK) != 0) {\n            if ((modifier & Modifier.ACC_ABSTRACT) != 0) {\n                mc = isAbstract();\n            }\n            if ((modifier & Modifier.ACC_PUBLIC) != 0) {\n                if (mc != null) {\n                    mc = not ? mc.or(isPublic()) : mc.and(isPublic());\n                } else {\n                    mc = isPublic();\n                }\n            }\n            if ((modifier & Modifier.ACC_PRIVATE) != 0) {\n                if (mc != null) {\n                    mc = not ? mc.or(isPrivate()) : mc.and(isPrivate());\n                } else {\n                    mc = isPrivate();\n                }\n            }\n\n            if ((modifier & Modifier.ACC_INTERFACE) != 0) {\n                if (mc != null) {\n                    mc = not ? mc.or(isInterface()) : mc.and(isInterface());\n                } else {\n                    mc = isInterface();\n                }\n            }\n\n            if ((modifier & Modifier.ACC_PROTECTED) != 0) {\n                if (mc != null) {\n                    mc = not ? mc.or(isProtected()) : mc.and(isProtected());\n                } else {\n                    mc = isProtected();\n                }\n            }\n            if (not) {\n                mc = new NegatingMatcher<>(mc);\n            }\n        }\n        return mc;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/megaease/easeagent/core/plugin/matcher/ClassTransformation.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.core.plugin.matcher;\n\nimport com.megaease.easeagent.plugin.Ordered;\nimport lombok.Data;\nimport net.bytebuddy.description.type.TypeDescription;\nimport net.bytebuddy.matcher.ElementMatcher;\nimport net.bytebuddy.matcher.ElementMatcher.Junction;\n\nimport java.util.Set;\n\nimport static net.bytebuddy.matcher.ElementMatchers.any;\n\n@Data\npublic class ClassTransformation implements Ordered {\n    private int order;\n    private Junction<TypeDescription> classMatcher;\n    private ElementMatcher<ClassLoader> classloaderMatcher;\n    private Set<MethodTransformation> methodTransformations;\n    private boolean hasDynamicField;\n    private String typeFieldAccessor;\n\n    public ClassTransformation(int order,\n                               ElementMatcher<ClassLoader> classloaderMatcher,\n                               Junction<TypeDescription> classMatcher,\n                               Set<MethodTransformation> methodTransformations,\n                               boolean hasDynamicField,\n                               String typeFieldAccessor) {\n        this.order = order;\n        if (classloaderMatcher == null) {\n            this.classloaderMatcher = any();\n        } else {\n            this.classloaderMatcher = classloaderMatcher;\n        }\n        this.classMatcher = classMatcher;\n        this.methodTransformations = methodTransformations;\n        this.hasDynamicField = hasDynamicField;\n        this.typeFieldAccessor = typeFieldAccessor;\n    }\n\n    public static Builder builder() {\n        return new Builder();\n    }\n\n    @Override\n    public int order() {\n        return this.order;\n    }\n\n    public static class Builder {\n        private int order;\n        private Junction<TypeDescription> classMatcher;\n        private ElementMatcher<ClassLoader> classloaderMatcher = null;\n        private Set<MethodTransformation> methodTransformations;\n        private boolean hasDynamicField;\n\n        private String typeFieldAccessor;\n\n        Builder() {\n        }\n\n        public Builder order(int order) {\n            this.order = order;\n            return this;\n        }\n\n        public Builder classloaderMatcher(ElementMatcher<ClassLoader> clmMatcher) {\n            this.classloaderMatcher = clmMatcher;\n            return this;\n        }\n\n        public Builder classMatcher(Junction<TypeDescription> classMatcher) {\n            this.classMatcher = classMatcher;\n            return this;\n        }\n\n        public Builder methodTransformations(Set<MethodTransformation> methodTransformations) {\n            this.methodTransformations = methodTransformations;\n            return this;\n        }\n\n        public Builder hasDynamicField(boolean hasDynamicField) {\n            this.hasDynamicField = hasDynamicField;\n            return this;\n        }\n\n        public Builder typeFieldAccessor(String typeFieldAccessor) {\n            this.typeFieldAccessor = typeFieldAccessor;\n            return this;\n        }\n\n        public ClassTransformation build() {\n            return new ClassTransformation(order, classloaderMatcher, classMatcher,\n                methodTransformations, hasDynamicField, typeFieldAccessor);\n        }\n\n        public String toString() {\n            return \"ClassTransformation.Builder(order=\" + this.order + \", classMatcher=\" + this.classMatcher + \", methodTransformations=\" + this.methodTransformations + \", hasDynamicField=\" + this.hasDynamicField + \")\";\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/megaease/easeagent/core/plugin/matcher/Converter.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.core.plugin.matcher;\n\npublic interface Converter<S, T> {\n    T convert(S source);\n}\n"
  },
  {
    "path": "core/src/main/java/com/megaease/easeagent/core/plugin/matcher/MethodMatcherConvert.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.core.plugin.matcher;\n\nimport com.megaease.easeagent.plugin.asm.Modifier;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.operator.AndMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.operator.NegateMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.operator.OrMethodMatcher;\nimport net.bytebuddy.description.method.MethodDescription;\nimport net.bytebuddy.description.type.TypeDescription;\nimport net.bytebuddy.matcher.ElementMatcher.Junction;\nimport net.bytebuddy.matcher.NegatingMatcher;\n\nimport static net.bytebuddy.matcher.ElementMatchers.*;\n\npublic class MethodMatcherConvert\n    implements Converter<IMethodMatcher, Junction<MethodDescription>> {\n\n    public static final MethodMatcherConvert INSTANCE = new MethodMatcherConvert();\n\n    @Override\n    public Junction<MethodDescription> convert(IMethodMatcher source) {\n        if (source == null) {\n            return null;\n        }\n\n        if (source instanceof AndMethodMatcher) {\n            AndMethodMatcher andMatcher = (AndMethodMatcher) source;\n            Junction<MethodDescription> leftMatcher = this.convert(andMatcher.getLeft());\n            Junction<MethodDescription> rightMatcher = this.convert(andMatcher.getRight());\n            return leftMatcher.and(rightMatcher);\n        } else if (source instanceof OrMethodMatcher) {\n            OrMethodMatcher andMatcher = (OrMethodMatcher) source;\n            Junction<MethodDescription> leftMatcher = this.convert(andMatcher.getLeft());\n            Junction<MethodDescription> rightMatcher = this.convert(andMatcher.getRight());\n            return leftMatcher.or(rightMatcher);\n        } else if (source instanceof NegateMethodMatcher) {\n            NegateMethodMatcher matcher = (NegateMethodMatcher) source;\n            Junction<MethodDescription> notMatcher = this.convert(matcher.getMatcher());\n            return new NegatingMatcher<>(notMatcher);\n        }\n\n        if (!(source instanceof MethodMatcher)) {\n            return null;\n        }\n\n        return this.convert((MethodMatcher) source);\n    }\n\n    private Junction<MethodDescription> convert(MethodMatcher matcher) {\n        Junction<MethodDescription> c = null;\n        if (matcher.getName() != null && matcher.getNameMatchType() != null) {\n            switch (matcher.getNameMatchType()) {\n                case EQUALS:\n                    if (\"<init>\".equals(matcher.getName())) {\n                        c = isConstructor();\n                    } else {\n                        c = named(matcher.getName());\n                    }\n                    break;\n                case START_WITH:\n                    c = nameStartsWith(matcher.getName());\n                    break;\n                case END_WITH:\n                    c = nameEndsWith(matcher.getName());\n                    break;\n                case CONTAINS:\n                    c = nameContains(matcher.getName());\n                    break;\n                default:\n                    return null;\n            }\n        }\n\n        Junction<MethodDescription> mc = fromModifier(matcher.getModifier(), false);\n        if (mc != null) {\n            c = c == null ? mc : c.and(mc);\n        }\n        mc = fromModifier(matcher.getNotModifier(), true);\n        if (mc != null) {\n            c = c == null ? mc : c.and(mc);\n        }\n        if (matcher.getReturnType() != null) {\n            mc = returns(named(matcher.getReturnType()));\n            c = c == null ? mc : c.and(mc);\n        }\n        if (matcher.getArgsLength() > -1) {\n            mc = takesArguments(matcher.getArgsLength());\n            c = c == null ? mc : c.and(mc);\n        }\n        String[] args = matcher.getArgs();\n        if (args != null) {\n            for (int i = 0; i < args.length; i++) {\n                if (args[i] != null) {\n                    mc = takesArgument(i, named(args[i]));\n                    c = c == null ? mc : c.and(mc);\n                }\n            }\n        }\n\n        if (matcher.getArgsLength() >= 0) {\n            mc = takesArguments(matcher.getArgsLength());\n            c = c == null ? mc : c.and(mc);\n        }\n\n        if (matcher.getOverriddenFrom() != null) {\n            Junction<TypeDescription> cls = ClassMatcherConvert.INSTANCE.convert(matcher.getOverriddenFrom());\n            mc = isOverriddenFrom(cls);\n            c = c == null ? mc : c.and(mc);\n        }\n\n        return c;\n    }\n\n    Junction<MethodDescription> fromModifier(int modifier, boolean not) {\n        Junction<MethodDescription> mc = null;\n        if ((modifier & ClassMatcher.MODIFIER_MASK) != 0) {\n            if ((modifier & Modifier.ACC_ABSTRACT) != 0) {\n                mc = isAbstract();\n            }\n            if ((modifier & Modifier.ACC_PUBLIC) != 0) {\n                if (mc != null) {\n                    mc = not ? mc.or(isPublic()) : mc.and(isPublic());\n                } else {\n                    mc = isPublic();\n                }\n            }\n            if ((modifier & Modifier.ACC_PRIVATE) != 0) {\n                if (mc != null) {\n                    mc = not ? mc.or(isPrivate()) : mc.and(isPrivate());\n                } else {\n                    mc = isPrivate();\n                }\n            }\n            if ((modifier & Modifier.ACC_PROTECTED) != 0) {\n                if (mc != null) {\n                    mc = not ? mc.or(isProtected()) : mc.and(isProtected());\n                } else {\n                    mc = isProtected();\n                }\n            }\n            if ((modifier & Modifier.ACC_STATIC) != 0) {\n                if (mc != null) {\n                    mc = not ? mc.or(isStatic()) : mc.and(isStatic());\n                } else {\n                    mc = isStatic();\n                }\n            }\n            if (not) {\n                mc = new NegatingMatcher<>(mc);\n            }\n        }\n        return mc;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/megaease/easeagent/core/plugin/matcher/MethodTransformation.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.core.plugin.matcher;\n\nimport com.megaease.easeagent.core.plugin.Dispatcher;\nimport com.megaease.easeagent.core.plugin.interceptor.InterceptorPluginDecorator;\nimport com.megaease.easeagent.core.plugin.interceptor.ProviderChain;\nimport com.megaease.easeagent.log4j2.Logger;\nimport com.megaease.easeagent.log4j2.LoggerFactory;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.Ordered;\nimport com.megaease.easeagent.plugin.interceptor.AgentInterceptorChain;\nimport lombok.Data;\nimport net.bytebuddy.description.method.MethodDescription;\nimport net.bytebuddy.matcher.ElementMatcher.Junction;\n\nimport java.util.Comparator;\nimport java.util.List;\nimport java.util.function.Supplier;\nimport java.util.stream.Collectors;\n\n@Data\n@SuppressWarnings(\"unused\")\npublic class MethodTransformation {\n    private static final Logger log = LoggerFactory.getLogger(MethodTransformation.class);\n\n    private int index;\n    private Junction<? super MethodDescription> matcher;\n    private ProviderChain.Builder providerBuilder;\n\n    public MethodTransformation(int index,\n                                Junction<? super MethodDescription> matcher,\n                                ProviderChain.Builder chain) {\n        this.index = index;\n        this.matcher = matcher;\n        this.providerBuilder = chain;\n    }\n\n    public AgentInterceptorChain getAgentInterceptorChain(final int uniqueIndex,\n                                                          final String type,\n                                                          final String method,\n                                                          final String methodDescription) {\n        List<Supplier<Interceptor>> suppliers = this.providerBuilder.build()\n            .getSupplierChain();\n\n        List<Interceptor> interceptors = suppliers.stream()\n            .map(Supplier::get)\n            .sorted(Comparator.comparing(Ordered::order))\n            .collect(Collectors.toList());\n\n        interceptors.forEach(i -> {\n            InterceptorPluginDecorator interceptor;\n            if (i instanceof InterceptorPluginDecorator) {\n                interceptor = (InterceptorPluginDecorator) i;\n                try {\n                    interceptor.init(interceptor.getConfig(), type, method, methodDescription);\n                    interceptor.init(interceptor.getConfig(), uniqueIndex);\n                } catch (Exception e) {\n                    log.error(\"Interceptor init fail: {}::{}, {}\", type, method, interceptor.getClass().getSimpleName());\n                }\n            }\n        });\n\n        return new AgentInterceptorChain(interceptors);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/megaease/easeagent/core/plugin/registry/AdviceRegistry.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.core.plugin.registry;\n\nimport com.megaease.easeagent.core.plugin.matcher.MethodTransformation;\nimport com.megaease.easeagent.core.plugin.transformer.advice.AgentAdvice.Dispatcher;\nimport com.megaease.easeagent.core.plugin.transformer.advice.AgentAdvice.OffsetMapping;\nimport com.megaease.easeagent.core.plugin.transformer.advice.AgentJavaConstantValue;\nimport com.megaease.easeagent.core.plugin.transformer.advice.MethodIdentityJavaConstant;\nimport com.megaease.easeagent.log4j2.Logger;\nimport com.megaease.easeagent.log4j2.LoggerFactory;\nimport com.megaease.easeagent.plugin.interceptor.AgentInterceptorChain;\nimport com.megaease.easeagent.plugin.utils.common.WeakConcurrentMap;\nimport net.bytebuddy.description.method.MethodDescription;\nimport net.bytebuddy.description.type.TypeDescription;\nimport net.bytebuddy.implementation.bytecode.StackManipulation;\n\nimport java.lang.ref.WeakReference;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.concurrent.locks.ReentrantLock;\n\npublic class AdviceRegistry {\n\n    private AdviceRegistry() {\n    }\n\n    private static final ThreadLocal<WeakReference<ClassLoader>> CURRENT_CLASS_LOADER = new ThreadLocal<>();\n    private static final Logger log = LoggerFactory.getLogger(AdviceRegistry.class);\n    static Map<String, PointcutsUniqueId> methodsSet = new ConcurrentHashMap<>();\n\n    public static Integer check(TypeDescription instrumentedType,\n                                MethodDescription instrumentedMethod,\n                                Dispatcher.Resolved.ForMethodEnter methodEnter,\n                                Dispatcher.Resolved.ForMethodExit methodExit) {\n        String clazz = instrumentedType.getName();\n        String method = instrumentedMethod.getName();\n        String methodDescriptor = instrumentedMethod.getDescriptor();\n        String key = clazz + \":\" + method + methodDescriptor;\n        PointcutsUniqueId newIdentity = new PointcutsUniqueId();\n        PointcutsUniqueId pointcutsUniqueId = methodsSet.putIfAbsent(key, newIdentity);\n\n        Integer pointcutIndex;\n        boolean merge = false;\n\n        // already exist\n        if (pointcutsUniqueId != null) {\n            newIdentity.tryRelease();\n            pointcutIndex = getPointcutIndex(methodEnter);\n            // this pointcut's interceptors have injected into chain\n            if (pointcutsUniqueId.checkPointcutExist(pointcutIndex)) {\n                if (pointcutsUniqueId.checkClassloaderExist()) {\n                    // don't need to instrumented again.\n                    return 0;\n                } else {\n                    /*\n                     * Although the interceptor of the pointcut has been injected,\n                     * the method of this class owned by current loader has not been instrumented\n                     */\n                    updateStackManipulation(methodEnter, pointcutsUniqueId.getUniqueId());\n                    updateStackManipulation(methodExit, pointcutsUniqueId.getUniqueId());\n                    return pointcutsUniqueId.getUniqueId();\n                }\n            } else {\n                // Orchestration\n                merge = true;\n            }\n        } else {\n            // new\n            pointcutsUniqueId = newIdentity;\n            pointcutIndex = updateStackManipulation(methodEnter, pointcutsUniqueId.getUniqueId());\n            updateStackManipulation(methodExit, pointcutsUniqueId.getUniqueId());\n        }\n\n        // merge or registry\n        MethodTransformation methodTransformation = PluginRegistry.getMethodTransformation(pointcutIndex);\n        if (methodTransformation == null) {\n            log.error(\"MethodTransformation get fail for {}\", pointcutIndex);\n            return 0;\n        }\n        int uniqueId = pointcutsUniqueId.getUniqueId();\n        AgentInterceptorChain chain = methodTransformation\n            .getAgentInterceptorChain(uniqueId, clazz, method, methodDescriptor);\n\n        try {\n            pointcutsUniqueId.lock();\n            AgentInterceptorChain previousChain = com.megaease.easeagent.core.plugin.Dispatcher.getChain(uniqueId);\n            if (previousChain == null) {\n                com.megaease.easeagent.core.plugin.Dispatcher.register(uniqueId, chain);\n            } else {\n                chain.merge(previousChain);\n                com.megaease.easeagent.core.plugin.Dispatcher.updateChain(uniqueId, chain);\n            }\n        } finally {\n            pointcutsUniqueId.unlock();\n        }\n\n        if (merge) {\n            return 0;\n        }\n\n        return uniqueId;\n    }\n\n    static Integer getPointcutIndex(Dispatcher.Resolved resolved) {\n        int index = 0;\n        Map<Integer, OffsetMapping> enterMap = resolved.getOffsetMapping();\n        for (Map.Entry<Integer, OffsetMapping> offset : enterMap.entrySet()) {\n            OffsetMapping om = offset.getValue();\n            if (!(om instanceof OffsetMapping.ForStackManipulation)) {\n                continue;\n            }\n            OffsetMapping.ForStackManipulation forStackManipulation = (OffsetMapping.ForStackManipulation) om;\n            if (!(forStackManipulation.getStackManipulation() instanceof AgentJavaConstantValue)) {\n                continue;\n            }\n\n            AgentJavaConstantValue value = (AgentJavaConstantValue) forStackManipulation.getStackManipulation();\n            index = value.getPointcutIndex();\n            break;\n        }\n        return index;\n    }\n\n    static Integer updateStackManipulation(Dispatcher.Resolved resolved, Integer value) {\n        int index = 0;\n        Map<Integer, OffsetMapping> enterMap = resolved.getOffsetMapping();\n\n        for (Map.Entry<Integer, OffsetMapping> offset : enterMap.entrySet()) {\n            OffsetMapping om = offset.getValue();\n            if (!(om instanceof OffsetMapping.ForStackManipulation)) {\n                continue;\n            }\n            OffsetMapping.ForStackManipulation forStackManipulation = (OffsetMapping.ForStackManipulation) om;\n            if (!(forStackManipulation.getStackManipulation() instanceof AgentJavaConstantValue)) {\n                continue;\n            }\n\n            AgentJavaConstantValue oldValue = (AgentJavaConstantValue) forStackManipulation.getStackManipulation();\n            index = oldValue.getPointcutIndex();\n\n            MethodIdentityJavaConstant constant = new MethodIdentityJavaConstant(value);\n            StackManipulation stackManipulation = new AgentJavaConstantValue(constant, index);\n            enterMap.put(offset.getKey(), forStackManipulation.with(stackManipulation));\n\n            return index;\n        }\n        return index;\n    }\n\n    public static void setCurrentClassLoader(ClassLoader loader) {\n        CURRENT_CLASS_LOADER.set(new WeakReference<>(loader));\n    }\n\n    public static ClassLoader getCurrentClassLoader() {\n        return CURRENT_CLASS_LOADER.get().get();\n    }\n\n    public static void cleanCurrentClassLoader() {\n        CURRENT_CLASS_LOADER.remove();\n    }\n\n    private static class PointcutsUniqueId {\n        static AtomicInteger index = new AtomicInteger(1);\n        ReentrantLock lock = new ReentrantLock();\n        int uniqueId;\n        ConcurrentHashMap<Integer, Integer> pointcutIndexSet = new ConcurrentHashMap<>();\n        WeakConcurrentMap<ClassLoader, Boolean> cache = new WeakConcurrentMap<>();\n\n        public PointcutsUniqueId() {\n            this.uniqueId = index.incrementAndGet();\n        }\n\n        public boolean checkPointcutExist(Integer pointcutIndex) {\n            return this.pointcutIndexSet.putIfAbsent(pointcutIndex, pointcutIndex) != null;\n        }\n\n        public int getUniqueId() {\n            return this.uniqueId;\n        }\n\n        public boolean checkClassloaderExist() {\n            ClassLoader loader = getCurrentClassLoader();\n            return cache.putIfProbablyAbsent(loader, true) != null;\n        }\n\n        public void lock() {\n            this.lock.lock();\n        }\n\n        public void unlock() {\n            this.lock.unlock();\n        }\n\n        /**\n         * Some empty slots may appear, the effect can be ignored and can be optimized later\n         */\n        public void tryRelease() {\n            int id = this.uniqueId;\n            index.compareAndSet(id, id - 1);\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/megaease/easeagent/core/plugin/registry/PluginRegistry.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.core.plugin.registry;\n\nimport com.google.common.base.Strings;\nimport com.megaease.easeagent.core.plugin.interceptor.ProviderChain;\nimport com.megaease.easeagent.core.plugin.interceptor.ProviderChain.Builder;\nimport com.megaease.easeagent.core.plugin.interceptor.ProviderPluginDecorator;\nimport com.megaease.easeagent.core.plugin.matcher.*;\nimport com.megaease.easeagent.core.utils.AgentArray;\nimport com.megaease.easeagent.plugin.AgentPlugin;\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.api.logging.Logger;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.InterceptorProvider;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport net.bytebuddy.description.method.MethodDescription;\nimport net.bytebuddy.description.type.TypeDescription;\nimport net.bytebuddy.matcher.ElementMatcher;\nimport net.bytebuddy.matcher.ElementMatcher.Junction;\n\nimport java.util.*;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.stream.Collectors;\n\npublic class PluginRegistry {\n    static Logger log = EaseAgent.getLogger(PluginRegistry.class);\n\n    static final ConcurrentHashMap<String, AgentPlugin> QUALIFIER_TO_PLUGIN = new ConcurrentHashMap<>();\n    static final ConcurrentHashMap<String, AgentPlugin> POINTS_TO_PLUGIN = new ConcurrentHashMap<>();\n    static final ConcurrentHashMap<String, AgentPlugin> PLUGIN_CLASSNAME_TO_PLUGIN = new ConcurrentHashMap<>();\n    static final ConcurrentHashMap<String, Points> POINTS_CLASSNAME_TO_POINTS = new ConcurrentHashMap<>();\n\n    static final ConcurrentHashMap<String, Integer> QUALIFIER_TO_INDEX = new ConcurrentHashMap<>();\n    static final ConcurrentHashMap<Integer, MethodTransformation> INDEX_TO_METHOD_TRANSFORMATION = new ConcurrentHashMap<>();\n    static final AgentArray<Builder> INTERCEPTOR_PROVIDERS = new AgentArray<>();\n\n    private PluginRegistry() {\n    }\n\n    public static void register(AgentPlugin plugin) {\n        PLUGIN_CLASSNAME_TO_PLUGIN.putIfAbsent(plugin.getClass().getCanonicalName(), plugin);\n    }\n\n    public static void register(Points points) {\n        POINTS_CLASSNAME_TO_POINTS.putIfAbsent(points.getClass().getCanonicalName(), points);\n    }\n\n    public static Collection<Points> getPoints() {\n        return POINTS_CLASSNAME_TO_POINTS.values();\n    }\n\n    public static Points getPoints(String pointsClassName) {\n        return POINTS_CLASSNAME_TO_POINTS.get(pointsClassName);\n    }\n\n    private static String getMethodQualifier(String classname, String qualifier) {\n        return classname + \":\" + qualifier;\n    }\n\n    public static ClassTransformation registerClassTransformation(Points points) {\n        String pointsClassName = points.getClass().getCanonicalName();\n        IClassMatcher classMatcher = points.getClassMatcher();\n        boolean hasDynamicField = points.isAddDynamicField();\n        Junction<TypeDescription> innerClassMatcher = ClassMatcherConvert.INSTANCE.convert(classMatcher);\n        ElementMatcher<ClassLoader> loaderMatcher = ClassLoaderMatcherConvert.INSTANCE\n            .convert(points.getClassLoaderMatcher());\n\n        Set<IMethodMatcher> methodMatchers = points.getMethodMatcher();\n\n        Set<MethodTransformation> mInfo = methodMatchers.stream().map(matcher -> {\n            Junction<MethodDescription> bMethodMatcher = MethodMatcherConvert.INSTANCE.convert(matcher);\n            String qualifier = getMethodQualifier(pointsClassName, matcher.getQualifier());\n            Integer index = QUALIFIER_TO_INDEX.get(qualifier);\n            if (index == null) {\n                // it is unusual for this is a pointcut without interceptor.\n                // maybe there is some error in plugin providers configuration\n                return null;\n            }\n            Builder providerBuilder = INTERCEPTOR_PROVIDERS.get(index);\n            if (providerBuilder == null) {\n                return null;\n            }\n            MethodTransformation mt = new MethodTransformation(index, bMethodMatcher, providerBuilder);\n            if (INDEX_TO_METHOD_TRANSFORMATION.putIfAbsent(index, mt) != null) {\n                log.error(\"There are duplicate qualifier in Points:{}!\", qualifier);\n            }\n            return mt;\n        }).filter(Objects::nonNull).collect(Collectors.toSet());\n\n        AgentPlugin plugin = POINTS_TO_PLUGIN.get(pointsClassName);\n        int order = plugin.order();\n        return ClassTransformation.builder().classMatcher(innerClassMatcher)\n            .hasDynamicField(hasDynamicField)\n            .methodTransformations(mInfo)\n            .classloaderMatcher(loaderMatcher)\n            .typeFieldAccessor(points.getTypeFieldAccessor())\n            .order(order).build();\n    }\n\n    public static int register(InterceptorProvider provider) {\n        String qualifier = provider.getAdviceTo();\n        // map interceptor/pointcut to plugin\n\n        AgentPlugin plugin = PLUGIN_CLASSNAME_TO_PLUGIN.get(provider.getPluginClassName());\n        if (plugin == null) {\n            // code autogenerate issues that are unlikely to occur!\n            throw new RuntimeException();\n        }\n        QUALIFIER_TO_PLUGIN.putIfAbsent(qualifier, plugin);\n        POINTS_TO_PLUGIN.putIfAbsent(getPointsClassName(qualifier), plugin);\n\n        // generate index and supplier chain\n        Integer index = QUALIFIER_TO_INDEX.get(provider.getAdviceTo());\n        if (index == null) {\n            synchronized (QUALIFIER_TO_INDEX) {\n                index = QUALIFIER_TO_INDEX.get(provider.getAdviceTo());\n                if (index == null) {\n                    index = INTERCEPTOR_PROVIDERS.add(ProviderChain.builder());\n                    QUALIFIER_TO_INDEX.putIfAbsent(provider.getAdviceTo(), index);\n                }\n            }\n        }\n        INTERCEPTOR_PROVIDERS.get(index)\n            .addProvider(new ProviderPluginDecorator(plugin, provider));\n\n        return index;\n    }\n\n    public static String getPointsClassName(String name) {\n        int index;\n        if (Strings.isNullOrEmpty(name)) {\n            return \"unknown\";\n        }\n        index = name.indexOf(':');\n        if (index < 0) {\n            return name;\n        }\n        return name.substring(0, index);\n    }\n\n    public static MethodTransformation getMethodTransformation(int pointcutIndex) {\n        return INDEX_TO_METHOD_TRANSFORMATION.get(pointcutIndex);\n    }\n\n    public static void addMethodTransformation(int pointcutIndex, MethodTransformation info) {\n        INDEX_TO_METHOD_TRANSFORMATION.putIfAbsent(pointcutIndex, info);\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/com/megaease/easeagent/core/plugin/transformer/AnnotationTransformer.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.core.plugin.transformer;\n\nimport com.megaease.easeagent.core.plugin.annotation.EaseAgentInstrumented;\nimport com.megaease.easeagent.core.plugin.matcher.MethodTransformation;\nimport net.bytebuddy.agent.builder.AgentBuilder;\nimport net.bytebuddy.asm.AsmVisitorWrapper;\nimport net.bytebuddy.asm.MemberAttributeExtension;\nimport net.bytebuddy.description.annotation.AnnotationDescription;\nimport net.bytebuddy.description.method.MethodDescription;\nimport net.bytebuddy.description.type.TypeDescription;\nimport net.bytebuddy.dynamic.DynamicType;\nimport net.bytebuddy.implementation.Implementation;\nimport net.bytebuddy.jar.asm.MethodVisitor;\nimport net.bytebuddy.matcher.ElementMatcher;\nimport net.bytebuddy.pool.TypePool;\nimport net.bytebuddy.utility.JavaModule;\n\npublic class AnnotationTransformer implements AgentBuilder.Transformer {\n    private final AsmVisitorWrapper visitor;\n    private final MethodTransformation methodTransformInfo;\n    private final AnnotationDescription annotation;\n\n    public AnnotationTransformer(MethodTransformation info) {\n        this.methodTransformInfo = info;\n        this.annotation = AnnotationDescription.Builder\n            .ofType(EaseAgentInstrumented.class)\n            .define(\"value\", info.getIndex())\n            .build();\n        MemberAttributeExtension.ForMethod mForMethod = new MemberAttributeExtension.ForMethod()\n            .annotateMethod(this.annotation);\n        this.visitor = new ForMethodDelegate(mForMethod, annotation, methodTransformInfo)\n            .on(methodTransformInfo.getMatcher());\n    }\n\n    @Override\n    public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule module) {\n        return builder.visit(this.visitor);\n    }\n\n    public static class ForMethodDelegate implements AsmVisitorWrapper.ForDeclaredMethods.MethodVisitorWrapper {\n        private final TypeDescription annotation;\n        private final MemberAttributeExtension.ForMethod mForMethod;\n        private final MethodTransformation info;\n\n        ForMethodDelegate(MemberAttributeExtension.ForMethod mForMethod,\n                          AnnotationDescription annotation,\n                          MethodTransformation info) {\n            this.annotation = annotation.getAnnotationType();\n            this.mForMethod = mForMethod;\n            this.info = info;\n        }\n\n        @Override\n        public MethodVisitor wrap(TypeDescription instrumentedType,\n                                  MethodDescription instrumentedMethod,\n                                  MethodVisitor methodVisitor,\n                                  Implementation.Context implementationContext,\n                                  TypePool typePool,\n                                  int writerFlags,\n                                  int readerFlags) {\n            AnnotationDescription annotation = instrumentedMethod.getDeclaredAnnotations().ofType(this.annotation);\n            if (annotation != null) {\n                // merge interceptor chain\n                Integer index = annotation.getValue(\"value\").resolve(Integer.class);\n            }\n            return methodVisitor;\n        }\n\n        public AsmVisitorWrapper on(ElementMatcher<? super MethodDescription> matcher) {\n            return new AsmVisitorWrapper.ForDeclaredMethods().invokable(matcher, this);\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/megaease/easeagent/core/plugin/transformer/CompoundPluginTransformer.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.core.plugin.transformer;\n\nimport net.bytebuddy.agent.builder.AgentBuilder;\nimport net.bytebuddy.description.type.TypeDescription;\nimport net.bytebuddy.dynamic.DynamicType;\nimport net.bytebuddy.utility.JavaModule;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class CompoundPluginTransformer implements AgentBuilder.Transformer {\n    private final List<AgentBuilder.Transformer> transformers;\n\n    public CompoundPluginTransformer(List<AgentBuilder.Transformer> transformers) {\n        this.transformers = new ArrayList<>();\n        for (AgentBuilder.Transformer transformer : transformers) {\n            if (transformer instanceof CompoundPluginTransformer) {\n                this.transformers.addAll(((CompoundPluginTransformer) transformer).transformers);\n                continue;\n            }\n            this.transformers.add(transformer);\n        }\n    }\n\n    @Override\n    public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder,\n                                            TypeDescription typeDescription,\n                                            ClassLoader classLoader,\n                                            JavaModule module) {\n        for (AgentBuilder.Transformer transformer : this.transformers) {\n            builder = transformer.transform(builder, typeDescription, classLoader, module);\n        }\n\n        return builder;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/megaease/easeagent/core/plugin/transformer/DynamicFieldAdvice.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.core.plugin.transformer;\n\nimport com.megaease.easeagent.log4j2.Logger;\nimport com.megaease.easeagent.log4j2.LoggerFactory;\nimport com.megaease.easeagent.plugin.field.DynamicFieldAccessor;\nimport com.megaease.easeagent.plugin.field.NullObject;\nimport net.bytebuddy.asm.Advice;\n\npublic class DynamicFieldAdvice {\n\n    private DynamicFieldAdvice() {\n    }\n\n    public static final Logger log = LoggerFactory.getLogger(DynamicFieldAdvice.class);\n\n    public static class DynamicInstanceInit {\n\n        private DynamicInstanceInit() {\n        }\n\n        @Advice.OnMethodExit\n        public static void exit(@Advice.This(optional = true) Object target) {\n            if (target instanceof DynamicFieldAccessor) {\n                DynamicFieldAccessor accessor = (DynamicFieldAccessor) target;\n                if (accessor.getEaseAgent$$DynamicField$$Data() == null) {\n                    accessor.setEaseAgent$$DynamicField$$Data(NullObject.NULL);\n                }\n            }\n        }\n    }\n\n    public static class DynamicClassInit {\n\n        private DynamicClassInit() {\n        }\n\n        @Advice.OnMethodExit\n        public static void exit(@Advice.Origin(\"#m\") String method) {\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/megaease/easeagent/core/plugin/transformer/DynamicFieldTransformer.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.core.plugin.transformer;\n\nimport com.google.common.cache.Cache;\nimport com.google.common.cache.CacheBuilder;\nimport com.megaease.easeagent.core.plugin.transformer.DynamicFieldAdvice.DynamicInstanceInit;\nimport com.megaease.easeagent.log4j2.Logger;\nimport com.megaease.easeagent.log4j2.LoggerFactory;\nimport com.megaease.easeagent.plugin.field.DynamicFieldAccessor;\nimport net.bytebuddy.agent.builder.AgentBuilder;\nimport net.bytebuddy.asm.Advice;\nimport net.bytebuddy.description.type.TypeDescription;\nimport net.bytebuddy.dynamic.DynamicType;\nimport net.bytebuddy.implementation.FieldAccessor;\nimport net.bytebuddy.jar.asm.Opcodes;\nimport net.bytebuddy.matcher.ElementMatchers;\nimport net.bytebuddy.utility.JavaModule;\n\nimport java.util.concurrent.ConcurrentHashMap;\n\npublic class DynamicFieldTransformer implements AgentBuilder.Transformer {\n    private static final Logger log = LoggerFactory.getLogger(DynamicFieldTransformer.class);\n    private static final ConcurrentHashMap<String, Cache<ClassLoader, Boolean>> FIELD_MAP = new ConcurrentHashMap<>();\n\n    private final String fieldName;\n    private final Class<?> accessor;\n    private final AgentBuilder.Transformer.ForAdvice transformer;\n\n    public DynamicFieldTransformer(String fieldName) {\n        this(fieldName, DynamicFieldAccessor.class);\n    }\n\n    public DynamicFieldTransformer(String fieldName, Class<?> accessor) {\n        this.fieldName = fieldName;\n        this.accessor = accessor;\n        this.transformer = new AgentBuilder.Transformer\n            .ForAdvice(Advice.withCustomMapping())\n            .include(getClass().getClassLoader())\n            .advice(ElementMatchers.isConstructor(), DynamicInstanceInit.class.getName());\n    }\n\n    @Override\n    public DynamicType.Builder<?> transform(DynamicType.Builder<?> b,\n                                            TypeDescription td, ClassLoader cl, JavaModule m) {\n        if (check(td, this.accessor, cl) && this.fieldName != null) {\n            try {\n                b = b.defineField(this.fieldName, Object.class, Opcodes.ACC_PRIVATE)\n                    .implement(this.accessor)\n                    .intercept(FieldAccessor.ofField(this.fieldName));\n            } catch (Exception e) {\n                log.debug(\"Type:{} add extend field again!\", td.getName());\n            }\n            return transformer.transform(b, td, cl, m);\n        }\n        return b;\n    }\n\n    /**\n     * Avoiding add a accessor interface to a class repeatedly\n     *\n     * @param td       represent the class to be enhanced\n     * @param accessor access interface class\n     * @param cl       current classloader\n     * @return return true when it is the first time\n     */\n    private static boolean check(TypeDescription td, Class<?> accessor, ClassLoader cl) {\n        String key = td.getCanonicalName() + accessor.getCanonicalName();\n\n        Cache<ClassLoader, Boolean> checkCache = FIELD_MAP.get(key);\n        if (checkCache == null) {\n            Cache<ClassLoader, Boolean> cache = CacheBuilder.newBuilder().weakKeys().build();\n            if (cl == null) {\n                cl = Thread.currentThread().getContextClassLoader();\n            }\n            cache.put(cl, true);\n            checkCache = FIELD_MAP.putIfAbsent(key, cache);\n            if (checkCache == null) {\n                return true;\n            }\n        }\n\n        return checkCache.getIfPresent(cl) == null;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/megaease/easeagent/core/plugin/transformer/ForAdviceTransformer.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.core.plugin.transformer;\n\nimport com.megaease.easeagent.core.plugin.CommonInlineAdvice;\nimport com.megaease.easeagent.core.plugin.annotation.Index;\nimport com.megaease.easeagent.core.plugin.matcher.MethodTransformation;\nimport com.megaease.easeagent.core.plugin.registry.AdviceRegistry;\nimport com.megaease.easeagent.core.plugin.transformer.advice.AgentAdvice;\nimport com.megaease.easeagent.core.plugin.transformer.advice.AgentAdvice.OffsetMapping;\nimport com.megaease.easeagent.core.plugin.transformer.advice.AgentForAdvice;\nimport com.megaease.easeagent.core.plugin.transformer.advice.AgentJavaConstantValue;\nimport com.megaease.easeagent.core.plugin.transformer.advice.MethodIdentityJavaConstant;\nimport com.megaease.easeagent.core.plugin.transformer.classloader.CompoundClassloader;\nimport net.bytebuddy.agent.builder.AgentBuilder;\nimport net.bytebuddy.description.type.TypeDescription;\nimport net.bytebuddy.dynamic.DynamicType;\nimport net.bytebuddy.implementation.bytecode.StackManipulation;\nimport net.bytebuddy.utility.JavaModule;\n\npublic class ForAdviceTransformer implements AgentBuilder.Transformer {\n\n    private final AgentForAdvice transformer;\n    private final MethodTransformation methodTransformInfo;\n\n    public ForAdviceTransformer(MethodTransformation methodTransformInfo) {\n        this.methodTransformInfo = methodTransformInfo;\n\n        MethodIdentityJavaConstant value = new MethodIdentityJavaConstant(methodTransformInfo.getIndex());\n        StackManipulation stackManipulation = new AgentJavaConstantValue(value, methodTransformInfo.getIndex());\n        TypeDescription typeDescription = value.getTypeDescription();\n\n        OffsetMapping.Factory<Index> factory = new OffsetMapping.ForStackManipulation.Factory<>(Index.class,\n            stackManipulation,\n            typeDescription.asGenericType());\n\n        this.transformer = new AgentForAdvice(AgentAdvice.withCustomMapping()\n            .bind(factory))\n            .include(getClass().getClassLoader())\n            .advice(methodTransformInfo.getMatcher(),\n                CommonInlineAdvice.class.getCanonicalName());\n    }\n\n    @Override\n    public DynamicType.Builder<?> transform(DynamicType.Builder<?> b, TypeDescription td, ClassLoader cl, JavaModule m) {\n        CompoundClassloader.compound(this.getClass().getClassLoader(), cl);\n\n        AdviceRegistry.setCurrentClassLoader(cl);\n        DynamicType.Builder<?> bd = transformer.transform(b, td, cl, m);\n        AdviceRegistry.cleanCurrentClassLoader();\n\n        return bd;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/megaease/easeagent/core/plugin/transformer/TypeFieldTransformer.java",
    "content": "package com.megaease.easeagent.core.plugin.transformer;\n\nimport com.google.common.cache.Cache;\nimport com.google.common.cache.CacheBuilder;\nimport com.megaease.easeagent.log4j2.Logger;\nimport com.megaease.easeagent.log4j2.LoggerFactory;\nimport com.megaease.easeagent.plugin.field.TypeFieldGetter;\nimport net.bytebuddy.agent.builder.AgentBuilder;\nimport net.bytebuddy.asm.Advice;\nimport net.bytebuddy.description.type.TypeDescription;\nimport net.bytebuddy.dynamic.DynamicType;\nimport net.bytebuddy.implementation.FieldAccessor;\nimport net.bytebuddy.utility.JavaModule;\n\nimport java.util.concurrent.ConcurrentHashMap;\n\npublic class TypeFieldTransformer implements AgentBuilder.Transformer {\n    private static final Logger log = LoggerFactory.getLogger(TypeFieldTransformer.class);\n\n    private static final ConcurrentHashMap<String, Cache<ClassLoader, Boolean>> FIELD_MAP = new ConcurrentHashMap<>();\n    private final String fieldName;\n    private final Class<?> accessor;\n    private final AgentBuilder.Transformer.ForAdvice transformer;\n    public TypeFieldTransformer(String fieldName) {\n        this.fieldName = fieldName;\n        this.accessor = TypeFieldGetter.class;\n        this.transformer = new AgentBuilder.Transformer\n            .ForAdvice(Advice.withCustomMapping())\n            .include(getClass().getClassLoader());\n    }\n\n    @Override\n    public DynamicType.Builder<?> transform(DynamicType.Builder<?> b,\n                                            TypeDescription td, ClassLoader cl, JavaModule m) {\n        if (check(td, this.accessor, cl) && this.fieldName != null) {\n            try {\n                b = b.implement(this.accessor)\n                    .intercept(FieldAccessor.ofField(this.fieldName));\n            } catch (Exception e) {\n                log.debug(\"Type:{} add extend field again!\", td.getName());\n            }\n            return transformer.transform(b, td, cl, m);\n        }\n        return b;\n    }\n\n    /**\n     * Avoiding add a accessor interface to a class repeatedly\n     *\n     * @param td       represent the class to be enhanced\n     * @param accessor access interface class\n     * @param cl       current classloader\n     * @return return true when it is the first time\n     */\n    private static boolean check(TypeDescription td, Class<?> accessor, ClassLoader cl) {\n        String key = td.getCanonicalName() + accessor.getCanonicalName();\n\n        Cache<ClassLoader, Boolean> checkCache = FIELD_MAP.get(key);\n        if (checkCache == null) {\n            Cache<ClassLoader, Boolean> cache = CacheBuilder.newBuilder().weakKeys().build();\n            if (cl == null) {\n                cl = Thread.currentThread().getContextClassLoader();\n            }\n            cache.put(cl, true);\n            checkCache = FIELD_MAP.putIfAbsent(key, cache);\n            if (checkCache == null) {\n                return true;\n            }\n        }\n\n        return checkCache.getIfPresent(cl) == null;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/megaease/easeagent/core/plugin/transformer/advice/AgentAdvice.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.core.plugin.transformer.advice;\n\nimport com.megaease.easeagent.core.plugin.registry.AdviceRegistry;\nimport net.bytebuddy.ClassFileVersion;\nimport net.bytebuddy.asm.Advice;\nimport net.bytebuddy.asm.AsmVisitorWrapper;\nimport net.bytebuddy.build.HashCodeAndEqualsPlugin;\nimport net.bytebuddy.description.annotation.AnnotationDescription;\nimport net.bytebuddy.description.enumeration.EnumerationDescription;\nimport net.bytebuddy.description.field.FieldDescription;\nimport net.bytebuddy.description.method.MethodDescription;\nimport net.bytebuddy.description.method.MethodList;\nimport net.bytebuddy.description.method.ParameterDescription;\nimport net.bytebuddy.description.method.ParameterList;\nimport net.bytebuddy.description.type.TypeDefinition;\nimport net.bytebuddy.description.type.TypeDescription;\nimport net.bytebuddy.dynamic.ClassFileLocator;\nimport net.bytebuddy.dynamic.TargetType;\nimport net.bytebuddy.dynamic.scaffold.FieldLocator;\nimport net.bytebuddy.implementation.FieldAccessor;\nimport net.bytebuddy.implementation.Implementation;\nimport net.bytebuddy.implementation.SuperMethodCall;\nimport net.bytebuddy.implementation.bytecode.*;\nimport net.bytebuddy.implementation.bytecode.assign.Assigner;\nimport net.bytebuddy.implementation.bytecode.collection.ArrayAccess;\nimport net.bytebuddy.implementation.bytecode.collection.ArrayFactory;\nimport net.bytebuddy.implementation.bytecode.constant.*;\nimport net.bytebuddy.implementation.bytecode.member.FieldAccess;\nimport net.bytebuddy.implementation.bytecode.member.MethodInvocation;\nimport net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;\nimport net.bytebuddy.jar.asm.*;\nimport net.bytebuddy.utility.CompoundList;\nimport net.bytebuddy.utility.JavaConstant;\nimport net.bytebuddy.utility.JavaType;\nimport net.bytebuddy.utility.OpenedClassReader;\nimport net.bytebuddy.utility.visitor.ExceptionTableSensitiveMethodVisitor;\nimport net.bytebuddy.utility.visitor.FramePaddingMethodVisitor;\nimport net.bytebuddy.utility.visitor.LineNumberPrependingMethodVisitor;\nimport net.bytebuddy.utility.visitor.StackAwareMethodVisitor;\n\nimport java.io.IOException;\nimport java.io.Serializable;\nimport java.lang.annotation.*;\nimport java.lang.reflect.Constructor;\nimport java.lang.reflect.Method;\nimport java.util.*;\n\nimport static net.bytebuddy.matcher.ElementMatchers.*;\n\n@SuppressWarnings(\"unused, rawtypes, unchecked\")\npublic class AgentAdvice extends Advice {\n    private static final ClassReader UNDEFINED = null;\n\n    /**\n     * A reference to the {@link OnMethodEnter#skipOn()} method.\n     */\n    private static final MethodDescription.InDefinedShape SKIP_ON;\n\n    /**\n     * A reference to the {@link OnMethodEnter#prependLineNumber()} method.\n     */\n    private static final MethodDescription.InDefinedShape PREPEND_LINE_NUMBER;\n\n    /**\n     * A reference to the {@link OnMethodEnter#inline()} method.\n     */\n    private static final MethodDescription.InDefinedShape INLINE_ENTER;\n\n    /**\n     * A reference to the {@link OnMethodEnter#suppress()} method.\n     */\n    private static final MethodDescription.InDefinedShape SUPPRESS_ENTER;\n\n    /**\n     * A reference to the {@link OnMethodExit#repeatOn()} method.\n     */\n    private static final MethodDescription.InDefinedShape REPEAT_ON;\n\n    /**\n     * A reference to the {@link OnMethodExit#onThrowable()} method.\n     */\n    private static final MethodDescription.InDefinedShape ON_THROWABLE;\n\n    /**\n     * A reference to the {@link OnMethodExit#backupArguments()} method.\n     */\n    private static final MethodDescription.InDefinedShape BACKUP_ARGUMENTS;\n\n    /**\n     * A reference to the {@link OnMethodExit#inline()} method.\n     */\n    private static final MethodDescription.InDefinedShape INLINE_EXIT;\n\n    /**\n     * A reference to the {@link OnMethodExit#suppress()} method.\n     */\n    private static final MethodDescription.InDefinedShape SUPPRESS_EXIT;\n\n    static {\n        MethodList<MethodDescription.InDefinedShape> enter = TypeDescription.ForLoadedType.of(OnMethodEnter.class).getDeclaredMethods();\n        SKIP_ON = enter.filter(named(\"skipOn\")).getOnly();\n        PREPEND_LINE_NUMBER = enter.filter(named(\"prependLineNumber\")).getOnly();\n        INLINE_ENTER = enter.filter(named(\"inline\")).getOnly();\n        SUPPRESS_ENTER = enter.filter(named(\"suppress\")).getOnly();\n        MethodList<MethodDescription.InDefinedShape> exit = TypeDescription.ForLoadedType.of(OnMethodExit.class).getDeclaredMethods();\n        REPEAT_ON = exit.filter(named(\"repeatOn\")).getOnly();\n        ON_THROWABLE = exit.filter(named(\"onThrowable\")).getOnly();\n        BACKUP_ARGUMENTS = exit.filter(named(\"backupArguments\")).getOnly();\n        INLINE_EXIT = exit.filter(named(\"inline\")).getOnly();\n        SUPPRESS_EXIT = exit.filter(named(\"suppress\")).getOnly();\n    }\n\n\n    /**\n     * The dispatcher for instrumenting the instrumented method upon entering.\n     */\n    private final Dispatcher.Resolved.ForMethodEnter methodEnter;\n\n    /**\n     * The dispatcher for instrumenting the instrumented method upon exiting.\n     */\n    private final Dispatcher.Resolved.ForMethodExit methodExit;\n\n    private final Dispatcher.Resolved.ForMethodExit methodExitNonThrowable;\n\n    /**\n     * The assigner to use.\n     */\n    private final Assigner assigner;\n\n    /**\n     * The exception handler to apply.\n     */\n    private final ExceptionHandler exceptionHandler;\n\n    /**\n     * The delegate implementation to apply if this advice is used as an instrumentation.\n     */\n    private final Implementation delegate;\n\n    /**\n     * Creates a new advice.\n     *\n     * @param methodEnter The dispatcher for instrumenting the instrumented method upon entering.\n     * @param methodExit  The dispatcher for instrumenting the instrumented method upon exiting.\n     */\n    protected AgentAdvice(Dispatcher.Resolved.ForMethodEnter methodEnter,\n                          // Dispatcher.Resolved.ForMethodExit methodExitNonThrowable,\n                          Dispatcher.Resolved.ForMethodExit methodExit) {\n        this(methodEnter, methodExit,\n            null,\n            Assigner.DEFAULT, ExceptionHandler.Default.SUPPRESSING, SuperMethodCall.INSTANCE);\n    }\n\n    protected AgentAdvice(Dispatcher.Resolved.ForMethodEnter methodEnter,\n                          Dispatcher.Resolved.ForMethodExit methodExitNonThrowable,\n                          Dispatcher.Resolved.ForMethodExit methodExit) {\n        this(methodEnter, methodExit,\n            methodExitNonThrowable,\n            Assigner.DEFAULT,\n            ExceptionHandler.Default.SUPPRESSING,\n            SuperMethodCall.INSTANCE);\n    }\n\n    /**\n     * Creates a new advice.\n     *\n     * @param methodEnter      The dispatcher for instrumenting the instrumented method upon entering.\n     * @param methodExit       The dispatcher for instrumenting the instrumented method upon exiting.\n     * @param assigner         The assigner to use.\n     * @param exceptionHandler The exception handler to apply.\n     * @param delegate         The delegate implementation to apply if this advice is used as an instrumentation.\n     */\n    private AgentAdvice(Dispatcher.Resolved.ForMethodEnter methodEnter,\n                        Dispatcher.Resolved.ForMethodExit methodExit,\n                        Dispatcher.Resolved.ForMethodExit methodExitNonThrowable,\n                        Assigner assigner,\n                        ExceptionHandler exceptionHandler,\n                        Implementation delegate) {\n        super(null, null);\n        this.methodEnter = methodEnter;\n        this.methodExit = methodExit;\n        this.methodExitNonThrowable = methodExitNonThrowable;\n        this.assigner = assigner;\n        this.exceptionHandler = exceptionHandler;\n        this.delegate = delegate;\n    }\n\n    private static boolean isNoExceptionHandler(TypeDescription t) {\n        return t.getName().endsWith(\"NoExceptionHandler\");\n        // return t.represents(NoExceptionHandler.class) || t.represents()\n    }\n\n    /**\n     * Creates a new advice.\n     *\n     * @param advice               A description of the type declaring the advice.\n     * @param postProcessorFactory The post processor factory to use.\n     * @param classFileLocator     The class file locator for locating the advisory class's class file.\n     * @param userFactories        A list of custom factories for user generated offset mappings.\n     * @param delegator            The delegator to use.\n     * @return A method visitor wrapper representing the supplied advice.\n     */\n    protected static AgentAdvice tto(TypeDescription advice,\n                                     PostProcessor.Factory postProcessorFactory,\n                                     ClassFileLocator classFileLocator,\n                                     List<? extends OffsetMapping.Factory<?>> userFactories,\n                                     Delegator delegator) {\n        Dispatcher.Unresolved methodEnter = Dispatcher.Inactive.INSTANCE,\n            methodExit = Dispatcher.Inactive.INSTANCE,\n            methodExitNoException = Dispatcher.Inactive.INSTANCE;\n        for (MethodDescription.InDefinedShape methodDescription : advice.getDeclaredMethods()) {\n            methodEnter = locate(OnMethodEnter.class, INLINE_ENTER, methodEnter, methodDescription, delegator);\n            AnnotationDescription.Loadable<?> al = methodDescription.getDeclaredAnnotations().ofType(OnMethodExit.class);\n            if (al != null) {\n                TypeDescription throwable = al.getValue(ON_THROWABLE).resolve(TypeDescription.class);\n                if (isNoExceptionHandler(throwable)) {\n                    methodExitNoException = locate(OnMethodExit.class, INLINE_EXIT, methodExitNoException, methodDescription, delegator);\n                } else {\n                    methodExit = locate(OnMethodExit.class, INLINE_EXIT, methodExit, methodDescription, delegator);\n                }\n            }\n        }\n        if (methodExit == Dispatcher.Inactive.INSTANCE) {\n            methodEnter = methodExitNoException;\n        }\n        if (!methodEnter.isAlive() && !methodExit.isAlive() && !methodExitNoException.isAlive()) {\n            throw new IllegalArgumentException(\"No advice defined by \" + advice);\n        }\n        try {\n            ClassReader classReader = methodEnter.isBinary() || methodExit.isBinary()\n                ? OpenedClassReader.of(classFileLocator.locate(advice.getName()).resolve())\n                : UNDEFINED;\n            return new AgentAdvice(methodEnter.asMethodEnter(userFactories, classReader, methodExit, postProcessorFactory),\n                methodExitNoException.asMethodExit(userFactories, classReader, methodEnter, postProcessorFactory),\n                methodExit.asMethodExit(userFactories, classReader, methodEnter, postProcessorFactory));\n        } catch (IOException exception) {\n            throw new IllegalStateException(\"Error reading class file of \" + advice, exception);\n        }\n    }\n\n    public static AgentAdvice.WithCustomMapping withCustomMapping() {\n        return new WithCustomMapping();\n    }\n\n    private static Dispatcher.Unresolved locate(Class<? extends Annotation> type,\n                                                MethodDescription.InDefinedShape property,\n                                                Dispatcher.Unresolved dispatcher,\n                                                MethodDescription.InDefinedShape methodDescription,\n                                                Delegator delegator) {\n        AnnotationDescription annotation = methodDescription.getDeclaredAnnotations().ofType(type);\n        if (annotation == null) {\n            return dispatcher;\n        } else if (dispatcher.isAlive()) {\n            throw new IllegalStateException(\"Duplicate advice for \" + dispatcher + \" and \" + methodDescription);\n        } else if (!methodDescription.isStatic()) {\n            throw new IllegalStateException(\"Advice for \" + methodDescription + \" is not static\");\n        } else {\n            return new Dispatcher.Inlining(methodDescription);\n        }\n    }\n\n    /**\n     * Configures this advice to use the specified assigner. Any previous or default assigner is replaced.\n     *\n     * @param assigner The assigner to use,\n     * @return A version of this advice that uses the specified assigner.\n     */\n    @Override\n    public AgentAdvice withAssigner(Assigner assigner) {\n        return new AgentAdvice(methodEnter, methodExitNonThrowable,\n            methodExit, assigner, exceptionHandler, delegate);\n    }\n\n    /**\n     * Configures this advice to execute the given exception handler upon a suppressed exception. The stack manipulation is executed with a\n     * {@link Throwable} instance on the operand stack. The stack must be empty upon completing the exception handler.\n     *\n     * @param exceptionHandler The exception handler to apply.\n     * @return A version of this advice that applies the supplied exception handler.\n     */\n    @Override\n    public AgentAdvice withExceptionHandler(ExceptionHandler exceptionHandler) {\n        return new AgentAdvice(methodEnter, methodExitNonThrowable,\n            methodExit, assigner, exceptionHandler, delegate);\n    }\n\n    /**\n     * A builder step for creating an {@link Advice} that uses custom mappings of annotations to constant pool values.\n     */\n    @HashCodeAndEqualsPlugin.Enhance\n    public static class WithCustomMapping extends Advice.WithCustomMapping {\n        /**\n         * The post processor factory to apply.\n         */\n        private final PostProcessor.Factory postProcessorFactory;\n\n        /**\n         * The delegator to use.\n         */\n        private final Delegator delegator;\n\n        /**\n         * A map containing dynamically computed constant pool values that are mapped by their triggering annotation type.\n         */\n        private final Map<Class<? extends Annotation>, OffsetMapping.Factory<?>> offsetMappings;\n\n        /**\n         * Creates a new custom mapping builder step without including any custom mappings.\n         */\n        public WithCustomMapping() {\n            this(PostProcessor.NoOp.INSTANCE, Collections.emptyMap(), Delegator.ForStaticInvocation.INSTANCE);\n        }\n\n\n        /**\n         * Creates a new custom mapping builder step with the given custom mappings.\n         *\n         * @param postProcessorFactory The post processor factory to apply.\n         * @param offsetMappings       A map containing dynamically computed constant pool values that are mapped by their triggering annotation type.\n         * @param delegator            The delegator to use.\n         */\n        protected WithCustomMapping(PostProcessor.Factory postProcessorFactory,\n                                    Map<Class<? extends Annotation>, OffsetMapping.Factory<?>> offsetMappings,\n                                    Delegator delegator) {\n            this.postProcessorFactory = postProcessorFactory;\n            this.offsetMappings = offsetMappings;\n            this.delegator = delegator;\n        }\n\n        /**\n         * Binds the supplied annotation to a type constant of the supplied value. Constants can be strings, method handles, method types\n         * and any primitive or the value {@code null}.\n         *\n         * @param type  The type of the annotation being bound.\n         * @param value The value to bind to the annotation.\n         * @param <T>   The annotation type.\n         * @return A new builder for an advice that considers the supplied annotation type during binding.\n         */\n        @Override\n        public <T extends Annotation> WithCustomMapping bind(Class<T> type, Object value) {\n            return bind(OffsetMapping.ForStackManipulation.Factory.of(type, value));\n        }\n\n        /**\n         * Binds an annotation to a dynamically computed value. Whenever the {@link Advice} component discovers the given annotation on\n         * a parameter of an advice method, the dynamic value is asked to provide a value that is then assigned to the parameter in question.\n         *\n         * @param offsetMapping The dynamic value that is computed for binding the parameter to a value.\n         * @return A new builder for an advice that considers the supplied annotation type during binding.\n         */\n        public WithCustomMapping bind(OffsetMapping.Factory<?> offsetMapping) {\n            Map<Class<? extends Annotation>, OffsetMapping.Factory<?>> offsetMappings = new HashMap<>(this.offsetMappings);\n            if (!offsetMapping.getAnnotationType().isAnnotation()) {\n                throw new IllegalArgumentException(\"Not an annotation type: \" + offsetMapping.getAnnotationType());\n            } else if (offsetMappings.put(offsetMapping.getAnnotationType(), offsetMapping) != null) {\n                throw new IllegalArgumentException(\"Annotation type already mapped: \" + offsetMapping.getAnnotationType());\n            }\n            return new WithCustomMapping(postProcessorFactory, offsetMappings, delegator);\n        }\n\n\n        /**\n         * Implements advice where every matched method is advised by the given type's advisory methods.\n         *\n         * @param advice           A description of the type declaring the advice.\n         * @param classFileLocator The class file locator for locating the advisory class's class file.\n         * @return A method visitor wrapper representing the supplied advice.\n         */\n        @Override\n        public AgentAdvice to(TypeDescription advice, ClassFileLocator classFileLocator) {\n            return AgentAdvice.tto(advice, postProcessorFactory, classFileLocator,\n                new ArrayList<>(offsetMappings.values()), delegator);\n        }\n    }\n\n    /**\n     * Wraps the method visitor to implement this advice.\n     *\n     * @param instrumentedType      The instrumented type.\n     * @param instrumentedMethod    The instrumented method.\n     * @param methodVisitor         The method visitor to write to.\n     * @param implementationContext The implementation context to use.\n     * @param writerFlags           The ASM writer flags to use.\n     * @param readerFlags           The, plies this advice.\n     */\n    @Override\n    protected MethodVisitor doWrap(TypeDescription instrumentedType,\n                                   MethodDescription instrumentedMethod,\n                                   MethodVisitor methodVisitor,\n                                   Implementation.Context implementationContext,\n                                   int writerFlags,\n                                   int readerFlags) {\n        Dispatcher.Resolved.ForMethodExit exit;\n        if (instrumentedMethod.isConstructor()) {\n            exit = methodExitNonThrowable;\n        } else {\n            exit = methodExit;\n        }\n\n        if (AdviceRegistry.check(instrumentedType, instrumentedMethod, methodEnter, exit) == 0) {\n            return methodVisitor;\n        }\n\n        methodVisitor = new FramePaddingMethodVisitor(methodEnter.isPrependLineNumber()\n            ? new LineNumberPrependingMethodVisitor(methodVisitor)\n            : methodVisitor);\n\n        if (instrumentedMethod.isConstructor()) {\n            return new WithExitAdvice.WithoutExceptionHandling(methodVisitor,\n                implementationContext,\n                assigner,\n                exceptionHandler.resolve(instrumentedMethod, instrumentedType),\n                instrumentedType,\n                instrumentedMethod,\n                methodEnter,\n                methodExitNonThrowable,\n                writerFlags,\n                readerFlags);\n        } else {\n            return new WithExitAdvice.WithExceptionHandling(methodVisitor,\n                implementationContext,\n                assigner,\n                exceptionHandler.resolve(instrumentedMethod, instrumentedType),\n                instrumentedType,\n                instrumentedMethod,\n                methodEnter,\n                methodExit,\n                writerFlags,\n                readerFlags,\n                methodExit.getThrowable());\n        }\n    }\n\n    /**\n     * A method visitor that weaves the advice methods' byte codes.\n     */\n    protected abstract static class AdviceVisitor\n        extends ExceptionTableSensitiveMethodVisitor implements Dispatcher.RelocationHandler.Relocation {\n\n        /**\n         * The expected index for the {@code this} variable.\n         */\n        private static final int THIS_VARIABLE_INDEX = 0;\n\n        /**\n         * The expected name for the {@code this} variable.\n         */\n        private static final String THIS_VARIABLE_NAME = \"this\";\n\n        /**\n         * A description of the instrumented method.\n         */\n        protected final MethodDescription instrumentedMethod;\n\n        /**\n         * A label that indicates the start of the preparation of a user method execution.\n         */\n        private final Label preparationStart;\n\n        /**\n         * The dispatcher to be used for method enter.\n         */\n        private final Dispatcher.Bound methodEnter;\n\n        /**\n         * The dispatcher to be used for method exit.\n         */\n        protected final Dispatcher.Bound methodExit;\n\n        /**\n         * The handler for accessing arguments of the method's local variable array.\n         */\n        protected final ArgumentHandler.ForInstrumentedMethod argumentHandler;\n\n        /**\n         * A handler for computing the method size requirements.\n         */\n        protected final MethodSizeHandler.ForInstrumentedMethod methodSizeHandler;\n\n        /**\n         * A handler for translating and injecting stack map frames.\n         */\n        protected final StackMapFrameHandler.ForInstrumentedMethod stackMapFrameHandler;\n\n        /**\n         * Creates a new advice visitor.\n         *\n         * @param methodVisitor         The actual method visitor that is underlying this method visitor to which all instructions are written.\n         * @param implementationContext The implementation context to use.\n         * @param assigner              The assigner to use.\n         * @param exceptionHandler      The stack manipulation to apply within a suppression handler.\n         * @param instrumentedType      A description of the instrumented type.\n         * @param instrumentedMethod    The instrumented method.\n         * @param methodEnter           The method enter advice.\n         * @param methodExit            The method exit advice.\n         * @param postMethodTypes       A list of virtual method arguments that are available after the instrumented method has completed.\n         * @param writerFlags           The ASM writer flags that were set.\n         * @param readerFlags           The ASM reader flags that were set.\n         */\n        protected AdviceVisitor(MethodVisitor methodVisitor,\n                                Context implementationContext,\n                                Assigner assigner,\n                                StackManipulation exceptionHandler,\n                                TypeDescription instrumentedType,\n                                MethodDescription instrumentedMethod,\n                                Dispatcher.Resolved.ForMethodEnter methodEnter,\n                                Dispatcher.Resolved.ForMethodExit methodExit,\n                                List<? extends TypeDescription> postMethodTypes,\n                                int writerFlags,\n                                int readerFlags) {\n            super(OpenedClassReader.ASM_API, methodVisitor);\n            this.instrumentedMethod = instrumentedMethod;\n            preparationStart = new Label();\n            SortedMap<String, TypeDefinition> namedTypes = new TreeMap<>();\n            namedTypes.putAll(methodEnter.getNamedTypes());\n            namedTypes.putAll(methodExit.getNamedTypes());\n            argumentHandler = methodExit.getArgumentHandlerFactory().resolve(instrumentedMethod,\n                methodEnter.getAdviceType(),\n                methodExit.getAdviceType(),\n                namedTypes);\n            List<TypeDescription> initialTypes = CompoundList.of(methodExit.getAdviceType().represents(void.class)\n                ? Collections.emptyList()\n                : Collections.singletonList(methodExit.getAdviceType().asErasure()), argumentHandler.getNamedTypes());\n            List<TypeDescription> latentTypes = methodEnter.getActualAdviceType().represents(void.class)\n                ? Collections.emptyList()\n                : Collections.singletonList(methodEnter.getActualAdviceType().asErasure());\n            List<TypeDescription> preMethodTypes = methodEnter.getAdviceType().represents(void.class)\n                ? Collections.emptyList()\n                : Collections.singletonList(methodEnter.getAdviceType().asErasure());\n            methodSizeHandler = MethodSizeHandler.Default.of(instrumentedMethod,\n                initialTypes,\n                preMethodTypes,\n                postMethodTypes,\n                argumentHandler.isCopyingArguments(),\n                writerFlags);\n            stackMapFrameHandler = StackMapFrameHandler.Default.of(instrumentedType,\n                instrumentedMethod,\n                initialTypes,\n                latentTypes,\n                preMethodTypes,\n                postMethodTypes,\n                methodExit.isAlive(),\n                argumentHandler.isCopyingArguments(),\n                implementationContext.getClassFileVersion(),\n                writerFlags,\n                readerFlags);\n            this.methodEnter = methodEnter.bind(instrumentedType,\n                instrumentedMethod,\n                methodVisitor,\n                implementationContext,\n                assigner,\n                argumentHandler,\n                methodSizeHandler,\n                stackMapFrameHandler,\n                exceptionHandler,\n                this);\n            this.methodExit = methodExit.bind(instrumentedType,\n                instrumentedMethod,\n                methodVisitor,\n                implementationContext,\n                assigner,\n                argumentHandler,\n                methodSizeHandler,\n                stackMapFrameHandler,\n                exceptionHandler,\n                new ForLabel(preparationStart));\n        }\n\n        @Override\n        protected void onAfterExceptionTable() {\n            methodEnter.prepare();\n            onUserPrepare();\n            methodExit.prepare();\n            methodEnter.initialize();\n            methodExit.initialize();\n            stackMapFrameHandler.injectInitializationFrame(mv);\n            methodEnter.apply();\n            mv.visitLabel(preparationStart);\n            methodSizeHandler.requireStackSize(argumentHandler.prepare(mv));\n            stackMapFrameHandler.injectStartFrame(mv);\n            onUserStart();\n        }\n\n        /**\n         * Invoked when the user method's exception handler (if any) is supposed to be prepared.\n         */\n        protected abstract void onUserPrepare();\n\n        /**\n         * Writes the advice for entering the instrumented method.\n         */\n        protected abstract void onUserStart();\n\n        @Override\n        protected void onVisitVarInsn(int opcode, int offset) {\n            mv.visitVarInsn(opcode, argumentHandler.argument(offset));\n        }\n\n        @Override\n        protected void onVisitIincInsn(int offset, int increment) {\n            mv.visitIincInsn(argumentHandler.argument(offset), increment);\n        }\n\n        @Override\n        public void onVisitFrame(int type, int localVariableLength, Object[] localVariable, int stackSize, Object[] stack) {\n            stackMapFrameHandler.translateFrame(mv, type, localVariableLength, localVariable, stackSize, stack);\n        }\n\n        @Override\n        public void visitMaxs(int stackSize, int localVariableLength) {\n            onUserEnd();\n            mv.visitMaxs(methodSizeHandler.compoundStackSize(stackSize), methodSizeHandler.compoundLocalVariableLength(localVariableLength));\n        }\n\n        @Override\n        public void visitLocalVariable(String name, String descriptor, String signature, Label start, Label end, int index) {\n            // The 'this' variable is exempt from remapping as it is assumed immutable and remapping it confuses debuggers to not display the variable.\n            mv.visitLocalVariable(name, descriptor, signature, start, end, index == THIS_VARIABLE_INDEX && THIS_VARIABLE_NAME.equals(name)\n                ? index\n                : argumentHandler.variable(index));\n        }\n\n        @Override\n        public AnnotationVisitor visitLocalVariableAnnotation(int typeReference,\n                                                              TypePath typePath,\n                                                              Label[] start,\n                                                              Label[] end,\n                                                              int[] index,\n                                                              String descriptor,\n                                                              boolean visible) {\n            int[] translated = new int[index.length];\n            for (int anIndex = 0; anIndex < index.length; anIndex++) {\n                translated[anIndex] = argumentHandler.variable(index[anIndex]);\n            }\n            return mv.visitLocalVariableAnnotation(typeReference, typePath, start, end, translated, descriptor, visible);\n        }\n\n        /**\n         * Writes the advice for completing the instrumented method.\n         */\n        protected abstract void onUserEnd();\n\n    }\n\n    /**\n     * An advice visitor that does not apply exit advice.\n     */\n    protected static class WithoutExitAdvice extends AdviceVisitor {\n\n        /**\n         * Creates an advice visitor that does not apply exit advice.\n         *\n         * @param methodVisitor         The method visitor for the instrumented method.\n         * @param implementationContext The implementation context to use.\n         * @param assigner              The assigner to use.\n         * @param exceptionHandler      The stack manipulation to apply within a suppression handler.\n         * @param instrumentedType      A description of the instrumented type.\n         * @param instrumentedMethod    A description of the instrumented method.\n         * @param methodEnter           The dispatcher to be used for method enter.\n         * @param writerFlags           The ASM writer flags that were set.\n         * @param readerFlags           The ASM reader flags that were set.\n         */\n        protected WithoutExitAdvice(MethodVisitor methodVisitor,\n                                    Implementation.Context implementationContext,\n                                    Assigner assigner,\n                                    StackManipulation exceptionHandler,\n                                    TypeDescription instrumentedType,\n                                    MethodDescription instrumentedMethod,\n                                    Dispatcher.Resolved.ForMethodEnter methodEnter,\n                                    int writerFlags,\n                                    int readerFlags) {\n            super(methodVisitor,\n                implementationContext,\n                assigner,\n                exceptionHandler,\n                instrumentedType,\n                instrumentedMethod,\n                methodEnter,\n                Dispatcher.Inactive.INSTANCE,\n                Collections.emptyList(),\n                writerFlags,\n                readerFlags);\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        public void apply(MethodVisitor methodVisitor) {\n            if (instrumentedMethod.getReturnType().represents(boolean.class)\n                || instrumentedMethod.getReturnType().represents(byte.class)\n                || instrumentedMethod.getReturnType().represents(short.class)\n                || instrumentedMethod.getReturnType().represents(char.class)\n                || instrumentedMethod.getReturnType().represents(int.class)) {\n                methodVisitor.visitInsn(Opcodes.ICONST_0);\n                methodVisitor.visitInsn(Opcodes.IRETURN);\n            } else if (instrumentedMethod.getReturnType().represents(long.class)) {\n                methodVisitor.visitInsn(Opcodes.LCONST_0);\n                methodVisitor.visitInsn(Opcodes.LRETURN);\n            } else if (instrumentedMethod.getReturnType().represents(float.class)) {\n                methodVisitor.visitInsn(Opcodes.FCONST_0);\n                methodVisitor.visitInsn(Opcodes.FRETURN);\n            } else if (instrumentedMethod.getReturnType().represents(double.class)) {\n                methodVisitor.visitInsn(Opcodes.DCONST_0);\n                methodVisitor.visitInsn(Opcodes.DRETURN);\n            } else if (instrumentedMethod.getReturnType().represents(void.class)) {\n                methodVisitor.visitInsn(Opcodes.RETURN);\n            } else {\n                methodVisitor.visitInsn(Opcodes.ACONST_NULL);\n                methodVisitor.visitInsn(Opcodes.ARETURN);\n            }\n        }\n\n        @Override\n        protected void onUserPrepare() {\n            /* do nothing */\n        }\n\n        @Override\n        protected void onUserStart() {\n            /* do nothing */\n        }\n\n        @Override\n        protected void onUserEnd() {\n            /* do nothing */\n        }\n    }\n\n    /**\n     * An advice visitor that applies exit advice.\n     */\n    protected abstract static class WithExitAdvice extends AdviceVisitor {\n        /**\n         * Indicates the handler for the value returned by the advice method.\n         */\n        protected final Label returnHandler;\n\n        /**\n         * Creates an advice visitor that applies exit advice.\n         *\n         * @param methodVisitor         The method visitor for the instrumented method.\n         * @param implementationContext The implementation context to use.\n         * @param assigner              The assigner to use.\n         * @param exceptionHandler      The stack manipulation to apply within a suppression handler.\n         * @param instrumentedType      A description of the instrumented type.\n         * @param instrumentedMethod    A description of the instrumented method.\n         * @param methodEnter           The dispatcher to be used for method enter.\n         * @param methodExit            The dispatcher to be used for method exit.\n         * @param postMethodTypes       A list of virtual method arguments that are available after the instrumented method has completed.\n         * @param writerFlags           The ASM writer flags that were set.\n         * @param readerFlags           The ASM reader flags that were set.\n         */\n        protected WithExitAdvice(MethodVisitor methodVisitor,\n                                 Implementation.Context implementationContext,\n                                 Assigner assigner,\n                                 StackManipulation exceptionHandler,\n                                 TypeDescription instrumentedType,\n                                 MethodDescription instrumentedMethod,\n                                 Dispatcher.Resolved.ForMethodEnter methodEnter,\n                                 Dispatcher.Resolved.ForMethodExit methodExit,\n                                 List<? extends TypeDescription> postMethodTypes,\n                                 int writerFlags,\n                                 int readerFlags) {\n            super(new StackAwareMethodVisitor(methodVisitor, instrumentedMethod),\n                implementationContext,\n                assigner,\n                exceptionHandler,\n                instrumentedType,\n                instrumentedMethod,\n                methodEnter,\n                methodExit,\n                postMethodTypes,\n                writerFlags,\n                readerFlags);\n            returnHandler = new Label();\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        public void apply(MethodVisitor methodVisitor) {\n            if (instrumentedMethod.getReturnType().represents(boolean.class)\n                || instrumentedMethod.getReturnType().represents(byte.class)\n                || instrumentedMethod.getReturnType().represents(short.class)\n                || instrumentedMethod.getReturnType().represents(char.class)\n                || instrumentedMethod.getReturnType().represents(int.class)) {\n                methodVisitor.visitInsn(Opcodes.ICONST_0);\n            } else if (instrumentedMethod.getReturnType().represents(long.class)) {\n                methodVisitor.visitInsn(Opcodes.LCONST_0);\n            } else if (instrumentedMethod.getReturnType().represents(float.class)) {\n                methodVisitor.visitInsn(Opcodes.FCONST_0);\n            } else if (instrumentedMethod.getReturnType().represents(double.class)) {\n                methodVisitor.visitInsn(Opcodes.DCONST_0);\n            } else if (!instrumentedMethod.getReturnType().represents(void.class)) {\n                methodVisitor.visitInsn(Opcodes.ACONST_NULL);\n            }\n            methodVisitor.visitJumpInsn(Opcodes.GOTO, returnHandler);\n        }\n\n        @Override\n        protected void onVisitInsn(int opcode) {\n            switch (opcode) {\n                case Opcodes.RETURN:\n                    ((StackAwareMethodVisitor) mv).drainStack();\n                    break;\n                case Opcodes.IRETURN:\n                    methodSizeHandler.requireLocalVariableLength(((StackAwareMethodVisitor) mv).drainStack(Opcodes.ISTORE, Opcodes.ILOAD, StackSize.SINGLE));\n                    break;\n                case Opcodes.FRETURN:\n                    methodSizeHandler.requireLocalVariableLength(((StackAwareMethodVisitor) mv).drainStack(Opcodes.FSTORE, Opcodes.FLOAD, StackSize.SINGLE));\n                    break;\n                case Opcodes.DRETURN:\n                    methodSizeHandler.requireLocalVariableLength(((StackAwareMethodVisitor) mv).drainStack(Opcodes.DSTORE, Opcodes.DLOAD, StackSize.DOUBLE));\n                    break;\n                case Opcodes.LRETURN:\n                    methodSizeHandler.requireLocalVariableLength((((StackAwareMethodVisitor) mv).drainStack(Opcodes.LSTORE, Opcodes.LLOAD, StackSize.DOUBLE)));\n                    break;\n                case Opcodes.ARETURN:\n                    methodSizeHandler.requireLocalVariableLength((((StackAwareMethodVisitor) mv).drainStack(Opcodes.ASTORE, Opcodes.ALOAD, StackSize.SINGLE)));\n                    break;\n                default:\n                    mv.visitInsn(opcode);\n                    return;\n            }\n            mv.visitJumpInsn(Opcodes.GOTO, returnHandler);\n        }\n\n        @Override\n        protected void onUserEnd() {\n            mv.visitLabel(returnHandler);\n            onUserReturn();\n            stackMapFrameHandler.injectCompletionFrame(mv);\n            methodExit.apply();\n            onExitAdviceReturn();\n            if (instrumentedMethod.getReturnType().represents(boolean.class)\n                || instrumentedMethod.getReturnType().represents(byte.class)\n                || instrumentedMethod.getReturnType().represents(short.class)\n                || instrumentedMethod.getReturnType().represents(char.class)\n                || instrumentedMethod.getReturnType().represents(int.class)) {\n                mv.visitVarInsn(Opcodes.ILOAD, argumentHandler.returned());\n                mv.visitInsn(Opcodes.IRETURN);\n            } else if (instrumentedMethod.getReturnType().represents(long.class)) {\n                mv.visitVarInsn(Opcodes.LLOAD, argumentHandler.returned());\n                mv.visitInsn(Opcodes.LRETURN);\n            } else if (instrumentedMethod.getReturnType().represents(float.class)) {\n                mv.visitVarInsn(Opcodes.FLOAD, argumentHandler.returned());\n                mv.visitInsn(Opcodes.FRETURN);\n            } else if (instrumentedMethod.getReturnType().represents(double.class)) {\n                mv.visitVarInsn(Opcodes.DLOAD, argumentHandler.returned());\n                mv.visitInsn(Opcodes.DRETURN);\n            } else if (!instrumentedMethod.getReturnType().represents(void.class)) {\n                mv.visitVarInsn(Opcodes.ALOAD, argumentHandler.returned());\n                mv.visitInsn(Opcodes.ARETURN);\n            } else {\n                mv.visitInsn(Opcodes.RETURN);\n            }\n            methodSizeHandler.requireStackSize(instrumentedMethod.getReturnType().getStackSize().getSize());\n        }\n\n        /**\n         * Invoked after the user method has returned.\n         */\n        protected abstract void onUserReturn();\n\n        /**\n         * Invoked after the exit advice method has returned.\n         */\n        protected abstract void onExitAdviceReturn();\n\n        /**\n         * An advice visitor that does not capture exceptions.\n         */\n        protected static class WithoutExceptionHandling extends WithExitAdvice {\n\n            /**\n             * Creates a new advice visitor that does not capture exceptions.\n             *\n             * @param methodVisitor         The method visitor for the instrumented method.\n             * @param implementationContext The implementation context to use.\n             * @param assigner              The assigner to use.\n             * @param exceptionHandler      The stack manipulation to apply within a suppression handler.\n             * @param instrumentedType      A description of the instrumented type.\n             * @param instrumentedMethod    A description of the instrumented method.\n             * @param methodEnter           The dispatcher to be used for method enter.\n             * @param methodExit            The dispatcher to be used for method exit.\n             * @param writerFlags           The ASM writer flags that were set.\n             * @param readerFlags           The ASM reader flags that were set.\n             */\n            protected WithoutExceptionHandling(MethodVisitor methodVisitor,\n                                               Implementation.Context implementationContext,\n                                               Assigner assigner,\n                                               StackManipulation exceptionHandler,\n                                               TypeDescription instrumentedType,\n                                               MethodDescription instrumentedMethod,\n                                               Dispatcher.Resolved.ForMethodEnter methodEnter,\n                                               Dispatcher.Resolved.ForMethodExit methodExit,\n                                               int writerFlags,\n                                               int readerFlags) {\n                super(methodVisitor,\n                    implementationContext,\n                    assigner,\n                    exceptionHandler,\n                    instrumentedType,\n                    instrumentedMethod,\n                    methodEnter,\n                    methodExit,\n                    instrumentedMethod.getReturnType().represents(void.class)\n                        ? Collections.emptyList()\n                        : Collections.singletonList(instrumentedMethod.getReturnType().asErasure()),\n                    writerFlags,\n                    readerFlags);\n            }\n\n            @Override\n            protected void onUserPrepare() {\n                /* do nothing */\n            }\n\n            @Override\n            protected void onUserStart() {\n                /* do nothing */\n            }\n\n            @Override\n            protected void onUserReturn() {\n                if (instrumentedMethod.getReturnType().represents(boolean.class)\n                    || instrumentedMethod.getReturnType().represents(byte.class)\n                    || instrumentedMethod.getReturnType().represents(short.class)\n                    || instrumentedMethod.getReturnType().represents(char.class)\n                    || instrumentedMethod.getReturnType().represents(int.class)) {\n                    stackMapFrameHandler.injectReturnFrame(mv);\n                    mv.visitVarInsn(Opcodes.ISTORE, argumentHandler.returned());\n                } else if (instrumentedMethod.getReturnType().represents(long.class)) {\n                    stackMapFrameHandler.injectReturnFrame(mv);\n                    mv.visitVarInsn(Opcodes.LSTORE, argumentHandler.returned());\n                } else if (instrumentedMethod.getReturnType().represents(float.class)) {\n                    stackMapFrameHandler.injectReturnFrame(mv);\n                    mv.visitVarInsn(Opcodes.FSTORE, argumentHandler.returned());\n                } else if (instrumentedMethod.getReturnType().represents(double.class)) {\n                    stackMapFrameHandler.injectReturnFrame(mv);\n                    mv.visitVarInsn(Opcodes.DSTORE, argumentHandler.returned());\n                } else if (!instrumentedMethod.getReturnType().represents(void.class)) {\n                    stackMapFrameHandler.injectReturnFrame(mv);\n                    mv.visitVarInsn(Opcodes.ASTORE, argumentHandler.returned());\n                }\n            }\n\n            @Override\n            protected void onExitAdviceReturn() {\n                /* do nothing */\n            }\n        }\n\n        /**\n         * An advice visitor that captures exceptions by weaving try-catch blocks around user code.\n         */\n        protected static class WithExceptionHandling extends WithExitAdvice {\n            /**\n             * The type of the handled throwable type for which this advice is invoked.\n             */\n            private final TypeDescription throwable;\n\n            /**\n             * Indicates the exception handler.\n             */\n            private final Label exceptionHandler;\n\n            /**\n             * Indicates the start of the user method.\n             */\n            protected final Label userStart;\n\n            /**\n             * Creates a new advice visitor that captures exception by weaving try-catch blocks around user code.\n             *\n             * @param methodVisitor         The method visitor for the instrumented method.\n             * @param instrumentedType      A description of the instrumented type.\n             * @param implementationContext The implementation context to use.\n             * @param assigner              The assigner to use.\n             * @param exceptionHandler      The stack manipulation to apply within a suppression handler.\n             * @param instrumentedMethod    A description of the instrumented method.\n             * @param methodEnter           The dispatcher to be used for method enter.\n             * @param methodExit            The dispatcher to be used for method exit.\n             * @param writerFlags           The ASM writer flags that were set.\n             * @param readerFlags           The ASM reader flags that were set.\n             * @param throwable             The type of the handled throwable type for which this advice is invoked.\n             */\n            protected WithExceptionHandling(MethodVisitor methodVisitor,\n                                            Implementation.Context implementationContext,\n                                            Assigner assigner,\n                                            StackManipulation exceptionHandler,\n                                            TypeDescription instrumentedType,\n                                            MethodDescription instrumentedMethod,\n                                            Dispatcher.Resolved.ForMethodEnter methodEnter,\n                                            Dispatcher.Resolved.ForMethodExit methodExit,\n                                            int writerFlags,\n                                            int readerFlags,\n                                            TypeDescription throwable) {\n                super(methodVisitor,\n                    implementationContext,\n                    assigner,\n                    exceptionHandler,\n                    instrumentedType,\n                    instrumentedMethod,\n                    methodEnter,\n                    methodExit,\n                    instrumentedMethod.getReturnType().represents(void.class)\n                        ? Collections.singletonList(TypeDescription.THROWABLE)\n                        : Arrays.asList(instrumentedMethod.getReturnType().asErasure(), TypeDescription.THROWABLE),\n                    writerFlags,\n                    readerFlags);\n                this.throwable = throwable;\n                this.exceptionHandler = new Label();\n                userStart = new Label();\n            }\n\n            @Override\n            protected void onUserPrepare() {\n                mv.visitTryCatchBlock(userStart, returnHandler, exceptionHandler, throwable.getInternalName());\n            }\n\n            @Override\n            protected void onUserStart() {\n                mv.visitLabel(userStart);\n            }\n\n            @Override\n            protected void onUserReturn() {\n                stackMapFrameHandler.injectReturnFrame(mv);\n                if (instrumentedMethod.getReturnType().represents(boolean.class)\n                    || instrumentedMethod.getReturnType().represents(byte.class)\n                    || instrumentedMethod.getReturnType().represents(short.class)\n                    || instrumentedMethod.getReturnType().represents(char.class)\n                    || instrumentedMethod.getReturnType().represents(int.class)) {\n                    mv.visitVarInsn(Opcodes.ISTORE, argumentHandler.returned());\n                } else if (instrumentedMethod.getReturnType().represents(long.class)) {\n                    mv.visitVarInsn(Opcodes.LSTORE, argumentHandler.returned());\n                } else if (instrumentedMethod.getReturnType().represents(float.class)) {\n                    mv.visitVarInsn(Opcodes.FSTORE, argumentHandler.returned());\n                } else if (instrumentedMethod.getReturnType().represents(double.class)) {\n                    mv.visitVarInsn(Opcodes.DSTORE, argumentHandler.returned());\n                } else if (!instrumentedMethod.getReturnType().represents(void.class)) {\n                    mv.visitVarInsn(Opcodes.ASTORE, argumentHandler.returned());\n                }\n                mv.visitInsn(Opcodes.ACONST_NULL);\n                mv.visitVarInsn(Opcodes.ASTORE, argumentHandler.thrown());\n                Label endOfHandler = new Label();\n                mv.visitJumpInsn(Opcodes.GOTO, endOfHandler);\n                mv.visitLabel(exceptionHandler);\n                stackMapFrameHandler.injectExceptionFrame(mv);\n                mv.visitVarInsn(Opcodes.ASTORE, argumentHandler.thrown());\n                if (instrumentedMethod.getReturnType().represents(boolean.class)\n                    || instrumentedMethod.getReturnType().represents(byte.class)\n                    || instrumentedMethod.getReturnType().represents(short.class)\n                    || instrumentedMethod.getReturnType().represents(char.class)\n                    || instrumentedMethod.getReturnType().represents(int.class)) {\n                    mv.visitInsn(Opcodes.ICONST_0);\n                    mv.visitVarInsn(Opcodes.ISTORE, argumentHandler.returned());\n                } else if (instrumentedMethod.getReturnType().represents(long.class)) {\n                    mv.visitInsn(Opcodes.LCONST_0);\n                    mv.visitVarInsn(Opcodes.LSTORE, argumentHandler.returned());\n                } else if (instrumentedMethod.getReturnType().represents(float.class)) {\n                    mv.visitInsn(Opcodes.FCONST_0);\n                    mv.visitVarInsn(Opcodes.FSTORE, argumentHandler.returned());\n                } else if (instrumentedMethod.getReturnType().represents(double.class)) {\n                    mv.visitInsn(Opcodes.DCONST_0);\n                    mv.visitVarInsn(Opcodes.DSTORE, argumentHandler.returned());\n                } else if (!instrumentedMethod.getReturnType().represents(void.class)) {\n                    mv.visitInsn(Opcodes.ACONST_NULL);\n                    mv.visitVarInsn(Opcodes.ASTORE, argumentHandler.returned());\n                }\n                mv.visitLabel(endOfHandler);\n                methodSizeHandler.requireStackSize(StackSize.SINGLE.getSize());\n            }\n\n            @Override\n            protected void onExitAdviceReturn() {\n                mv.visitVarInsn(Opcodes.ALOAD, argumentHandler.thrown());\n                Label endOfHandler = new Label();\n                mv.visitJumpInsn(Opcodes.IFNULL, endOfHandler);\n                mv.visitVarInsn(Opcodes.ALOAD, argumentHandler.thrown());\n                mv.visitInsn(Opcodes.ATHROW);\n                mv.visitLabel(endOfHandler);\n                stackMapFrameHandler.injectPostCompletionFrame(mv);\n            }\n        }\n    }\n\n\n    /**\n     * A dispatcher for implementing advice.\n     */\n    public interface Dispatcher {\n\n        /**\n         * Indicates that a method does not represent advice and does not need to be visited.\n         */\n        MethodVisitor IGNORE_METHOD = null;\n\n        /**\n         * Expresses that an annotation should not be visited.\n         */\n        AnnotationVisitor IGNORE_ANNOTATION = null;\n\n        /**\n         * Returns {@code true} if this dispatcher is alive.\n         *\n         * @return {@code true} if this dispatcher is alive.\n         */\n        boolean isAlive();\n\n        /**\n         * The type that is produced as a result of executing this advice method.\n         *\n         * @return A description of the type that is produced by this advice method.\n         */\n        TypeDefinition getAdviceType();\n\n        /**\n         * A dispatcher that is not yet resolved.\n         */\n        interface Unresolved extends Dispatcher {\n\n            /**\n             * Indicates that this dispatcher requires access to the class file declaring the advice method.\n             *\n             * @return {@code true} if this dispatcher requires access to the advice method's class file.\n             */\n            boolean isBinary();\n\n            /**\n             * Returns the named types declared by this enter advice.\n             *\n             * @return The named types declared by this enter advice.\n             */\n            Map<String, TypeDefinition> getNamedTypes();\n\n            /**\n             * Resolves this dispatcher as a dispatcher for entering a method.\n             *\n             * @param userFactories        A list of custom factories for binding parameters of an advice method.\n             * @param classReader          A class reader to query for a class file which might be {@code null} if this dispatcher is not binary.\n             * @param methodExit           The unresolved dispatcher for the method exit advice.\n             * @param postProcessorFactory The post processor factory to use.\n             * @return This dispatcher as a dispatcher for entering a method.\n             */\n            Resolved.ForMethodEnter asMethodEnter(List<? extends OffsetMapping.Factory<?>> userFactories,\n                                                  ClassReader classReader,\n                                                  Unresolved methodExit,\n                                                  PostProcessor.Factory postProcessorFactory);\n\n            /**\n             * Resolves this dispatcher as a dispatcher for exiting a method.\n             *\n             * @param userFactories        A list of custom factories for binding parameters of an advice method.\n             * @param classReader          A class reader to query for a class file which might be {@code null} if this dispatcher is not binary.\n             * @param methodEnter          The unresolved dispatcher for the method enter advice.\n             * @param postProcessorFactory The post processor factory to use.\n             * @return This dispatcher as a dispatcher for exiting a method.\n             */\n            Resolved.ForMethodExit asMethodExit(List<? extends OffsetMapping.Factory<?>> userFactories,\n                                                ClassReader classReader,\n                                                Unresolved methodEnter,\n                                                PostProcessor.Factory postProcessorFactory);\n        }\n\n\n        /**\n         * A suppression handler for optionally suppressing exceptions.\n         */\n        interface SuppressionHandler {\n\n            /**\n             * Binds the suppression handler for instrumenting a specific method.\n             *\n             * @param exceptionHandler The stack manipulation to apply within a suppression handler.\n             * @return A bound version of the suppression handler.\n             */\n            Bound bind(StackManipulation exceptionHandler);\n\n            /**\n             * A bound version of a suppression handler that must not be reused.\n             */\n            interface Bound {\n\n                /**\n                 * Invoked to prepare the suppression handler, i.e. to write an exception handler entry if appropriate.\n                 *\n                 * @param methodVisitor The method visitor to apply the preparation to.\n                 */\n                void onPrepare(MethodVisitor methodVisitor);\n\n                /**\n                 * Invoked at the start of a method.\n                 *\n                 * @param methodVisitor The method visitor of the instrumented method.\n                 */\n                void onStart(MethodVisitor methodVisitor);\n\n                /**\n                 * Invoked at the end of a method.\n                 *\n                 * @param methodVisitor         The method visitor of the instrumented method.\n                 * @param implementationContext The implementation context to use.\n                 * @param methodSizeHandler     The advice method's method size handler.\n                 * @param stackMapFrameHandler  A handler for translating and injecting stack map frames.\n                 * @param returnType            The return type of the advice method.\n                 */\n                void onEnd(MethodVisitor methodVisitor,\n                           Implementation.Context implementationContext,\n                           MethodSizeHandler.ForAdvice methodSizeHandler,\n                           StackMapFrameHandler.ForAdvice stackMapFrameHandler,\n                           TypeDefinition returnType);\n\n                /**\n                 * Invoked at the end of a method if the exception handler should be wrapped in a skipping block.\n                 *\n                 * @param methodVisitor         The method visitor of the instrumented method.\n                 * @param implementationContext The implementation context to use.\n                 * @param methodSizeHandler     The advice method's method size handler.\n                 * @param stackMapFrameHandler  A handler for translating and injecting stack map frames.\n                 * @param returnType            The return type of the advice method.\n                 */\n                void onEndWithSkip(MethodVisitor methodVisitor,\n                                   Implementation.Context implementationContext,\n                                   MethodSizeHandler.ForAdvice methodSizeHandler,\n                                   StackMapFrameHandler.ForAdvice stackMapFrameHandler,\n                                   TypeDefinition returnType);\n            }\n\n            /**\n             * A non-operational suppression handler that does not suppress any method.\n             */\n            enum NoOp implements SuppressionHandler, Bound {\n\n                /**\n                 * The singleton instance.\n                 */\n                INSTANCE;\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public Bound bind(StackManipulation exceptionHandler) {\n                    return this;\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public void onPrepare(MethodVisitor methodVisitor) {\n                    /* do nothing */\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public void onStart(MethodVisitor methodVisitor) {\n                    /* do nothing */\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public void onEnd(MethodVisitor methodVisitor,\n                                  Implementation.Context implementationContext,\n                                  MethodSizeHandler.ForAdvice methodSizeHandler,\n                                  StackMapFrameHandler.ForAdvice stackMapFrameHandler,\n                                  TypeDefinition returnType) {\n                    /* do nothing */\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public void onEndWithSkip(MethodVisitor methodVisitor,\n                                          Implementation.Context implementationContext,\n                                          MethodSizeHandler.ForAdvice methodSizeHandler,\n                                          StackMapFrameHandler.ForAdvice stackMapFrameHandler,\n                                          TypeDefinition returnType) {\n                    /* do nothing */\n                }\n            }\n\n            /**\n             * A suppression handler that suppresses a given throwable type.\n             */\n            @HashCodeAndEqualsPlugin.Enhance\n            class Suppressing implements SuppressionHandler {\n\n                /**\n                 * The suppressed throwable type.\n                 */\n                private final TypeDescription suppressedType;\n\n                /**\n                 * Creates a new suppressing suppression handler.\n                 *\n                 * @param suppressedType The suppressed throwable type.\n                 */\n                protected Suppressing(TypeDescription suppressedType) {\n                    this.suppressedType = suppressedType;\n                }\n\n                /**\n                 * Resolves an appropriate suppression handler.\n                 *\n                 * @param suppressedType The suppressed type or {@link NoExceptionHandler} if no type should be suppressed.\n                 * @return An appropriate suppression handler.\n                 */\n                protected static SuppressionHandler of(TypeDescription suppressedType) {\n                    return isNoExceptionHandler(suppressedType)\n                        ? NoOp.INSTANCE\n                        : new Suppressing(suppressedType);\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public SuppressionHandler.Bound bind(StackManipulation exceptionHandler) {\n                    return new Bound(suppressedType, exceptionHandler);\n                }\n\n                /**\n                 * An active, bound suppression handler.\n                 */\n                protected static class Bound implements SuppressionHandler.Bound {\n\n                    /**\n                     * The suppressed throwable type.\n                     */\n                    private final TypeDescription suppressedType;\n\n                    /**\n                     * The stack manipulation to apply within a suppression handler.\n                     */\n                    private final StackManipulation exceptionHandler;\n\n                    /**\n                     * A label indicating the start of the method.\n                     */\n                    private final Label startOfMethod;\n\n                    /**\n                     * A label indicating the end of the method.\n                     */\n                    private final Label endOfMethod;\n\n                    /**\n                     * Creates a new active, bound suppression handler.\n                     *\n                     * @param suppressedType   The suppressed throwable type.\n                     * @param exceptionHandler The stack manipulation to apply within a suppression handler.\n                     */\n                    protected Bound(TypeDescription suppressedType, StackManipulation exceptionHandler) {\n                        this.suppressedType = suppressedType;\n                        this.exceptionHandler = exceptionHandler;\n                        startOfMethod = new Label();\n                        endOfMethod = new Label();\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public void onPrepare(MethodVisitor methodVisitor) {\n                        methodVisitor.visitTryCatchBlock(startOfMethod, endOfMethod, endOfMethod, suppressedType.getInternalName());\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public void onStart(MethodVisitor methodVisitor) {\n                        methodVisitor.visitLabel(startOfMethod);\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public void onEnd(MethodVisitor methodVisitor,\n                                      Implementation.Context implementationContext,\n                                      MethodSizeHandler.ForAdvice methodSizeHandler,\n                                      StackMapFrameHandler.ForAdvice stackMapFrameHandler,\n                                      TypeDefinition returnType) {\n                        methodVisitor.visitLabel(endOfMethod);\n                        stackMapFrameHandler.injectExceptionFrame(methodVisitor);\n                        methodSizeHandler.requireStackSize(1 + exceptionHandler.apply(methodVisitor, implementationContext).getMaximalSize());\n                        if (returnType.represents(boolean.class)\n                            || returnType.represents(byte.class)\n                            || returnType.represents(short.class)\n                            || returnType.represents(char.class)\n                            || returnType.represents(int.class)) {\n                            methodVisitor.visitInsn(Opcodes.ICONST_0);\n                        } else if (returnType.represents(long.class)) {\n                            methodVisitor.visitInsn(Opcodes.LCONST_0);\n                        } else if (returnType.represents(float.class)) {\n                            methodVisitor.visitInsn(Opcodes.FCONST_0);\n                        } else if (returnType.represents(double.class)) {\n                            methodVisitor.visitInsn(Opcodes.DCONST_0);\n                        } else if (!returnType.represents(void.class)) {\n                            methodVisitor.visitInsn(Opcodes.ACONST_NULL);\n                        }\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public void onEndWithSkip(MethodVisitor methodVisitor,\n                                              Implementation.Context implementationContext,\n                                              MethodSizeHandler.ForAdvice methodSizeHandler,\n                                              StackMapFrameHandler.ForAdvice stackMapFrameHandler,\n                                              TypeDefinition returnType) {\n                        Label skipExceptionHandler = new Label();\n                        methodVisitor.visitJumpInsn(Opcodes.GOTO, skipExceptionHandler);\n                        onEnd(methodVisitor, implementationContext, methodSizeHandler, stackMapFrameHandler, returnType);\n                        methodVisitor.visitLabel(skipExceptionHandler);\n                        stackMapFrameHandler.injectReturnFrame(methodVisitor);\n                    }\n                }\n            }\n        }\n\n        /**\n         * A relocation handler is responsible for chaining the usual control flow of an instrumented method.\n         */\n        interface RelocationHandler {\n\n            /**\n             * Binds this relocation handler to a relocation dispatcher.\n             *\n             * @param instrumentedMethod The instrumented method.\n             * @param relocation         The relocation to apply.\n             * @return A bound relocation handler.\n             */\n            Bound bind(MethodDescription instrumentedMethod, Relocation relocation);\n\n            /**\n             * A re-locator is responsible for triggering a relocation if a relocation handler triggers a relocating condition.\n             */\n            interface Relocation {\n\n                /**\n                 * Applies this relocator.\n                 *\n                 * @param methodVisitor The method visitor to use.\n                 */\n                void apply(MethodVisitor methodVisitor);\n\n                /**\n                 * A relocation that unconditionally jumps to a given label.\n                 */\n                @HashCodeAndEqualsPlugin.Enhance\n                class ForLabel implements Relocation {\n\n                    /**\n                     * The label to jump to.\n                     */\n                    private final Label label;\n\n                    /**\n                     * Creates a new relocation for an unconditional jump to a given label.\n                     *\n                     * @param label The label to jump to.\n                     */\n                    public ForLabel(Label label) {\n                        this.label = label;\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public void apply(MethodVisitor methodVisitor) {\n                        methodVisitor.visitJumpInsn(Opcodes.GOTO, label);\n                    }\n                }\n            }\n\n            /**\n             * A bound {@link RelocationHandler}.\n             */\n            interface Bound {\n\n                /**\n                 * Indicates that this relocation handler does not require a minimal stack size.\n                 */\n                int NO_REQUIRED_SIZE = 0;\n\n                /**\n                 * Applies this relocation handler.\n                 *\n                 * @param methodVisitor The method visitor to use.\n                 * @param offset        The offset of the relevant value.\n                 * @return The minimal required stack size to apply this relocation handler.\n                 */\n                int apply(MethodVisitor methodVisitor, int offset);\n            }\n\n            /**\n             * A disabled relocation handler that does never trigger a relocation.\n             */\n            enum Disabled implements RelocationHandler, Bound {\n\n                /**\n                 * The singleton instance.\n                 */\n                INSTANCE;\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public Bound bind(MethodDescription instrumentedMethod, Relocation relocation) {\n                    return this;\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public int apply(MethodVisitor methodVisitor, int offset) {\n                    return NO_REQUIRED_SIZE;\n                }\n            }\n\n            /**\n             * A relocation handler that triggers a relocation for a default or non-default value.\n             */\n            enum ForValue implements RelocationHandler {\n\n                /**\n                 * A relocation handler for an {@code int} type or any compatible type.\n                 */\n                INTEGER(Opcodes.ILOAD, Opcodes.IFNE, Opcodes.IFEQ, 0) {\n                    @Override\n                    protected void convertValue(MethodVisitor methodVisitor) {\n                        /* do nothing */\n                    }\n                },\n\n                /**\n                 * A relocation handler for a {@code long} type.\n                 */\n                LONG(Opcodes.LLOAD, Opcodes.IFNE, Opcodes.IFEQ, 0) {\n                    @Override\n                    protected void convertValue(MethodVisitor methodVisitor) {\n                        methodVisitor.visitInsn(Opcodes.L2I);\n                    }\n                },\n\n                /**\n                 * A relocation handler for a {@code float} type.\n                 */\n                FLOAT(Opcodes.FLOAD, Opcodes.IFNE, Opcodes.IFEQ, 2) {\n                    @Override\n                    protected void convertValue(MethodVisitor methodVisitor) {\n                        methodVisitor.visitInsn(Opcodes.FCONST_0);\n                        methodVisitor.visitInsn(Opcodes.FCMPL);\n                    }\n                },\n\n                /**\n                 * A relocation handler for a {@code double} type.\n                 */\n                DOUBLE(Opcodes.DLOAD, Opcodes.IFNE, Opcodes.IFEQ, 4) {\n                    @Override\n                    protected void convertValue(MethodVisitor methodVisitor) {\n                        methodVisitor.visitInsn(Opcodes.DCONST_0);\n                        methodVisitor.visitInsn(Opcodes.DCMPL);\n                    }\n                },\n\n                /**\n                 * A relocation handler for a reference type.\n                 */\n                REFERENCE(Opcodes.ALOAD, Opcodes.IFNONNULL, Opcodes.IFNULL, 0) {\n                    @Override\n                    protected void convertValue(MethodVisitor methodVisitor) {\n                        /* do nothing */\n                    }\n                };\n\n                /**\n                 * An opcode for loading a value of the represented type from the local variable array.\n                 */\n                private final int load;\n\n                /**\n                 * The opcode to check for a non-default value.\n                 */\n                private final int defaultJump;\n\n                /**\n                 * The opcode to check for a default value.\n                 */\n                private final int nonDefaultJump;\n\n                /**\n                 * The minimal required stack size to apply this relocation handler.\n                 */\n                private final int requiredSize;\n\n                /**\n                 * Creates a new relocation handler for a type's default or non-default value.\n                 *\n                 * @param load           An opcode for loading a value of the represented type from the local variable array.\n                 * @param defaultJump    The opcode to check for a non-default value.\n                 * @param nonDefaultJump The opcode to check for a default value.\n                 * @param requiredSize   The minimal required stack size to apply this relocation handler.\n                 */\n                ForValue(int load, int defaultJump, int nonDefaultJump, int requiredSize) {\n                    this.load = load;\n                    this.defaultJump = defaultJump;\n                    this.nonDefaultJump = nonDefaultJump;\n                    this.requiredSize = requiredSize;\n                }\n\n                /**\n                 * Resolves a relocation handler for a given type.\n                 *\n                 * @param typeDefinition The type to be resolved for a relocation attempt.\n                 * @param inverted       {@code true} if the relocation should be applied for any non-default value of a type.\n                 * @return An appropriate relocation handler.\n                 */\n                protected static RelocationHandler of(TypeDefinition typeDefinition, boolean inverted) {\n                    ForValue skipDispatcher;\n                    if (typeDefinition.represents(long.class)) {\n                        skipDispatcher = LONG;\n                    } else if (typeDefinition.represents(float.class)) {\n                        skipDispatcher = FLOAT;\n                    } else if (typeDefinition.represents(double.class)) {\n                        skipDispatcher = DOUBLE;\n                    } else if (typeDefinition.represents(void.class)) {\n                        throw new IllegalStateException(\"Cannot skip on default value for void return type\");\n                    } else if (typeDefinition.isPrimitive()) { // anyOf(byte, short, char, int)\n                        skipDispatcher = INTEGER;\n                    } else {\n                        skipDispatcher = REFERENCE;\n                    }\n                    return inverted\n                        ? skipDispatcher.new Inverted()\n                        : skipDispatcher;\n                }\n\n                /**\n                 * Applies a value conversion prior to a applying a conditional jump.\n                 *\n                 * @param methodVisitor The method visitor to use.\n                 */\n                protected abstract void convertValue(MethodVisitor methodVisitor);\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public RelocationHandler.Bound bind(MethodDescription instrumentedMethod, Relocation relocation) {\n                    return new Bound(instrumentedMethod, relocation, false);\n                }\n\n                /**\n                 * An inverted version of the outer relocation handler.\n                 */\n                @HashCodeAndEqualsPlugin.Enhance(includeSyntheticFields = true)\n                protected class Inverted implements RelocationHandler {\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public Bound bind(MethodDescription instrumentedMethod, Relocation relocation) {\n                        return new ForValue.Bound(instrumentedMethod, relocation, true);\n                    }\n                }\n\n                /**\n                 * A bound relocation handler for {@link ForValue}.\n                 */\n                @HashCodeAndEqualsPlugin.Enhance(includeSyntheticFields = true)\n                protected class Bound implements RelocationHandler.Bound {\n\n                    /**\n                     * The instrumented method.\n                     */\n                    private final MethodDescription instrumentedMethod;\n\n                    /**\n                     * The relocation to apply.\n                     */\n                    private final Relocation relocation;\n\n                    /**\n                     * {@code true} if the relocation should be applied for any non-default value of a type.\n                     */\n                    private final boolean inverted;\n\n                    /**\n                     * Creates a new bound relocation handler.\n                     *\n                     * @param instrumentedMethod The instrumented method.\n                     * @param relocation         The relocation to apply.\n                     * @param inverted           {@code true} if the relocation should be applied for any non-default value of a type.\n                     */\n                    protected Bound(MethodDescription instrumentedMethod, Relocation relocation, boolean inverted) {\n                        this.instrumentedMethod = instrumentedMethod;\n                        this.relocation = relocation;\n                        this.inverted = inverted;\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public int apply(MethodVisitor methodVisitor, int offset) {\n                        if (instrumentedMethod.isConstructor()) {\n                            throw new IllegalStateException(\"Cannot skip code execution from constructor: \" + instrumentedMethod);\n                        }\n                        methodVisitor.visitVarInsn(load, offset);\n                        convertValue(methodVisitor);\n                        Label noSkip = new Label();\n                        methodVisitor.visitJumpInsn(inverted\n                            ? nonDefaultJump\n                            : defaultJump, noSkip);\n                        relocation.apply(methodVisitor);\n                        methodVisitor.visitLabel(noSkip);\n                        return requiredSize;\n                    }\n                }\n            }\n\n            /**\n             * A relocation handler that is triggered if the checked value is an instance of a given type.\n             */\n            @HashCodeAndEqualsPlugin.Enhance\n            class ForType implements RelocationHandler {\n\n                /**\n                 * The type that triggers a relocation.\n                 */\n                private final TypeDescription typeDescription;\n\n                /**\n                 * Creates a new relocation handler that triggers a relocation if a value is an instance of a given type.\n                 *\n                 * @param typeDescription The type that triggers a relocation.\n                 */\n                protected ForType(TypeDescription typeDescription) {\n                    this.typeDescription = typeDescription;\n                }\n\n                /**\n                 * Resolves a relocation handler that is triggered if the checked instance is of a given type.\n                 *\n                 * @param typeDescription The type that triggers a relocation.\n                 * @param checkedType     The type that is carrying the checked value.\n                 * @return An appropriate relocation handler.\n                 */\n                protected static RelocationHandler of(TypeDescription typeDescription, TypeDefinition checkedType) {\n                    if (typeDescription.represents(void.class)) {\n                        return Disabled.INSTANCE;\n                    } else if (typeDescription.represents(OnDefaultValue.class)) {\n                        return ForValue.of(checkedType, false);\n                    } else if (typeDescription.represents(OnNonDefaultValue.class)) {\n                        return ForValue.of(checkedType, true);\n                    } else if (typeDescription.isPrimitive() || checkedType.isPrimitive()) {\n                        throw new IllegalStateException(\"Cannot skip method by instance type for primitive return type \" + checkedType);\n                    } else {\n                        return new ForType(typeDescription);\n                    }\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public RelocationHandler.Bound bind(MethodDescription instrumentedMethod, Relocation relocation) {\n                    return new Bound(instrumentedMethod, relocation);\n                }\n\n                /**\n                 * A bound relocation handler for {@link ForType}.\n                 */\n                @HashCodeAndEqualsPlugin.Enhance(includeSyntheticFields = true)\n                protected class Bound implements RelocationHandler.Bound {\n\n                    /**\n                     * The instrumented method.\n                     */\n                    private final MethodDescription instrumentedMethod;\n\n                    /**\n                     * The relocation to use.\n                     */\n                    private final Relocation relocation;\n\n                    /**\n                     * Creates a new bound relocation handler.\n                     *\n                     * @param instrumentedMethod The instrumented method.\n                     * @param relocation         The relocation to apply.\n                     */\n                    protected Bound(MethodDescription instrumentedMethod, Relocation relocation) {\n                        this.instrumentedMethod = instrumentedMethod;\n                        this.relocation = relocation;\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public int apply(MethodVisitor methodVisitor, int offset) {\n                        if (instrumentedMethod.isConstructor()) {\n                            throw new IllegalStateException(\"Cannot skip code execution from constructor: \" + instrumentedMethod);\n                        }\n                        methodVisitor.visitVarInsn(Opcodes.ALOAD, offset);\n                        methodVisitor.visitTypeInsn(Opcodes.INSTANCEOF, typeDescription.getInternalName());\n                        Label noSkip = new Label();\n                        methodVisitor.visitJumpInsn(Opcodes.IFEQ, noSkip);\n                        relocation.apply(methodVisitor);\n                        methodVisitor.visitLabel(noSkip);\n                        return NO_REQUIRED_SIZE;\n                    }\n                }\n            }\n        }\n\n        /**\n         * Represents a resolved dispatcher.\n         */\n        interface Resolved extends Dispatcher {\n\n            /**\n             * Returns the named types defined by this advice.\n             *\n             * @return The named types defined by this advice.\n             */\n            Map<String, TypeDefinition> getNamedTypes();\n\n            /**\n             * Binds this dispatcher for resolution to a specific method.\n             *\n             * @param instrumentedType      The instrumented type.\n             * @param instrumentedMethod    The instrumented method.\n             * @param methodVisitor         The method visitor for writing the instrumented method.\n             * @param implementationContext The implementation context to use.\n             * @param assigner              The assigner to use.\n             * @param argumentHandler       A handler for accessing values on the local variable array.\n             * @param methodSizeHandler     A handler for computing the method size requirements.\n             * @param stackMapFrameHandler  A handler for translating and injecting stack map frames.\n             * @param exceptionHandler      The stack manipulation to apply within a suppression handler.\n             * @param relocation            A relocation to use with a relocation handler.\n             * @return A dispatcher that is bound to the instrumented method.\n             */\n            Bound bind(TypeDescription instrumentedType,\n                       MethodDescription instrumentedMethod,\n                       MethodVisitor methodVisitor,\n                       Implementation.Context implementationContext,\n                       Assigner assigner,\n                       ArgumentHandler.ForInstrumentedMethod argumentHandler,\n                       MethodSizeHandler.ForInstrumentedMethod methodSizeHandler,\n                       StackMapFrameHandler.ForInstrumentedMethod stackMapFrameHandler,\n                       StackManipulation exceptionHandler,\n                       RelocationHandler.Relocation relocation);\n\n\n            Map<Integer, OffsetMapping> getOffsetMapping();\n\n            /**\n             * Represents a resolved dispatcher for entering a method.\n             */\n            interface ForMethodEnter extends Resolved {\n\n                /**\n                 * Returns {@code true} if the first discovered line number information should be prepended to the advice code.\n                 *\n                 * @return {@code true} if the first discovered line number information should be prepended to the advice code.\n                 */\n                boolean isPrependLineNumber();\n\n                /**\n                 * Returns the actual advice type, even if it is not required post advice processing.\n                 *\n                 * @return The actual advice type, even if it is not required post advice processing.\n                 */\n                TypeDefinition getActualAdviceType();\n            }\n\n            /**\n             * Represents a resolved dispatcher for exiting a method.\n             */\n            interface ForMethodExit extends Resolved {\n\n                /**\n                 * Returns the type of throwable for which this exit advice is supposed to be invoked.\n                 *\n                 * @return The {@link Throwable} type for which to invoke this exit advice or a description of {@link NoExceptionHandler}\n                 * if this exit advice does not expect to be invoked upon any throwable.\n                 */\n                TypeDescription getThrowable();\n\n                /**\n                 * Returns a factory for creating an {@link ArgumentHandler}.\n                 *\n                 * @return A factory for creating an {@link ArgumentHandler}.\n                 */\n                ArgumentHandler.Factory getArgumentHandlerFactory();\n            }\n\n            /**\n             * An abstract base implementation of a {@link Resolved} dispatcher.\n             */\n            @HashCodeAndEqualsPlugin.Enhance\n            abstract class AbstractBase implements Resolved {\n\n                /**\n                 * The represented advice method.\n                 */\n                protected final MethodDescription.InDefinedShape adviceMethod;\n\n                /**\n                 * The post processor to apply.\n                 */\n                protected final PostProcessor postProcessor;\n\n                /**\n                 * A mapping from offset to a mapping for this offset with retained iteration order of the method's parameters.\n                 */\n                protected final Map<Integer, OffsetMapping> offsetMappings;\n\n                /**\n                 * The suppression handler to use.\n                 */\n                protected final SuppressionHandler suppressionHandler;\n\n                /**\n                 * The relocation handler to use.\n                 */\n                protected final RelocationHandler relocationHandler;\n\n                /**\n                 * Creates a new resolved version of a dispatcher.\n                 *\n                 * @param adviceMethod    The represented advice method.\n                 * @param postProcessor   The post processor to use.\n                 * @param factories       A list of factories to resolve for the parameters of the advice method.\n                 * @param throwableType   The type to handle by a suppression handler or {@link NoExceptionHandler} to not handle any exceptions.\n                 * @param relocatableType The type to trigger a relocation of the method's control flow or {@code void} if no relocation should be executed.\n                 * @param adviceType      The applied advice type.\n                 */\n                protected AbstractBase(MethodDescription.InDefinedShape adviceMethod,\n                                       PostProcessor postProcessor,\n                                       List<? extends OffsetMapping.Factory<?>> factories,\n                                       TypeDescription throwableType,\n                                       TypeDescription relocatableType,\n                                       OffsetMapping.Factory.AdviceType adviceType) {\n                    this.adviceMethod = adviceMethod;\n                    this.postProcessor = postProcessor;\n                    Map<TypeDescription, OffsetMapping.Factory<?>> offsetMappings = new HashMap<>();\n                    for (OffsetMapping.Factory<?> factory : factories) {\n                        offsetMappings.put(TypeDescription.ForLoadedType.of(factory.getAnnotationType()), factory);\n                    }\n                    this.offsetMappings = new LinkedHashMap<>();\n                    for (ParameterDescription.InDefinedShape parameterDescription : adviceMethod.getParameters()) {\n                        OffsetMapping offsetMapping = null;\n                        for (AnnotationDescription annotationDescription : parameterDescription.getDeclaredAnnotations()) {\n                            OffsetMapping.Factory<?> factory = offsetMappings.get(annotationDescription.getAnnotationType());\n                            if (factory != null) {\n                                @SuppressWarnings(\"unchecked, rawtypes\")\n                                OffsetMapping current = factory.make(parameterDescription,\n                                    (AnnotationDescription.Loadable) annotationDescription.prepare(factory.getAnnotationType()),\n                                    adviceType);\n                                if (offsetMapping == null) {\n                                    offsetMapping = current;\n                                } else {\n                                    throw new IllegalStateException(parameterDescription + \" is bound to both \" + current + \" and \" + offsetMapping);\n                                }\n                            }\n                        }\n                        this.offsetMappings.put(parameterDescription.getOffset(), offsetMapping == null\n                            ? new OffsetMapping.ForArgument.Unresolved(parameterDescription)\n                            : offsetMapping);\n                    }\n                    suppressionHandler = SuppressionHandler.Suppressing.of(throwableType);\n                    relocationHandler = RelocationHandler.ForType.of(relocatableType, adviceMethod.getReturnType());\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public boolean isAlive() {\n                    return true;\n                }\n\n                @Override\n                public Map<Integer, OffsetMapping> getOffsetMapping() {\n                    return this.offsetMappings;\n                }\n            }\n        }\n\n        /**\n         * A bound resolution of an advice method.\n         */\n        interface Bound {\n\n            /**\n             * Prepares the advice method's exception handlers.\n             */\n            void prepare();\n\n            /**\n             * Initialized the advice's methods local variables.\n             */\n            void initialize();\n\n            /**\n             * Applies this dispatcher.\n             */\n            void apply();\n        }\n\n        /**\n         * An implementation for inactive devise that does not write any byte code.\n         */\n        enum Inactive implements Dispatcher.Unresolved, Resolved.ForMethodEnter, Resolved.ForMethodExit, Bound {\n\n            /**\n             * The singleton instance.\n             */\n            INSTANCE;\n\n            /**\n             * {@inheritDoc}\n             */\n            public boolean isAlive() {\n                return false;\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public boolean isBinary() {\n                return false;\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public TypeDescription getAdviceType() {\n                return TypeDescription.VOID;\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public boolean isPrependLineNumber() {\n                return false;\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public TypeDefinition getActualAdviceType() {\n                return TypeDescription.VOID;\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public Map<String, TypeDefinition> getNamedTypes() {\n                return Collections.emptyMap();\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public TypeDescription getThrowable() {\n                return NoExceptionHandler.DESCRIPTION;\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public ArgumentHandler.Factory getArgumentHandlerFactory() {\n                return ArgumentHandler.Factory.SIMPLE;\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public Resolved.ForMethodEnter asMethodEnter(List<? extends OffsetMapping.Factory<?>> userFactories,\n                                                         ClassReader classReader,\n                                                         Unresolved methodExit,\n                                                         PostProcessor.Factory postProcessorFactory) {\n                return this;\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public Resolved.ForMethodExit asMethodExit(List<? extends OffsetMapping.Factory<?>> userFactories,\n                                                       ClassReader classReader,\n                                                       Unresolved methodEnter,\n                                                       PostProcessor.Factory postProcessorFactory) {\n                return this;\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public void prepare() {\n                /* do nothing */\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public void initialize() {\n                /* do nothing */\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public void apply() {\n                /* do nothing */\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public Bound bind(TypeDescription instrumentedType,\n                              MethodDescription instrumentedMethod,\n                              MethodVisitor methodVisitor,\n                              Implementation.Context implementationContext,\n                              Assigner assigner,\n                              ArgumentHandler.ForInstrumentedMethod argumentHandler,\n                              MethodSizeHandler.ForInstrumentedMethod methodSizeHandler,\n                              StackMapFrameHandler.ForInstrumentedMethod stackMapFrameHandler,\n                              StackManipulation exceptionHandler,\n                              RelocationHandler.Relocation relocation) {\n                return this;\n            }\n\n            @Override\n            public Map<Integer, OffsetMapping> getOffsetMapping() {\n                return null;\n            }\n        }\n\n        /**\n         * A dispatcher for an advice method that is being inlined into the instrumented method.\n         */\n        @HashCodeAndEqualsPlugin.Enhance\n        class Inlining implements Unresolved {\n\n            /**\n             * The advice method.\n             */\n            protected final MethodDescription.InDefinedShape adviceMethod;\n\n            /**\n             * A mapping of all available local variables by their name to their type.\n             */\n            private final Map<String, TypeDefinition> namedTypes;\n\n            /**\n             * Creates a dispatcher for inlined advice method.\n             *\n             * @param adviceMethod The advice method.\n             */\n            protected Inlining(MethodDescription.InDefinedShape adviceMethod) {\n                this.adviceMethod = adviceMethod;\n                namedTypes = new HashMap<>();\n                for (ParameterDescription parameterDescription\n                    : adviceMethod.getParameters().filter(isAnnotatedWith(Local.class))) {\n                    String name = parameterDescription.getDeclaredAnnotations()\n                        .ofType(Local.class).getValue(OffsetMapping.ForLocalValue.Factory.LOCAL_VALUE)\n                        .resolve(String.class);\n                    TypeDefinition previous = namedTypes.put(name, parameterDescription.getType());\n                    if (previous != null && !previous.equals(parameterDescription.getType())) {\n                        throw new IllegalStateException(\"Local variable for \" + name + \" is defined with inconsistent types\");\n                    }\n                }\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public boolean isAlive() {\n                return true;\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public boolean isBinary() {\n                return true;\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public TypeDescription getAdviceType() {\n                return adviceMethod.getReturnType().asErasure();\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public Map<String, TypeDefinition> getNamedTypes() {\n                return namedTypes;\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public Dispatcher.Resolved.ForMethodEnter asMethodEnter(List<? extends OffsetMapping.Factory<?>> userFactories,\n                                                                    ClassReader classReader,\n                                                                    Unresolved methodExit,\n                                                                    PostProcessor.Factory postProcessorFactory) {\n                return Resolved.ForMethodEnter.of(adviceMethod,\n                    postProcessorFactory.make(adviceMethod, false),\n                    namedTypes,\n                    userFactories,\n                    methodExit.getAdviceType(),\n                    classReader,\n                    methodExit.isAlive());\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public Dispatcher.Resolved.ForMethodExit asMethodExit(List<? extends OffsetMapping.Factory<?>> userFactories,\n                                                                  ClassReader classReader,\n                                                                  Unresolved methodEnter,\n                                                                  PostProcessor.Factory postProcessorFactory) {\n                Map<String, TypeDefinition> namedTypes = new HashMap<>(methodEnter.getNamedTypes()), uninitializedNamedTypes = new HashMap<>();\n                for (Map.Entry<String, TypeDefinition> entry : this.namedTypes.entrySet()) {\n                    TypeDefinition typeDefinition = namedTypes.get(entry.getKey()), uninitializedTypeDefinition = uninitializedNamedTypes.get(entry.getKey());\n                    if (typeDefinition == null && uninitializedTypeDefinition == null) {\n                        namedTypes.put(entry.getKey(), entry.getValue());\n                        uninitializedNamedTypes.put(entry.getKey(), entry.getValue());\n                    } else if (!(typeDefinition == null ? uninitializedTypeDefinition : typeDefinition).equals(entry.getValue())) {\n                        throw new IllegalStateException(\"Local variable for \" + entry.getKey() + \" is defined with inconsistent types\");\n                    }\n                }\n                return Resolved.ForMethodExit.of(adviceMethod,\n                    postProcessorFactory.make(adviceMethod, true),\n                    namedTypes, uninitializedNamedTypes, userFactories,\n                    classReader, methodEnter.getAdviceType());\n            }\n\n            public Dispatcher.Resolved.ForMethodExit asMethodExitNonThrowable(\n                List<? extends OffsetMapping.Factory<?>> userFactories,\n                ClassReader classReader,\n                Unresolved methodEnter,\n                PostProcessor.Factory postProcessorFactory) {\n                Map<String, TypeDefinition> namedTypes = new HashMap<>(methodEnter.getNamedTypes()), uninitializedNamedTypes = new HashMap<>();\n                for (Map.Entry<String, TypeDefinition> entry : this.namedTypes.entrySet()) {\n                    TypeDefinition typeDefinition = namedTypes.get(entry.getKey()), uninitializedTypeDefinition = uninitializedNamedTypes.get(entry.getKey());\n                    if (typeDefinition == null && uninitializedTypeDefinition == null) {\n                        namedTypes.put(entry.getKey(), entry.getValue());\n                        uninitializedNamedTypes.put(entry.getKey(), entry.getValue());\n                    } else if (!(typeDefinition == null ? uninitializedTypeDefinition : typeDefinition).equals(entry.getValue())) {\n                        throw new IllegalStateException(\"Local variable for \" + entry.getKey() + \" is defined with inconsistent types\");\n                    }\n                }\n                return Resolved.ForMethodExit.ofNonThrowable(adviceMethod,\n                    postProcessorFactory.make(adviceMethod, true),\n                    namedTypes, uninitializedNamedTypes, userFactories,\n                    classReader, methodEnter.getAdviceType());\n            }\n\n            /**\n             * A resolved version of a dispatcher.\n             */\n            protected abstract static class Resolved extends Dispatcher.Resolved.AbstractBase {\n\n                /**\n                 * A class reader to query for the class file of the advice method.\n                 */\n                protected final ClassReader classReader;\n\n                /**\n                 * Creates a new resolved version of a dispatcher.\n                 *\n                 * @param adviceMethod    The represented advice method.\n                 * @param postProcessor   The post processor to apply.\n                 * @param factories       A list of factories to resolve for the parameters of the advice method.\n                 * @param throwableType   The type to handle by a suppression handler or {@link NoExceptionHandler} to not handle any exceptions.\n                 * @param relocatableType The type to trigger a relocation of the method's control flow or {@code void} if no relocation should be executed.\n                 * @param classReader     A class reader to query for the class file of the advice method.\n                 */\n                protected Resolved(MethodDescription.InDefinedShape adviceMethod,\n                                   PostProcessor postProcessor,\n                                   List<? extends OffsetMapping.Factory<?>> factories,\n                                   TypeDescription throwableType,\n                                   TypeDescription relocatableType,\n                                   ClassReader classReader) {\n                    super(adviceMethod, postProcessor, factories, throwableType, relocatableType, OffsetMapping.Factory.AdviceType.INLINING);\n                    this.classReader = classReader;\n                }\n\n                /**\n                 * Resolves the initialization types of this advice method.\n                 *\n                 * @param argumentHandler The argument handler to use for resolving the initialization.\n                 * @return A mapping of parameter offsets to the type to initialize.\n                 */\n                protected abstract Map<Integer, TypeDefinition> resolveInitializationTypes(ArgumentHandler argumentHandler);\n\n                /**\n                 * Applies a resolution for a given instrumented method.\n                 *\n                 * @param methodVisitor         A method visitor for writing byte code to the instrumented method.\n                 * @param implementationContext The implementation context to use.\n                 * @param assigner              The assigner to use.\n                 * @param argumentHandler       A handler for accessing values on the local variable array.\n                 * @param methodSizeHandler     A handler for computing the method size requirements.\n                 * @param stackMapFrameHandler  A handler for translating and injecting stack map frames.\n                 * @param instrumentedType      A description of the instrumented type.\n                 * @param instrumentedMethod    A description of the instrumented method.\n                 * @param suppressionHandler    A bound suppression handler that is used for suppressing exceptions of this advice method.\n                 * @param relocationHandler     A bound relocation handler that is responsible for considering a non-standard control flow.\n                 * @param exceptionHandler      The exception handler that is resolved for the instrumented method.\n                 * @return A method visitor for visiting the advice method's byte code.\n                 */\n                protected abstract MethodVisitor apply(MethodVisitor methodVisitor,\n                                                       Implementation.Context implementationContext,\n                                                       Assigner assigner,\n                                                       ArgumentHandler.ForInstrumentedMethod argumentHandler,\n                                                       MethodSizeHandler.ForInstrumentedMethod methodSizeHandler,\n                                                       StackMapFrameHandler.ForInstrumentedMethod stackMapFrameHandler,\n                                                       TypeDescription instrumentedType,\n                                                       MethodDescription instrumentedMethod,\n                                                       SuppressionHandler.Bound suppressionHandler,\n                                                       RelocationHandler.Bound relocationHandler,\n                                                       StackManipulation exceptionHandler);\n\n                /**\n                 * A bound advice method that copies the code by first extracting the exception table and later appending the\n                 * code of the method without copying any meta data.\n                 */\n                protected class AdviceMethodInliner extends ClassVisitor implements Bound {\n\n                    /**\n                     * A description of the instrumented type.\n                     */\n                    protected final TypeDescription instrumentedType;\n\n                    /**\n                     * The instrumented method.\n                     */\n                    protected final MethodDescription instrumentedMethod;\n\n                    /**\n                     * The method visitor for writing the instrumented method.\n                     */\n                    protected final MethodVisitor methodVisitor;\n\n                    /**\n                     * The implementation context to use.\n                     */\n                    protected final Implementation.Context implementationContext;\n\n                    /**\n                     * The assigner to use.\n                     */\n                    protected final Assigner assigner;\n\n                    /**\n                     * A handler for accessing values on the local variable array.\n                     */\n                    protected final ArgumentHandler.ForInstrumentedMethod argumentHandler;\n\n                    /**\n                     * A handler for computing the method size requirements.\n                     */\n                    protected final MethodSizeHandler.ForInstrumentedMethod methodSizeHandler;\n\n                    /**\n                     * A handler for translating and injecting stack map frames.\n                     */\n                    protected final StackMapFrameHandler.ForInstrumentedMethod stackMapFrameHandler;\n\n                    /**\n                     * A bound suppression handler that is used for suppressing exceptions of this advice method.\n                     */\n                    protected final SuppressionHandler.Bound suppressionHandler;\n\n                    /**\n                     * A bound relocation handler that is responsible for considering a non-standard control flow.\n                     */\n                    protected final RelocationHandler.Bound relocationHandler;\n\n                    /**\n                     * The exception handler that is resolved for the instrumented method.\n                     */\n                    protected final StackManipulation exceptionHandler;\n\n                    /**\n                     * A class reader for parsing the class file containing the represented advice method.\n                     */\n                    protected final ClassReader classReader;\n\n                    /**\n                     * The labels that were found during parsing the method's exception handler in the order of their discovery.\n                     */\n                    protected final List<Label> labels;\n\n                    /**\n                     * Creates a new advice method inliner.\n                     *\n                     * @param instrumentedType      A description of the instrumented type.\n                     * @param instrumentedMethod    The instrumented method.\n                     * @param methodVisitor         The method visitor for writing the instrumented method.\n                     * @param implementationContext The implementation context to use.\n                     * @param assigner              The assigner to use.\n                     * @param argumentHandler       A handler for accessing values on the local variable array.\n                     * @param methodSizeHandler     A handler for computing the method size requirements.\n                     * @param stackMapFrameHandler  A handler for translating and injecting stack map frames.\n                     * @param suppressionHandler    A bound suppression handler that is used for suppressing exceptions of this advice method.\n                     * @param relocationHandler     A bound relocation handler that is responsible for considering a non-standard control flow.\n                     * @param exceptionHandler      The exception handler that is resolved for the instrumented method.\n                     * @param classReader           A class reader for parsing the class file containing the represented advice method.\n                     */\n                    protected AdviceMethodInliner(TypeDescription instrumentedType,\n                                                  MethodDescription instrumentedMethod,\n                                                  MethodVisitor methodVisitor,\n                                                  Implementation.Context implementationContext,\n                                                  Assigner assigner,\n                                                  ArgumentHandler.ForInstrumentedMethod argumentHandler,\n                                                  MethodSizeHandler.ForInstrumentedMethod methodSizeHandler,\n                                                  StackMapFrameHandler.ForInstrumentedMethod stackMapFrameHandler,\n                                                  SuppressionHandler.Bound suppressionHandler,\n                                                  RelocationHandler.Bound relocationHandler,\n                                                  StackManipulation exceptionHandler,\n                                                  ClassReader classReader) {\n                        super(OpenedClassReader.ASM_API);\n                        this.instrumentedType = instrumentedType;\n                        this.instrumentedMethod = instrumentedMethod;\n                        this.methodVisitor = methodVisitor;\n                        this.implementationContext = implementationContext;\n                        this.assigner = assigner;\n                        this.argumentHandler = argumentHandler;\n                        this.methodSizeHandler = methodSizeHandler;\n                        this.stackMapFrameHandler = stackMapFrameHandler;\n                        this.suppressionHandler = suppressionHandler;\n                        this.relocationHandler = relocationHandler;\n                        this.exceptionHandler = exceptionHandler;\n                        this.classReader = classReader;\n                        labels = new ArrayList<>();\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public void prepare() {\n                        classReader.accept(new ExceptionTableExtractor(), ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG);\n                        suppressionHandler.onPrepare(methodVisitor);\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public void initialize() {\n                        for (Map.Entry<Integer, TypeDefinition> typeDefinition : resolveInitializationTypes(argumentHandler).entrySet()) {\n                            if (typeDefinition.getValue().represents(boolean.class)\n                                || typeDefinition.getValue().represents(byte.class)\n                                || typeDefinition.getValue().represents(short.class)\n                                || typeDefinition.getValue().represents(char.class)\n                                || typeDefinition.getValue().represents(int.class)) {\n                                methodVisitor.visitInsn(Opcodes.ICONST_0);\n                                methodVisitor.visitVarInsn(Opcodes.ISTORE, typeDefinition.getKey());\n                            } else if (typeDefinition.getValue().represents(long.class)) {\n                                methodVisitor.visitInsn(Opcodes.LCONST_0);\n                                methodVisitor.visitVarInsn(Opcodes.LSTORE, typeDefinition.getKey());\n                            } else if (typeDefinition.getValue().represents(float.class)) {\n                                methodVisitor.visitInsn(Opcodes.FCONST_0);\n                                methodVisitor.visitVarInsn(Opcodes.FSTORE, typeDefinition.getKey());\n                            } else if (typeDefinition.getValue().represents(double.class)) {\n                                methodVisitor.visitInsn(Opcodes.DCONST_0);\n                                methodVisitor.visitVarInsn(Opcodes.DSTORE, typeDefinition.getKey());\n                            } else {\n                                methodVisitor.visitInsn(Opcodes.ACONST_NULL);\n                                methodVisitor.visitVarInsn(Opcodes.ASTORE, typeDefinition.getKey());\n                            }\n                            methodSizeHandler.requireStackSize(typeDefinition.getValue().getStackSize().getSize());\n                        }\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public void apply() {\n                        classReader.accept(this, ClassReader.SKIP_DEBUG | stackMapFrameHandler.getReaderHint());\n                    }\n\n                    @Override\n                    public MethodVisitor visitMethod(int modifiers, String internalName, String descriptor, String signature, String[] exception) {\n                        return adviceMethod.getInternalName().equals(internalName) && adviceMethod.getDescriptor().equals(descriptor)\n                            ? new ExceptionTableSubstitutor(Inlining.Resolved.this.apply(methodVisitor,\n                            implementationContext,\n                            assigner,\n                            argumentHandler,\n                            methodSizeHandler,\n                            stackMapFrameHandler,\n                            instrumentedType,\n                            instrumentedMethod,\n                            suppressionHandler,\n                            relocationHandler,\n                            exceptionHandler)) : IGNORE_METHOD;\n                    }\n\n                    /**\n                     * A class visitor that extracts the exception tables of the advice method.\n                     */\n                    protected class ExceptionTableExtractor extends ClassVisitor {\n\n                        /**\n                         * Creates a new exception table extractor.\n                         */\n                        protected ExceptionTableExtractor() {\n                            super(OpenedClassReader.ASM_API);\n                        }\n\n                        @Override\n                        public MethodVisitor visitMethod(int modifiers, String internalName, String descriptor, String signature, String[] exception) {\n                            return adviceMethod.getInternalName().equals(internalName) && adviceMethod.getDescriptor().equals(descriptor)\n                                ? new ExceptionTableCollector(methodVisitor)\n                                : IGNORE_METHOD;\n                        }\n                    }\n\n                    /**\n                     * A visitor that only writes try-catch-finally blocks to the supplied method visitor. All labels of these tables are collected\n                     * for substitution when revisiting the reminder of the method.\n                     */\n                    protected class ExceptionTableCollector extends MethodVisitor {\n\n                        /**\n                         * The method visitor for which the try-catch-finally blocks should be written.\n                         */\n                        private final MethodVisitor methodVisitor;\n\n                        /**\n                         * Creates a new exception table collector.\n                         *\n                         * @param methodVisitor The method visitor for which the try-catch-finally blocks should be written.\n                         */\n                        protected ExceptionTableCollector(MethodVisitor methodVisitor) {\n                            super(OpenedClassReader.ASM_API);\n                            this.methodVisitor = methodVisitor;\n                        }\n\n                        @Override\n                        public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {\n                            methodVisitor.visitTryCatchBlock(start, end, handler, type);\n                            labels.addAll(Arrays.asList(start, end, handler));\n                        }\n\n                        @Override\n                        public AnnotationVisitor visitTryCatchAnnotation(int typeReference, TypePath typePath, String descriptor, boolean visible) {\n                            return methodVisitor.visitTryCatchAnnotation(typeReference, typePath, descriptor, visible);\n                        }\n                    }\n\n                    /**\n                     * A label substitutor allows to visit an advice method a second time after the exception handlers were already written.\n                     * Doing so, this visitor substitutes all labels that were already created during the first visit to keep the mapping\n                     * consistent. It is not required to resolve labels for non-code instructions as meta information is not propagated to\n                     * the target method visitor for advice code.\n                     */\n                    protected class ExceptionTableSubstitutor extends MethodVisitor {\n\n                        /**\n                         * A map containing resolved substitutions.\n                         */\n                        private final Map<Label, Label> substitutions;\n\n                        /**\n                         * The current index of the visited labels that are used for try-catch-finally blocks.\n                         */\n                        private int index;\n\n                        /**\n                         * Creates a label substitutor.\n                         *\n                         * @param methodVisitor The method visitor for which to substitute labels.\n                         */\n                        protected ExceptionTableSubstitutor(MethodVisitor methodVisitor) {\n                            super(OpenedClassReader.ASM_API, methodVisitor);\n                            substitutions = new IdentityHashMap<>();\n                        }\n\n                        @Override\n                        public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {\n                            substitutions.put(start, labels.get(index++));\n                            substitutions.put(end, labels.get(index++));\n                            Label actualHandler = labels.get(index++);\n                            substitutions.put(handler, actualHandler);\n                            ((CodeTranslationVisitor) mv).propagateHandler(actualHandler);\n                        }\n\n                        @Override\n                        public AnnotationVisitor visitTryCatchAnnotation(int typeReference, TypePath typePath, String descriptor, boolean visible) {\n                            return IGNORE_ANNOTATION;\n                        }\n\n                        @Override\n                        public void visitLabel(Label label) {\n                            super.visitLabel(resolve(label));\n                        }\n\n                        @Override\n                        public void visitJumpInsn(int opcode, Label label) {\n                            super.visitJumpInsn(opcode, resolve(label));\n                        }\n\n                        @Override\n                        public void visitTableSwitchInsn(int minimum, int maximum, Label defaultOption, Label... label) {\n                            super.visitTableSwitchInsn(minimum, maximum, defaultOption, resolve(label));\n                        }\n\n                        @Override\n                        public void visitLookupSwitchInsn(Label defaultOption, int[] keys, Label[] label) {\n                            super.visitLookupSwitchInsn(resolve(defaultOption), keys, resolve(label));\n                        }\n\n                        /**\n                         * Resolves an array of labels.\n                         *\n                         * @param label The labels to resolved.\n                         * @return An array containing the resolved arrays.\n                         */\n                        private Label[] resolve(Label[] label) {\n                            Label[] resolved = new Label[label.length];\n                            int index = 0;\n                            for (Label aLabel : label) {\n                                resolved[index++] = resolve(aLabel);\n                            }\n                            return resolved;\n                        }\n\n                        /**\n                         * Resolves a single label if mapped or returns the original label.\n                         *\n                         * @param label The label to resolve.\n                         * @return The resolved label.\n                         */\n                        private Label resolve(Label label) {\n                            Label substitution = substitutions.get(label);\n                            return substitution == null\n                                ? label\n                                : substitution;\n                        }\n                    }\n                }\n\n                /**\n                 * A resolved dispatcher for implementing method enter advice.\n                 */\n                @HashCodeAndEqualsPlugin.Enhance\n                protected abstract static class ForMethodEnter extends Inlining.Resolved implements Dispatcher.Resolved.ForMethodEnter {\n\n                    /**\n                     * A mapping of all available local variables by their name to their type.\n                     */\n                    private final Map<String, TypeDefinition> namedTypes;\n\n                    /**\n                     * {@code true} if the first discovered line number information should be prepended to the advice code.\n                     */\n                    private final boolean prependLineNumber;\n\n                    /**\n                     * Creates a new resolved dispatcher for implementing method enter advice.\n                     *\n                     * @param adviceMethod  The represented advice method.\n                     * @param postProcessor The post processor to apply.\n                     * @param namedTypes    A mapping of all available local variables by their name to their type.\n                     * @param userFactories A list of user-defined factories for offset mappings.\n                     * @param exitType      The exit type or {@code void} if no exit type is defined.\n                     * @param classReader   A class reader to query for the class file of the advice method.\n                     */\n                    protected ForMethodEnter(MethodDescription.InDefinedShape adviceMethod,\n                                             PostProcessor postProcessor,\n                                             Map<String, TypeDefinition> namedTypes,\n                                             List<? extends OffsetMapping.Factory<?>> userFactories,\n                                             TypeDefinition exitType,\n                                             ClassReader classReader) {\n                        super(adviceMethod,\n                            postProcessor,\n                            CompoundList.of(Arrays.asList(OffsetMapping.ForArgument.Unresolved.Factory.INSTANCE,\n                                OffsetMapping.ForAllArguments.Factory.INSTANCE,\n                                OffsetMapping.ForThisReference.Factory.INSTANCE,\n                                OffsetMapping.ForField.Unresolved.Factory.INSTANCE,\n                                OffsetMapping.ForOrigin.Factory.INSTANCE,\n                                OffsetMapping.ForUnusedValue.Factory.INSTANCE,\n                                OffsetMapping.ForStubValue.INSTANCE,\n                                OffsetMapping.ForThrowable.Factory.INSTANCE,\n                                OffsetMapping.ForExitValue.Factory.of(exitType),\n                                new OffsetMapping.ForLocalValue.Factory(namedTypes),\n                                new OffsetMapping.Factory.Illegal<>(Thrown.class),\n                                new OffsetMapping.Factory.Illegal<>(Enter.class),\n                                new OffsetMapping.Factory.Illegal<>(Return.class)), userFactories),\n                            adviceMethod.getDeclaredAnnotations().ofType(OnMethodEnter.class).getValue(SUPPRESS_ENTER).resolve(TypeDescription.class),\n                            adviceMethod.getDeclaredAnnotations().ofType(OnMethodEnter.class).getValue(SKIP_ON).resolve(TypeDescription.class),\n                            classReader);\n                        this.namedTypes = namedTypes;\n                        prependLineNumber = adviceMethod.getDeclaredAnnotations().ofType(OnMethodEnter.class).getValue(PREPEND_LINE_NUMBER).resolve(Boolean.class);\n                    }\n\n                    /**\n                     * Resolves enter advice that only exposes the enter type if this is necessary.\n                     *\n                     * @param adviceMethod  The advice method.\n                     * @param postProcessor The post processor to apply.\n                     * @param namedTypes    A mapping of all available local variables by their name to their type.\n                     * @param userFactories A list of user-defined factories for offset mappings.\n                     * @param exitType      The exit type or {@code void} if no exit type is defined.\n                     * @param classReader   The class reader for parsing the advice method's class file.\n                     * @param methodExit    {@code true} if exit advice is applied.\n                     * @return An appropriate enter handler.\n                     */\n                    protected static Resolved.ForMethodEnter of(MethodDescription.InDefinedShape adviceMethod,\n                                                                PostProcessor postProcessor,\n                                                                Map<String, TypeDefinition> namedTypes,\n                                                                List<? extends OffsetMapping.Factory<?>> userFactories,\n                                                                TypeDefinition exitType,\n                                                                ClassReader classReader,\n                                                                boolean methodExit) {\n                        return methodExit\n                            ? new WithRetainedEnterType(adviceMethod, postProcessor, namedTypes, userFactories, exitType, classReader)\n                            : new WithDiscardedEnterType(adviceMethod, postProcessor, namedTypes, userFactories, exitType, classReader);\n                    }\n\n                    @Override\n                    protected Map<Integer, TypeDefinition> resolveInitializationTypes(ArgumentHandler argumentHandler) {\n                        SortedMap<Integer, TypeDefinition> resolved = new TreeMap<>();\n                        for (Map.Entry<String, TypeDefinition> entry : namedTypes.entrySet()) {\n                            resolved.put(argumentHandler.named(entry.getKey()), entry.getValue());\n                        }\n                        return resolved;\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public Bound bind(TypeDescription instrumentedType,\n                                      MethodDescription instrumentedMethod,\n                                      MethodVisitor methodVisitor,\n                                      Implementation.Context implementationContext,\n                                      Assigner assigner,\n                                      ArgumentHandler.ForInstrumentedMethod argumentHandler,\n                                      MethodSizeHandler.ForInstrumentedMethod methodSizeHandler,\n                                      StackMapFrameHandler.ForInstrumentedMethod stackMapFrameHandler,\n                                      StackManipulation exceptionHandler,\n                                      RelocationHandler.Relocation relocation) {\n                        return new AdviceMethodInliner(instrumentedType,\n                            instrumentedMethod,\n                            methodVisitor,\n                            implementationContext,\n                            assigner,\n                            argumentHandler,\n                            methodSizeHandler,\n                            stackMapFrameHandler,\n                            suppressionHandler.bind(exceptionHandler),\n                            relocationHandler.bind(instrumentedMethod, relocation),\n                            exceptionHandler,\n                            classReader);\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public boolean isPrependLineNumber() {\n                        return prependLineNumber;\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public TypeDefinition getActualAdviceType() {\n                        return adviceMethod.getReturnType();\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public Map<String, TypeDefinition> getNamedTypes() {\n                        return namedTypes;\n                    }\n\n                    @Override\n                    protected MethodVisitor apply(MethodVisitor methodVisitor,\n                                                  Context implementationContext,\n                                                  Assigner assigner,\n                                                  ArgumentHandler.ForInstrumentedMethod argumentHandler,\n                                                  MethodSizeHandler.ForInstrumentedMethod methodSizeHandler,\n                                                  StackMapFrameHandler.ForInstrumentedMethod stackMapFrameHandler,\n                                                  TypeDescription instrumentedType,\n                                                  MethodDescription instrumentedMethod,\n                                                  SuppressionHandler.Bound suppressionHandler,\n                                                  RelocationHandler.Bound relocationHandler,\n                                                  StackManipulation exceptionHandler) {\n                        return doApply(methodVisitor,\n                            implementationContext,\n                            assigner,\n                            argumentHandler.bindEnter(adviceMethod),\n                            methodSizeHandler.bindEnter(adviceMethod),\n                            stackMapFrameHandler.bindEnter(adviceMethod),\n                            instrumentedType,\n                            instrumentedMethod,\n                            suppressionHandler,\n                            relocationHandler,\n                            exceptionHandler);\n                    }\n\n                    /**\n                     * Applies a resolution for a given instrumented method.\n                     *\n                     * @param instrumentedType      A description of the instrumented type.\n                     * @param instrumentedMethod    The instrumented method that is being bound.\n                     * @param methodVisitor         The method visitor for writing to the instrumented method.\n                     * @param implementationContext The implementation context to use.\n                     * @param assigner              The assigner to use.\n                     * @param argumentHandler       A handler for accessing values on the local variable array.\n                     * @param methodSizeHandler     A handler for computing the method size requirements.\n                     * @param stackMapFrameHandler  A handler for translating and injecting stack map frames.\n                     * @param suppressionHandler    The bound suppression handler to use.\n                     * @param relocationHandler     The bound relocation handler to use.\n                     * @param exceptionHandler      The exception handler that is resolved for the instrumented method.\n                     * @return A method visitor for visiting the advice method's byte code.\n                     */\n                    protected MethodVisitor doApply(MethodVisitor methodVisitor,\n                                                    Implementation.Context implementationContext,\n                                                    Assigner assigner,\n                                                    ArgumentHandler.ForAdvice argumentHandler,\n                                                    MethodSizeHandler.ForAdvice methodSizeHandler,\n                                                    StackMapFrameHandler.ForAdvice stackMapFrameHandler,\n                                                    TypeDescription instrumentedType,\n                                                    MethodDescription instrumentedMethod,\n                                                    SuppressionHandler.Bound suppressionHandler,\n                                                    RelocationHandler.Bound relocationHandler,\n                                                    StackManipulation exceptionHandler) {\n                        Map<Integer, OffsetMapping.Target> offsetMappings = new HashMap<>();\n                        for (Map.Entry<Integer, OffsetMapping> entry : this.offsetMappings.entrySet()) {\n                            offsetMappings.put(entry.getKey(), entry.getValue().resolve(instrumentedType,\n                                instrumentedMethod,\n                                assigner,\n                                argumentHandler,\n                                OffsetMapping.Sort.ENTER));\n                        }\n                        return new CodeTranslationVisitor(methodVisitor,\n                            implementationContext,\n                            argumentHandler,\n                            methodSizeHandler,\n                            stackMapFrameHandler,\n                            instrumentedType,\n                            instrumentedMethod,\n                            assigner,\n                            adviceMethod,\n                            offsetMappings,\n                            suppressionHandler,\n                            relocationHandler,\n                            exceptionHandler,\n                            postProcessor,\n                            false);\n                    }\n\n                    /**\n                     * Implementation of an advice that does expose an enter type.\n                     */\n                    protected static class WithRetainedEnterType extends Inlining.Resolved.ForMethodEnter {\n\n\n                        /**\n                         * Creates a new resolved dispatcher for implementing method enter advice that does expose the enter type.\n                         *\n                         * @param adviceMethod  The represented advice method.\n                         * @param postProcessor The post processor to apply.\n                         * @param namedTypes    A mapping of all available local variables by their name to their type.\n                         * @param userFactories A list of user-defined factories for offset mappings.\n                         * @param exitType      The exit type or {@code void} if no exit type is defined.\n                         * @param classReader   A class reader to query for the class file of the advice method.\n                         */\n                        protected WithRetainedEnterType(MethodDescription.InDefinedShape adviceMethod,\n                                                        PostProcessor postProcessor,\n                                                        Map<String, TypeDefinition> namedTypes,\n                                                        List<? extends OffsetMapping.Factory<?>> userFactories,\n                                                        TypeDefinition exitType,\n                                                        ClassReader classReader) {\n                            super(adviceMethod, postProcessor, namedTypes, userFactories, exitType, classReader);\n                        }\n\n                        /**\n                         * {@inheritDoc}\n                         */\n                        public TypeDefinition getAdviceType() {\n                            return adviceMethod.getReturnType();\n                        }\n                    }\n\n                    /**\n                     * Implementation of an advice that does not expose an enter type.\n                     */\n                    protected static class WithDiscardedEnterType extends Inlining.Resolved.ForMethodEnter {\n\n                        /**\n                         * Creates a new resolved dispatcher for implementing method enter advice that does not expose the enter type.\n                         *\n                         * @param adviceMethod  The represented advice method.\n                         * @param postProcessor The post processor to apply.\n                         * @param namedTypes    A mapping of all available local variables by their name to their type.\n                         * @param userFactories A list of user-defined factories for offset mappings.\n                         * @param exitType      The exit type or {@code void} if no exit type is defined.\n                         * @param classReader   A class reader to query for the class file of the advice method.\n                         */\n                        protected WithDiscardedEnterType(MethodDescription.InDefinedShape adviceMethod,\n                                                         PostProcessor postProcessor,\n                                                         Map<String, TypeDefinition> namedTypes,\n                                                         List<? extends OffsetMapping.Factory<?>> userFactories,\n                                                         TypeDefinition exitType,\n                                                         ClassReader classReader) {\n                            super(adviceMethod, postProcessor, namedTypes, userFactories, exitType, classReader);\n                        }\n\n                        /**\n                         * {@inheritDoc}\n                         */\n                        public TypeDefinition getAdviceType() {\n                            return TypeDescription.VOID;\n                        }\n\n                        /**\n                         * {@inheritDoc}\n                         */\n                        @Override\n                        protected MethodVisitor doApply(MethodVisitor methodVisitor,\n                                                        Context implementationContext,\n                                                        Assigner assigner,\n                                                        ArgumentHandler.ForAdvice argumentHandler,\n                                                        MethodSizeHandler.ForAdvice methodSizeHandler,\n                                                        StackMapFrameHandler.ForAdvice stackMapFrameHandler,\n                                                        TypeDescription instrumentedType,\n                                                        MethodDescription instrumentedMethod,\n                                                        SuppressionHandler.Bound suppressionHandler,\n                                                        RelocationHandler.Bound relocationHandler,\n                                                        StackManipulation exceptionHandler) {\n                            methodSizeHandler.requireLocalVariableLengthPadding(adviceMethod.getReturnType().getStackSize().getSize());\n                            return super.doApply(methodVisitor,\n                                implementationContext,\n                                assigner,\n                                argumentHandler,\n                                methodSizeHandler,\n                                stackMapFrameHandler,\n                                instrumentedType,\n                                instrumentedMethod,\n                                suppressionHandler,\n                                relocationHandler,\n                                exceptionHandler);\n                        }\n                    }\n                }\n\n                /**\n                 * A resolved dispatcher for implementing method exit advice.\n                 */\n                @HashCodeAndEqualsPlugin.Enhance\n                protected abstract static class ForMethodExit extends Inlining.Resolved implements Dispatcher.Resolved.ForMethodExit {\n\n                    /**\n                     * A mapping of uninitialized local variables by their name.\n                     */\n                    private final Map<String, TypeDefinition> uninitializedNamedTypes;\n\n                    /**\n                     * {@code true} if the arguments of the instrumented method should be copied before executing the instrumented method.\n                     */\n                    private final boolean backupArguments;\n\n                    /**\n                     * Creates a new resolved dispatcher for implementing method exit advice.\n                     *\n                     * @param adviceMethod            The represented advice method.\n                     * @param postProcessor           The post processor to apply.\n                     * @param namedTypes              A mapping of all available local variables by their name to their type.\n                     * @param uninitializedNamedTypes A mapping of all uninitialized local variables by their name to their type.\n                     * @param userFactories           A list of user-defined factories for offset mappings.\n                     * @param classReader             The class reader for parsing the advice method's class file.\n                     * @param enterType               The type of the value supplied by the enter advice method or {@code void} if no such value exists.\n                     */\n                    protected ForMethodExit(MethodDescription.InDefinedShape adviceMethod,\n                                            PostProcessor postProcessor,\n                                            Map<String, TypeDefinition> namedTypes,\n                                            Map<String, TypeDefinition> uninitializedNamedTypes,\n                                            List<? extends OffsetMapping.Factory<?>> userFactories,\n                                            ClassReader classReader,\n                                            TypeDefinition enterType) {\n                        super(adviceMethod,\n                            postProcessor,\n                            CompoundList.of(Arrays.asList(OffsetMapping.ForArgument.Unresolved.Factory.INSTANCE,\n                                OffsetMapping.ForAllArguments.Factory.INSTANCE,\n                                OffsetMapping.ForThisReference.Factory.INSTANCE,\n                                OffsetMapping.ForField.Unresolved.Factory.INSTANCE,\n                                OffsetMapping.ForOrigin.Factory.INSTANCE,\n                                OffsetMapping.ForUnusedValue.Factory.INSTANCE,\n                                OffsetMapping.ForStubValue.INSTANCE,\n                                OffsetMapping.ForEnterValue.Factory.of(enterType),\n                                OffsetMapping.ForExitValue.Factory.of(adviceMethod.getReturnType()),\n                                new OffsetMapping.ForLocalValue.Factory(namedTypes),\n                                OffsetMapping.ForReturnValue.Factory.INSTANCE,\n                                OffsetMapping.ForThrowable.Factory.of(adviceMethod)\n                            ), userFactories),\n                            adviceMethod.getDeclaredAnnotations().ofType(OnMethodExit.class).getValue(SUPPRESS_EXIT).resolve(TypeDescription.class),\n                            adviceMethod.getDeclaredAnnotations().ofType(OnMethodExit.class).getValue(REPEAT_ON).resolve(TypeDescription.class),\n                            classReader);\n                        this.uninitializedNamedTypes = uninitializedNamedTypes;\n                        backupArguments = adviceMethod.getDeclaredAnnotations().ofType(OnMethodExit.class).getValue(BACKUP_ARGUMENTS).resolve(Boolean.class);\n                    }\n\n                    /**\n                     * Resolves exit advice that handles exceptions depending on the specification of the exit advice.\n                     *\n                     * @param adviceMethod            The advice method.\n                     * @param postProcessor           The post processor to apply.\n                     * @param namedTypes              A mapping of all available local variables by their name to their type.\n                     * @param uninitializedNamedTypes A mapping of all uninitialized local variables by their name to their type.\n                     * @param userFactories           A list of user-defined factories for offset mappings.\n                     * @param classReader             The class reader for parsing the advice method's class file.\n                     * @param enterType               The type of the value supplied by the enter advice method or {@code void} if no such value exists.\n                     * @return An appropriate exit handler.\n                     */\n                    protected static Resolved.ForMethodExit of(MethodDescription.InDefinedShape adviceMethod,\n                                                               PostProcessor postProcessor,\n                                                               Map<String, TypeDefinition> namedTypes,\n                                                               Map<String, TypeDefinition> uninitializedNamedTypes,\n                                                               List<? extends OffsetMapping.Factory<?>> userFactories,\n                                                               ClassReader classReader,\n                                                               TypeDefinition enterType) {\n                        TypeDescription throwable = adviceMethod.getDeclaredAnnotations()\n                            .ofType(OnMethodExit.class)\n                            .getValue(ON_THROWABLE).resolve(TypeDescription.class);\n                        return isNoExceptionHandler(throwable)\n                            ? new WithoutExceptionHandler(adviceMethod, postProcessor, namedTypes, uninitializedNamedTypes, userFactories, classReader, enterType)\n                            : new WithExceptionHandler(adviceMethod, postProcessor, namedTypes, uninitializedNamedTypes, userFactories, classReader, enterType, throwable);\n                    }\n\n                    protected static Resolved.ForMethodExit ofNonThrowable(MethodDescription.InDefinedShape adviceMethod,\n                                                                           PostProcessor postProcessor,\n                                                                           Map<String, TypeDefinition> namedTypes,\n                                                                           Map<String, TypeDefinition> uninitializedNamedTypes,\n                                                                           List<? extends OffsetMapping.Factory<?>> userFactories,\n                                                                           ClassReader classReader,\n                                                                           TypeDefinition enterType) {\n                        return new WithoutExceptionHandler(adviceMethod, postProcessor, namedTypes,\n                            uninitializedNamedTypes, userFactories, classReader, enterType);\n                    }\n\n                    @Override\n                    public Map<String, TypeDefinition> getNamedTypes() {\n                        return uninitializedNamedTypes;\n                    }\n\n                    @Override\n                    protected Map<Integer, TypeDefinition> resolveInitializationTypes(ArgumentHandler argumentHandler) {\n                        SortedMap<Integer, TypeDefinition> resolved = new TreeMap<>();\n                        for (Map.Entry<String, TypeDefinition> entry : uninitializedNamedTypes.entrySet()) {\n                            resolved.put(argumentHandler.named(entry.getKey()), entry.getValue());\n                        }\n                        if (!adviceMethod.getReturnType().represents(void.class)) {\n                            resolved.put(argumentHandler.exit(), adviceMethod.getReturnType());\n                        }\n                        return resolved;\n                    }\n\n                    @Override\n                    protected MethodVisitor apply(MethodVisitor methodVisitor,\n                                                  Implementation.Context implementationContext,\n                                                  Assigner assigner,\n                                                  ArgumentHandler.ForInstrumentedMethod argumentHandler,\n                                                  MethodSizeHandler.ForInstrumentedMethod methodSizeHandler,\n                                                  StackMapFrameHandler.ForInstrumentedMethod stackMapFrameHandler,\n                                                  TypeDescription instrumentedType,\n                                                  MethodDescription instrumentedMethod,\n                                                  SuppressionHandler.Bound suppressionHandler,\n                                                  RelocationHandler.Bound relocationHandler,\n                                                  StackManipulation exceptionHandler) {\n                        return doApply(methodVisitor,\n                            implementationContext,\n                            assigner,\n                            argumentHandler.bindExit(adviceMethod, isNoExceptionHandler(getThrowable())),\n                            methodSizeHandler.bindExit(adviceMethod),\n                            stackMapFrameHandler.bindExit(adviceMethod),\n                            instrumentedType,\n                            instrumentedMethod,\n                            suppressionHandler,\n                            relocationHandler,\n                            exceptionHandler);\n                    }\n\n                    /**\n                     * Applies a resolution for a given instrumented method.\n                     *\n                     * @param instrumentedType      A description of the instrumented type.\n                     * @param instrumentedMethod    The instrumented method that is being bound.\n                     * @param methodVisitor         The method visitor for writing to the instrumented method.\n                     * @param implementationContext The implementation context to use.\n                     * @param assigner              The assigner to use.\n                     * @param argumentHandler       A handler for accessing values on the local variable array.\n                     * @param methodSizeHandler     A handler for computing the method size requirements.\n                     * @param stackMapFrameHandler  A handler for translating and injecting stack map frames.\n                     * @param suppressionHandler    The bound suppression handler to use.\n                     * @param relocationHandler     The bound relocation handler to use.\n                     * @param exceptionHandler      The exception handler that is resolved for the instrumented method.\n                     * @return A method visitor for visiting the advice method's byte code.\n                     */\n                    private MethodVisitor doApply(MethodVisitor methodVisitor,\n                                                  Implementation.Context implementationContext,\n                                                  Assigner assigner,\n                                                  ArgumentHandler.ForAdvice argumentHandler,\n                                                  MethodSizeHandler.ForAdvice methodSizeHandler,\n                                                  StackMapFrameHandler.ForAdvice stackMapFrameHandler,\n                                                  TypeDescription instrumentedType,\n                                                  MethodDescription instrumentedMethod,\n                                                  SuppressionHandler.Bound suppressionHandler,\n                                                  RelocationHandler.Bound relocationHandler,\n                                                  StackManipulation exceptionHandler) {\n                        Map<Integer, OffsetMapping.Target> offsetMappings = new HashMap<>();\n                        for (Map.Entry<Integer, OffsetMapping> entry : this.offsetMappings.entrySet()) {\n                            offsetMappings.put(entry.getKey(), entry.getValue().resolve(instrumentedType,\n                                instrumentedMethod,\n                                assigner,\n                                argumentHandler,\n                                OffsetMapping.Sort.EXIT));\n                        }\n                        return new CodeTranslationVisitor(methodVisitor,\n                            implementationContext,\n                            argumentHandler,\n                            methodSizeHandler,\n                            stackMapFrameHandler,\n                            instrumentedType,\n                            instrumentedMethod,\n                            assigner,\n                            adviceMethod,\n                            offsetMappings,\n                            suppressionHandler,\n                            relocationHandler,\n                            exceptionHandler,\n                            postProcessor,\n                            true);\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public ArgumentHandler.Factory getArgumentHandlerFactory() {\n                        return backupArguments\n                            ? ArgumentHandler.Factory.COPYING\n                            : ArgumentHandler.Factory.SIMPLE;\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public TypeDefinition getAdviceType() {\n                        return adviceMethod.getReturnType();\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public Bound bind(TypeDescription instrumentedType,\n                                      MethodDescription instrumentedMethod,\n                                      MethodVisitor methodVisitor,\n                                      Implementation.Context implementationContext,\n                                      Assigner assigner,\n                                      ArgumentHandler.ForInstrumentedMethod argumentHandler,\n                                      MethodSizeHandler.ForInstrumentedMethod methodSizeHandler,\n                                      StackMapFrameHandler.ForInstrumentedMethod stackMapFrameHandler,\n                                      StackManipulation exceptionHandler,\n                                      RelocationHandler.Relocation relocation) {\n                        return new AdviceMethodInliner(instrumentedType,\n                            instrumentedMethod,\n                            methodVisitor,\n                            implementationContext,\n                            assigner,\n                            argumentHandler,\n                            methodSizeHandler,\n                            stackMapFrameHandler,\n                            suppressionHandler.bind(exceptionHandler),\n                            relocationHandler.bind(instrumentedMethod, relocation),\n                            exceptionHandler,\n                            classReader);\n                    }\n\n                    /**\n                     * Implementation of exit advice that handles exceptions.\n                     */\n                    @HashCodeAndEqualsPlugin.Enhance\n                    protected static class WithExceptionHandler extends Inlining.Resolved.ForMethodExit {\n\n                        /**\n                         * The type of the handled throwable type for which this advice is invoked.\n                         */\n                        private final TypeDescription throwable;\n\n                        /**\n                         * Creates a new resolved dispatcher for implementing method exit advice that handles exceptions.\n                         *\n                         * @param adviceMethod            The represented advice method.\n                         * @param postProcessor           The post processor to apply.\n                         * @param namedTypes              A mapping of all available local variables by their name to their type.\n                         * @param uninitializedNamedTypes A mapping of all uninitialized local variables by their name to their type.\n                         * @param userFactories           A list of user-defined factories for offset mappings.\n                         * @param classReader             The class reader for parsing the advice method's class file.\n                         * @param enterType               The type of the value supplied by the enter advice method or\n                         *                                a description of {@code void} if no such value exists.\n                         * @param throwable               The type of the handled throwable type for which this advice is invoked.\n                         */\n                        protected WithExceptionHandler(MethodDescription.InDefinedShape adviceMethod,\n                                                       PostProcessor postProcessor,\n                                                       Map<String, TypeDefinition> namedTypes,\n                                                       Map<String, TypeDefinition> uninitializedNamedTypes,\n                                                       List<? extends OffsetMapping.Factory<?>> userFactories,\n                                                       ClassReader classReader,\n                                                       TypeDefinition enterType,\n                                                       TypeDescription throwable) {\n                            super(adviceMethod, postProcessor, namedTypes, uninitializedNamedTypes, userFactories, classReader, enterType);\n                            this.throwable = throwable;\n                        }\n\n                        /**\n                         * {@inheritDoc}\n                         */\n                        public TypeDescription getThrowable() {\n                            return throwable;\n                        }\n                    }\n\n                    /**\n                     * Implementation of exit advice that ignores exceptions.\n                     */\n                    protected static class WithoutExceptionHandler extends Inlining.Resolved.ForMethodExit {\n\n                        /**\n                         * Creates a new resolved dispatcher for implementing method exit advice that does not handle exceptions.\n                         *\n                         * @param adviceMethod            The represented advice method.\n                         * @param postProcessor           The post processor to apply.\n                         * @param namedTypes              A mapping of all available local variables by their name to their type.\n                         * @param uninitializedNamedTypes A mapping of all uninitialized local variables by their name to their type.\n                         * @param userFactories           A list of user-defined factories for offset mappings.\n                         * @param classReader             A class reader to query for the class file of the advice method.\n                         * @param enterType               The type of the value supplied by the enter advice method or\n                         *                                a description of {@code void} if no such value exists.\n                         */\n                        protected WithoutExceptionHandler(MethodDescription.InDefinedShape adviceMethod,\n                                                          PostProcessor postProcessor,\n                                                          Map<String, TypeDefinition> namedTypes,\n                                                          Map<String, TypeDefinition> uninitializedNamedTypes,\n                                                          List<? extends OffsetMapping.Factory<?>> userFactories,\n                                                          ClassReader classReader,\n                                                          TypeDefinition enterType) {\n                            super(adviceMethod, postProcessor, namedTypes, uninitializedNamedTypes, userFactories, classReader, enterType);\n                        }\n\n                        /**\n                         * {@inheritDoc}\n                         */\n                        public TypeDescription getThrowable() {\n                            return NoExceptionHandler.DESCRIPTION;\n                        }\n                    }\n                }\n            }\n\n            /**\n             * A visitor for translating an advice method's byte code for inlining into the instrumented method.\n             */\n            protected static class CodeTranslationVisitor extends MethodVisitor {\n\n                /**\n                 * The original method visitor to which all instructions are eventually written to.\n                 */\n                protected final MethodVisitor methodVisitor;\n\n                /**\n                 * The implementation context to use.\n                 */\n                protected final Context implementationContext;\n\n                /**\n                 * A handler for accessing values on the local variable array.\n                 */\n                protected final ArgumentHandler.ForAdvice argumentHandler;\n\n                /**\n                 * A handler for computing the method size requirements.\n                 */\n                protected final MethodSizeHandler.ForAdvice methodSizeHandler;\n\n                /**\n                 * A handler for translating and injecting stack map frames.\n                 */\n                protected final StackMapFrameHandler.ForAdvice stackMapFrameHandler;\n\n                /**\n                 * The instrumented type.\n                 */\n                private final TypeDescription instrumentedType;\n\n                /**\n                 * The instrumented method.\n                 */\n                private final MethodDescription instrumentedMethod;\n\n                /**\n                 * The assigner to use.\n                 */\n                private final Assigner assigner;\n\n                /**\n                 * The advice method.\n                 */\n                protected final MethodDescription.InDefinedShape adviceMethod;\n\n                /**\n                 * A mapping of offsets to resolved target offsets in the instrumented method.\n                 */\n                private final Map<Integer, OffsetMapping.Target> offsetMappings;\n\n                /**\n                 * A bound suppression handler that is used for suppressing exceptions of this advice method.\n                 */\n                private final SuppressionHandler.Bound suppressionHandler;\n\n                /**\n                 * A bound relocation handler that is responsible for considering a non-standard control flow.\n                 */\n                private final RelocationHandler.Bound relocationHandler;\n\n                /**\n                 * The exception handler that is resolved for the instrumented method.\n                 */\n                private final StackManipulation exceptionHandler;\n\n                /**\n                 * The post processor to apply.\n                 */\n                private final PostProcessor postProcessor;\n\n                /**\n                 * {@code true} if this visitor is for exit advice.\n                 */\n                private final boolean exit;\n\n                /**\n                 * A label indicating the end of the advice byte code.\n                 */\n                protected final Label endOfMethod;\n\n                /**\n                 * Creates a new code translation visitor.\n                 *\n                 * @param methodVisitor         A method visitor for writing the instrumented method's byte code.\n                 * @param implementationContext The implementation context to use.\n                 * @param argumentHandler       A handler for accessing values on the local variable array.\n                 * @param methodSizeHandler     A handler for computing the method size requirements.\n                 * @param stackMapFrameHandler  A handler for translating and injecting stack map frames.\n                 * @param instrumentedType      The instrumented type.\n                 * @param instrumentedMethod    The instrumented method.\n                 * @param assigner              The assigner to use.\n                 * @param adviceMethod          The advice method.\n                 * @param offsetMappings        A mapping of offsets to resolved target offsets in the instrumented method.\n                 * @param suppressionHandler    A bound suppression handler that is used for suppressing exceptions of this advice method.\n                 * @param relocationHandler     A bound relocation handler that is responsible for considering a non-standard control flow.\n                 * @param exceptionHandler      The exception handler that is resolved for the instrumented method.\n                 * @param postProcessor         The post processor to apply.\n                 * @param exit                  {@code true} if this visitor is for exit advice.\n                 */\n                protected CodeTranslationVisitor(MethodVisitor methodVisitor,\n                                                 Context implementationContext,\n                                                 ArgumentHandler.ForAdvice argumentHandler,\n                                                 MethodSizeHandler.ForAdvice methodSizeHandler,\n                                                 StackMapFrameHandler.ForAdvice stackMapFrameHandler,\n                                                 TypeDescription instrumentedType,\n                                                 MethodDescription instrumentedMethod,\n                                                 Assigner assigner,\n                                                 MethodDescription.InDefinedShape adviceMethod,\n                                                 Map<Integer, OffsetMapping.Target> offsetMappings,\n                                                 SuppressionHandler.Bound suppressionHandler,\n                                                 RelocationHandler.Bound relocationHandler,\n                                                 StackManipulation exceptionHandler,\n                                                 PostProcessor postProcessor,\n                                                 boolean exit) {\n                    super(OpenedClassReader.ASM_API, new StackAwareMethodVisitor(methodVisitor, instrumentedMethod));\n                    this.methodVisitor = methodVisitor;\n                    this.implementationContext = implementationContext;\n                    this.argumentHandler = argumentHandler;\n                    this.methodSizeHandler = methodSizeHandler;\n                    this.stackMapFrameHandler = stackMapFrameHandler;\n                    this.instrumentedType = instrumentedType;\n                    this.instrumentedMethod = instrumentedMethod;\n                    this.assigner = assigner;\n                    this.adviceMethod = adviceMethod;\n                    this.offsetMappings = offsetMappings;\n                    this.suppressionHandler = suppressionHandler;\n                    this.relocationHandler = relocationHandler;\n                    this.exceptionHandler = exceptionHandler;\n                    this.postProcessor = postProcessor;\n                    this.exit = exit;\n                    endOfMethod = new Label();\n                }\n\n                /**\n                 * Propagates a label for an exception handler that is typically suppressed by the overlaying\n                 * {@link Resolved.AdviceMethodInliner.ExceptionTableSubstitutor}.\n                 *\n                 * @param label The label to register as a target for an exception handler.\n                 */\n                protected void propagateHandler(Label label) {\n                    ((StackAwareMethodVisitor) mv).register(label, Collections.singletonList(StackSize.SINGLE));\n                }\n\n                @Override\n                public void visitParameter(String name, int modifiers) {\n                    /* do nothing */\n                }\n\n                @Override\n                public void visitAnnotableParameterCount(int count, boolean visible) {\n                    /* do nothing */\n                }\n\n                @Override\n                public AnnotationVisitor visitAnnotationDefault() {\n                    return IGNORE_ANNOTATION;\n                }\n\n                @Override\n                public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {\n                    return IGNORE_ANNOTATION;\n                }\n\n                @Override\n                public AnnotationVisitor visitTypeAnnotation(int typeReference, TypePath typePath, String descriptor, boolean visible) {\n                    return IGNORE_ANNOTATION;\n                }\n\n                @Override\n                public AnnotationVisitor visitParameterAnnotation(int index, String descriptor, boolean visible) {\n                    return IGNORE_ANNOTATION;\n                }\n\n                @Override\n                public void visitAttribute(Attribute attribute) {\n                    /* do nothing */\n                }\n\n                @Override\n                public void visitCode() {\n                    suppressionHandler.onStart(methodVisitor);\n                }\n\n                @Override\n                public void visitFrame(int type, int localVariableLength, Object[] localVariable, int stackSize, Object[] stack) {\n                    stackMapFrameHandler.translateFrame(methodVisitor, type, localVariableLength, localVariable, stackSize, stack);\n                }\n\n                @Override\n                public void visitVarInsn(int opcode, int offset) {\n                    OffsetMapping.Target target = offsetMappings.get(offset);\n                    if (target != null) {\n                        StackManipulation stackManipulation;\n                        StackSize expectedGrowth;\n                        switch (opcode) {\n                            case Opcodes.ILOAD:\n                            case Opcodes.FLOAD:\n                            case Opcodes.ALOAD:\n                                stackManipulation = target.resolveRead();\n                                expectedGrowth = StackSize.SINGLE;\n                                break;\n                            case Opcodes.DLOAD:\n                            case Opcodes.LLOAD:\n                                stackManipulation = target.resolveRead();\n                                expectedGrowth = StackSize.DOUBLE;\n                                break;\n                            case Opcodes.ISTORE:\n                            case Opcodes.FSTORE:\n                            case Opcodes.ASTORE:\n                            case Opcodes.LSTORE:\n                            case Opcodes.DSTORE:\n                                stackManipulation = target.resolveWrite();\n                                expectedGrowth = StackSize.ZERO;\n                                break;\n                            default:\n                                throw new IllegalStateException(\"Unexpected opcode: \" + opcode);\n                        }\n                        methodSizeHandler.requireStackSizePadding(stackManipulation.apply(mv, implementationContext).getMaximalSize() - expectedGrowth.getSize());\n                    } else {\n                        mv.visitVarInsn(opcode, argumentHandler.mapped(offset));\n                    }\n                }\n\n                @Override\n                public void visitIincInsn(int offset, int value) {\n                    OffsetMapping.Target target = offsetMappings.get(offset);\n                    if (target != null) {\n                        methodSizeHandler.requireStackSizePadding(target.resolveIncrement(value).apply(mv, implementationContext).getMaximalSize());\n                    } else {\n                        mv.visitIincInsn(argumentHandler.mapped(offset), value);\n                    }\n                }\n\n                @Override\n                public void visitInsn(int opcode) {\n                    switch (opcode) {\n                        case Opcodes.RETURN:\n                            ((StackAwareMethodVisitor) mv).drainStack();\n                            break;\n                        case Opcodes.IRETURN:\n                            methodSizeHandler.requireLocalVariableLength(((StackAwareMethodVisitor) mv).drainStack(Opcodes.ISTORE, Opcodes.ILOAD, StackSize.SINGLE));\n                            break;\n                        case Opcodes.ARETURN:\n                            methodSizeHandler.requireLocalVariableLength(((StackAwareMethodVisitor) mv).drainStack(Opcodes.ASTORE, Opcodes.ALOAD, StackSize.SINGLE));\n                            break;\n                        case Opcodes.FRETURN:\n                            methodSizeHandler.requireLocalVariableLength(((StackAwareMethodVisitor) mv).drainStack(Opcodes.FSTORE, Opcodes.FLOAD, StackSize.SINGLE));\n                            break;\n                        case Opcodes.LRETURN:\n                            methodSizeHandler.requireLocalVariableLength(((StackAwareMethodVisitor) mv).drainStack(Opcodes.LSTORE, Opcodes.LLOAD, StackSize.DOUBLE));\n                            break;\n                        case Opcodes.DRETURN:\n                            methodSizeHandler.requireLocalVariableLength(((StackAwareMethodVisitor) mv).drainStack(Opcodes.DSTORE, Opcodes.DLOAD, StackSize.DOUBLE));\n                            break;\n                        default:\n                            mv.visitInsn(opcode);\n                            return;\n                    }\n                    mv.visitJumpInsn(Opcodes.GOTO, endOfMethod);\n                }\n\n                @Override\n                public void visitEnd() {\n                    suppressionHandler.onEnd(methodVisitor, implementationContext, methodSizeHandler, stackMapFrameHandler, adviceMethod.getReturnType());\n                    methodVisitor.visitLabel(endOfMethod);\n                    if (adviceMethod.getReturnType().represents(boolean.class)\n                        || adviceMethod.getReturnType().represents(byte.class)\n                        || adviceMethod.getReturnType().represents(short.class)\n                        || adviceMethod.getReturnType().represents(char.class)\n                        || adviceMethod.getReturnType().represents(int.class)) {\n                        stackMapFrameHandler.injectReturnFrame(methodVisitor);\n                        methodVisitor.visitVarInsn(Opcodes.ISTORE, exit ? argumentHandler.exit() : argumentHandler.enter());\n                    } else if (adviceMethod.getReturnType().represents(long.class)) {\n                        stackMapFrameHandler.injectReturnFrame(methodVisitor);\n                        methodVisitor.visitVarInsn(Opcodes.LSTORE, exit ? argumentHandler.exit() : argumentHandler.enter());\n                    } else if (adviceMethod.getReturnType().represents(float.class)) {\n                        stackMapFrameHandler.injectReturnFrame(methodVisitor);\n                        methodVisitor.visitVarInsn(Opcodes.FSTORE, exit ? argumentHandler.exit() : argumentHandler.enter());\n                    } else if (adviceMethod.getReturnType().represents(double.class)) {\n                        stackMapFrameHandler.injectReturnFrame(methodVisitor);\n                        methodVisitor.visitVarInsn(Opcodes.DSTORE, exit ? argumentHandler.exit() : argumentHandler.enter());\n                    } else if (!adviceMethod.getReturnType().represents(void.class)) {\n                        stackMapFrameHandler.injectReturnFrame(methodVisitor);\n                        methodVisitor.visitVarInsn(Opcodes.ASTORE, exit ? argumentHandler.exit() : argumentHandler.enter());\n                    }\n                    methodSizeHandler.requireStackSize(postProcessor\n                        .resolve(instrumentedType, instrumentedMethod, assigner, argumentHandler, stackMapFrameHandler, exceptionHandler)\n                        .apply(methodVisitor, implementationContext).getMaximalSize());\n                    methodSizeHandler.requireStackSize(relocationHandler.apply(methodVisitor, exit ? argumentHandler.exit() : argumentHandler.enter()));\n                    stackMapFrameHandler.injectCompletionFrame(methodVisitor);\n                }\n\n                @Override\n                public void visitMaxs(int stackSize, int localVariableLength) {\n                    methodSizeHandler.recordMaxima(stackSize, localVariableLength);\n                }\n            }\n        }\n\n        /**\n         * A dispatcher for an advice method that is being invoked from the instrumented method.\n         */\n        @HashCodeAndEqualsPlugin.Enhance\n        class Delegating implements Unresolved {\n\n            /**\n             * The advice method.\n             */\n            protected final MethodDescription.InDefinedShape adviceMethod;\n\n            /**\n             * The delegator to use.\n             */\n            protected final Delegator delegator;\n\n            /**\n             * Creates a new delegating advice dispatcher.\n             *\n             * @param adviceMethod The advice method.\n             * @param delegator    The delegator to use.\n             */\n            protected Delegating(MethodDescription.InDefinedShape adviceMethod, Delegator delegator) {\n                this.adviceMethod = adviceMethod;\n                this.delegator = delegator;\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public boolean isAlive() {\n                return true;\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public boolean isBinary() {\n                return false;\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public TypeDescription getAdviceType() {\n                return adviceMethod.getReturnType().asErasure();\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public Map<String, TypeDefinition> getNamedTypes() {\n                return Collections.emptyMap();\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public Dispatcher.Resolved.ForMethodEnter asMethodEnter(List<? extends OffsetMapping.Factory<?>> userFactories,\n                                                                    ClassReader classReader,\n                                                                    Unresolved methodExit,\n                                                                    PostProcessor.Factory postProcessorFactory) {\n                return Resolved.ForMethodEnter.of(adviceMethod, postProcessorFactory.make(adviceMethod, false), delegator, userFactories, methodExit.getAdviceType(), methodExit.isAlive());\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public Dispatcher.Resolved.ForMethodExit asMethodExit(List<? extends OffsetMapping.Factory<?>> userFactories,\n                                                                  ClassReader classReader,\n                                                                  Unresolved methodEnter,\n                                                                  PostProcessor.Factory postProcessorFactory) {\n                Map<String, TypeDefinition> namedTypes = methodEnter.getNamedTypes();\n                for (ParameterDescription parameterDescription : adviceMethod.getParameters().filter(isAnnotatedWith(Local.class))) {\n                    String name = parameterDescription.getDeclaredAnnotations()\n                        .ofType(Local.class).getValue(OffsetMapping.ForLocalValue.Factory.LOCAL_VALUE)\n                        .resolve(String.class);\n                    TypeDefinition typeDefinition = namedTypes.get(name);\n                    if (typeDefinition == null) {\n                        throw new IllegalStateException(adviceMethod + \" attempts use of undeclared local variable \" + name);\n                    } else if (!typeDefinition.equals(parameterDescription.getType())) {\n                        throw new IllegalStateException(adviceMethod + \" does not read variable \" + name + \" as \" + typeDefinition);\n                    }\n                }\n                return Resolved.ForMethodExit.of(adviceMethod, postProcessorFactory.make(adviceMethod, true), delegator, namedTypes, userFactories, methodEnter.getAdviceType());\n            }\n\n            /**\n             * A resolved version of a dispatcher.\n             */\n            protected abstract static class Resolved extends Dispatcher.Resolved.AbstractBase {\n\n                /**\n                 * The delegator to use.\n                 */\n                protected final Delegator delegator;\n\n                /**\n                 * Creates a new resolved version of a dispatcher.\n                 *\n                 * @param adviceMethod    The represented advice method.\n                 * @param postProcessor   The post processor to apply.\n                 * @param factories       A list of factories to resolve for the parameters of the advice method.\n                 * @param throwableType   The type to handle by a suppression handler or {@link NoExceptionHandler} to not handle any exceptions.\n                 * @param relocatableType The type to trigger a relocation of the method's control flow or {@code void} if no relocation should be executed.\n                 * @param delegator       The delegator to use.\n                 */\n                protected Resolved(MethodDescription.InDefinedShape adviceMethod,\n                                   PostProcessor postProcessor,\n                                   List<? extends OffsetMapping.Factory<?>> factories,\n                                   TypeDescription throwableType,\n                                   TypeDescription relocatableType,\n                                   Delegator delegator) {\n                    super(adviceMethod, postProcessor, factories, throwableType, relocatableType, OffsetMapping.Factory.AdviceType.DELEGATION);\n                    this.delegator = delegator;\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public Map<String, TypeDefinition> getNamedTypes() {\n                    return Collections.emptyMap();\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public Bound bind(TypeDescription instrumentedType,\n                                  MethodDescription instrumentedMethod,\n                                  MethodVisitor methodVisitor,\n                                  Implementation.Context implementationContext,\n                                  Assigner assigner,\n                                  ArgumentHandler.ForInstrumentedMethod argumentHandler,\n                                  MethodSizeHandler.ForInstrumentedMethod methodSizeHandler,\n                                  StackMapFrameHandler.ForInstrumentedMethod stackMapFrameHandler,\n                                  StackManipulation exceptionHandler,\n                                  RelocationHandler.Relocation relocation) {\n                    if (!adviceMethod.isVisibleTo(instrumentedType)) {\n                        throw new IllegalStateException(adviceMethod + \" is not visible to \" + instrumentedMethod.getDeclaringType());\n                    }\n                    return resolve(instrumentedType,\n                        instrumentedMethod,\n                        methodVisitor,\n                        implementationContext,\n                        assigner,\n                        argumentHandler,\n                        methodSizeHandler,\n                        stackMapFrameHandler,\n                        exceptionHandler,\n                        relocation);\n                }\n\n                /**\n                 * Binds this dispatcher for resolution to a specific method.\n                 *\n                 * @param instrumentedType      A description of the instrumented type.\n                 * @param instrumentedMethod    The instrumented method that is being bound.\n                 * @param methodVisitor         The method visitor for writing to the instrumented method.\n                 * @param implementationContext The implementation context to use.\n                 * @param assigner              The assigner to use.\n                 * @param argumentHandler       A handler for accessing values on the local variable array.\n                 * @param methodSizeHandler     A handler for computing the method size requirements.\n                 * @param stackMapFrameHandler  A handler for translating and injecting stack map frames.\n                 * @param exceptionHandler      The stack manipulation to apply within a suppression handler.\n                 * @param relocation            A relocation to use with a relocation handler.\n                 * @return An appropriate bound advice dispatcher.\n                 */\n                protected abstract Bound resolve(TypeDescription instrumentedType,\n                                                 MethodDescription instrumentedMethod,\n                                                 MethodVisitor methodVisitor,\n                                                 Implementation.Context implementationContext,\n                                                 Assigner assigner,\n                                                 ArgumentHandler.ForInstrumentedMethod argumentHandler,\n                                                 MethodSizeHandler.ForInstrumentedMethod methodSizeHandler,\n                                                 StackMapFrameHandler.ForInstrumentedMethod stackMapFrameHandler,\n                                                 StackManipulation exceptionHandler,\n                                                 RelocationHandler.Relocation relocation);\n\n                /**\n                 * A bound advice method that copies the code by first extracting the exception table and later appending the\n                 * code of the method without copying any meta data.\n                 */\n                protected abstract static class AdviceMethodWriter implements Bound {\n\n                    /**\n                     * The advice method.\n                     */\n                    protected final MethodDescription.InDefinedShape adviceMethod;\n\n                    /**\n                     * The instrumented type.\n                     */\n                    private final TypeDescription instrumentedType;\n\n                    /**\n                     * The instrumented method.\n                     */\n                    private final MethodDescription instrumentedMethod;\n\n                    /**\n                     * The assigner to use.\n                     */\n                    private final Assigner assigner;\n\n                    /**\n                     * The offset mappings available to this advice.\n                     */\n                    private final List<OffsetMapping.Target> offsetMappings;\n\n                    /**\n                     * The method visitor for writing the instrumented method.\n                     */\n                    protected final MethodVisitor methodVisitor;\n\n                    /**\n                     * The implementation context to use.\n                     */\n                    protected final Implementation.Context implementationContext;\n\n                    /**\n                     * A handler for accessing values on the local variable array.\n                     */\n                    protected final ArgumentHandler.ForAdvice argumentHandler;\n\n                    /**\n                     * A handler for computing the method size requirements.\n                     */\n                    protected final MethodSizeHandler.ForAdvice methodSizeHandler;\n\n                    /**\n                     * A handler for translating and injecting stack map frames.\n                     */\n                    protected final StackMapFrameHandler.ForAdvice stackMapFrameHandler;\n\n                    /**\n                     * A bound suppression handler that is used for suppressing exceptions of this advice method.\n                     */\n                    private final SuppressionHandler.Bound suppressionHandler;\n\n                    /**\n                     * A bound relocation handler that is responsible for considering a non-standard control flow.\n                     */\n                    private final RelocationHandler.Bound relocationHandler;\n\n                    /**\n                     * The exception handler that is resolved for the instrumented method.\n                     */\n                    private final StackManipulation exceptionHandler;\n\n                    /**\n                     * The post processor to apply.\n                     */\n                    private final PostProcessor postProcessor;\n\n                    /**\n                     * The delegator to use.\n                     */\n                    private final Delegator delegator;\n\n                    /**\n                     * Creates a new advice method writer.\n                     *\n                     * @param adviceMethod          The advice method.\n                     * @param instrumentedType      The instrumented type.\n                     * @param instrumentedMethod    The instrumented method.\n                     * @param assigner              The assigner to use.\n                     * @param postProcessor         The post processor to apply.\n                     * @param offsetMappings        The offset mappings available to this advice.\n                     * @param methodVisitor         The method visitor for writing the instrumented method.\n                     * @param implementationContext The implementation context to use.\n                     * @param argumentHandler       A handler for accessing values on the local variable array.\n                     * @param methodSizeHandler     A handler for computing the method size requirements.\n                     * @param stackMapFrameHandler  A handler for translating and injecting stack map frames.\n                     * @param suppressionHandler    A bound suppression handler that is used for suppressing exceptions of this advice method.\n                     * @param relocationHandler     A bound relocation handler that is responsible for considering a non-standard control flow.\n                     * @param exceptionHandler      The exception handler that is resolved for the instrumented method.\n                     * @param delegator             The delegator to use.\n                     */\n                    protected AdviceMethodWriter(MethodDescription.InDefinedShape adviceMethod,\n                                                 TypeDescription instrumentedType,\n                                                 MethodDescription instrumentedMethod,\n                                                 Assigner assigner,\n                                                 PostProcessor postProcessor,\n                                                 List<OffsetMapping.Target> offsetMappings,\n                                                 MethodVisitor methodVisitor,\n                                                 Context implementationContext,\n                                                 ArgumentHandler.ForAdvice argumentHandler,\n                                                 MethodSizeHandler.ForAdvice methodSizeHandler,\n                                                 StackMapFrameHandler.ForAdvice stackMapFrameHandler,\n                                                 SuppressionHandler.Bound suppressionHandler,\n                                                 RelocationHandler.Bound relocationHandler,\n                                                 StackManipulation exceptionHandler,\n                                                 Delegator delegator) {\n                        this.adviceMethod = adviceMethod;\n                        this.instrumentedType = instrumentedType;\n                        this.instrumentedMethod = instrumentedMethod;\n                        this.assigner = assigner;\n                        this.postProcessor = postProcessor;\n                        this.offsetMappings = offsetMappings;\n                        this.methodVisitor = methodVisitor;\n                        this.implementationContext = implementationContext;\n                        this.argumentHandler = argumentHandler;\n                        this.methodSizeHandler = methodSizeHandler;\n                        this.stackMapFrameHandler = stackMapFrameHandler;\n                        this.suppressionHandler = suppressionHandler;\n                        this.relocationHandler = relocationHandler;\n                        this.exceptionHandler = exceptionHandler;\n                        this.delegator = delegator;\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public void prepare() {\n                        suppressionHandler.onPrepare(methodVisitor);\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public void apply() {\n                        suppressionHandler.onStart(methodVisitor);\n                        int index = 0, currentStackSize = 0, maximumStackSize = 0;\n                        for (OffsetMapping.Target offsetMapping : offsetMappings) {\n                            currentStackSize += adviceMethod.getParameters().get(index++).getType().getStackSize().getSize();\n                            maximumStackSize = Math.max(maximumStackSize, currentStackSize + offsetMapping.resolveRead()\n                                .apply(methodVisitor, implementationContext)\n                                .getMaximalSize());\n                        }\n                        delegator.apply(methodVisitor, adviceMethod, instrumentedType, instrumentedMethod, isExitAdvice());\n                        suppressionHandler.onEndWithSkip(methodVisitor,\n                            implementationContext,\n                            methodSizeHandler,\n                            stackMapFrameHandler,\n                            adviceMethod.getReturnType());\n                        if (adviceMethod.getReturnType().represents(boolean.class)\n                            || adviceMethod.getReturnType().represents(byte.class)\n                            || adviceMethod.getReturnType().represents(short.class)\n                            || adviceMethod.getReturnType().represents(char.class)\n                            || adviceMethod.getReturnType().represents(int.class)) {\n                            methodVisitor.visitVarInsn(Opcodes.ISTORE, isExitAdvice() ? argumentHandler.exit() : argumentHandler.enter());\n                        } else if (adviceMethod.getReturnType().represents(long.class)) {\n                            methodVisitor.visitVarInsn(Opcodes.LSTORE, isExitAdvice() ? argumentHandler.exit() : argumentHandler.enter());\n                        } else if (adviceMethod.getReturnType().represents(float.class)) {\n                            methodVisitor.visitVarInsn(Opcodes.FSTORE, isExitAdvice() ? argumentHandler.exit() : argumentHandler.enter());\n                        } else if (adviceMethod.getReturnType().represents(double.class)) {\n                            methodVisitor.visitVarInsn(Opcodes.DSTORE, isExitAdvice() ? argumentHandler.exit() : argumentHandler.enter());\n                        } else if (!adviceMethod.getReturnType().represents(void.class)) {\n                            methodVisitor.visitVarInsn(Opcodes.ASTORE, isExitAdvice() ? argumentHandler.exit() : argumentHandler.enter());\n                        }\n                        methodSizeHandler.requireStackSize(postProcessor.resolve(instrumentedType,\n                                instrumentedMethod, assigner, argumentHandler, stackMapFrameHandler, exceptionHandler)\n                            .apply(methodVisitor, implementationContext).getMaximalSize());\n                        methodSizeHandler.requireStackSize(relocationHandler.apply(methodVisitor, isExitAdvice() ? argumentHandler.exit() : argumentHandler.enter()));\n                        stackMapFrameHandler.injectCompletionFrame(methodVisitor);\n                        methodSizeHandler.requireStackSize(Math.max(maximumStackSize, adviceMethod.getReturnType().getStackSize().getSize()));\n                        methodSizeHandler.requireLocalVariableLength(instrumentedMethod.getStackSize() + adviceMethod.getReturnType().getStackSize().getSize());\n                    }\n\n                    /**\n                     * Returns {@code true} if this writer represents exit advice.\n                     *\n                     * @return {@code true} if this writer represents exit advice.\n                     */\n                    protected abstract boolean isExitAdvice();\n\n                    /**\n                     * An advice method writer for a method enter.\n                     */\n                    protected static class ForMethodEnter extends AdviceMethodWriter {\n\n                        /**\n                         * Creates a new advice method writer.\n                         *\n                         * @param adviceMethod          The advice method.\n                         * @param instrumentedType      The instrumented type.\n                         * @param instrumentedMethod    The instrumented method.\n                         * @param assigner              The assigner to use.\n                         * @param postProcessor         The post processor to apply.\n                         * @param offsetMappings        The offset mappings available to this advice.\n                         * @param methodVisitor         The method visitor for writing the instrumented method.\n                         * @param implementationContext The implementation context to use.\n                         * @param argumentHandler       A handler for accessing values on the local variable array.\n                         * @param methodSizeHandler     A handler for computing the method size requirements.\n                         * @param stackMapFrameHandler  A handler for translating and injecting stack map frames.\n                         * @param suppressionHandler    A bound suppression handler that is used for suppressing exceptions of this advice method.\n                         * @param relocationHandler     A bound relocation handler that is responsible for considering a non-standard control flow.\n                         * @param exceptionHandler      The exception handler that is resolved for the instrumented method.\n                         * @param delegator             The delegator to use.\n                         */\n                        protected ForMethodEnter(MethodDescription.InDefinedShape adviceMethod,\n                                                 TypeDescription instrumentedType,\n                                                 MethodDescription instrumentedMethod,\n                                                 Assigner assigner,\n                                                 PostProcessor postProcessor,\n                                                 List<OffsetMapping.Target> offsetMappings,\n                                                 MethodVisitor methodVisitor,\n                                                 Implementation.Context implementationContext,\n                                                 ArgumentHandler.ForAdvice argumentHandler,\n                                                 MethodSizeHandler.ForAdvice methodSizeHandler,\n                                                 StackMapFrameHandler.ForAdvice stackMapFrameHandler,\n                                                 SuppressionHandler.Bound suppressionHandler,\n                                                 RelocationHandler.Bound relocationHandler,\n                                                 StackManipulation exceptionHandler,\n                                                 Delegator delegator) {\n                            super(adviceMethod,\n                                instrumentedType,\n                                instrumentedMethod,\n                                assigner,\n                                postProcessor,\n                                offsetMappings,\n                                methodVisitor,\n                                implementationContext,\n                                argumentHandler,\n                                methodSizeHandler,\n                                stackMapFrameHandler,\n                                suppressionHandler,\n                                relocationHandler,\n                                exceptionHandler,\n                                delegator);\n                        }\n\n                        /**\n                         * {@inheritDoc}\n                         */\n                        public void initialize() {\n                            /* do nothing */\n                        }\n\n                        @Override\n                        protected boolean isExitAdvice() {\n                            return false;\n                        }\n                    }\n\n                    /**\n                     * An advice method writer for a method exit.\n                     */\n                    protected static class ForMethodExit extends AdviceMethodWriter {\n\n                        /**\n                         * Creates a new advice method writer.\n                         *\n                         * @param adviceMethod          The advice method.\n                         * @param instrumentedType      The instrumented type.\n                         * @param instrumentedMethod    The instrumented method.\n                         * @param assigner              The assigner to use.\n                         * @param postProcessor         The post processor to apply.\n                         * @param offsetMappings        The offset mappings available to this advice.\n                         * @param methodVisitor         The method visitor for writing the instrumented method.\n                         * @param implementationContext The implementation context to use.\n                         * @param argumentHandler       A handler for accessing values on the local variable array.\n                         * @param methodSizeHandler     A handler for computing the method size requirements.\n                         * @param stackMapFrameHandler  A handler for translating and injecting stack map frames.\n                         * @param suppressionHandler    A bound suppression handler that is used for suppressing exceptions of this advice method.\n                         * @param relocationHandler     A bound relocation handler that is responsible for considering a non-standard control flow.\n                         * @param exceptionHandler      The exception handler that is resolved for the instrumented method.\n                         * @param delegator             The delegator to use.\n                         */\n                        protected ForMethodExit(MethodDescription.InDefinedShape adviceMethod,\n                                                TypeDescription instrumentedType,\n                                                MethodDescription instrumentedMethod,\n                                                Assigner assigner,\n                                                PostProcessor postProcessor,\n                                                List<OffsetMapping.Target> offsetMappings,\n                                                MethodVisitor methodVisitor,\n                                                Implementation.Context implementationContext,\n                                                ArgumentHandler.ForAdvice argumentHandler,\n                                                MethodSizeHandler.ForAdvice methodSizeHandler,\n                                                StackMapFrameHandler.ForAdvice stackMapFrameHandler,\n                                                SuppressionHandler.Bound suppressionHandler,\n                                                RelocationHandler.Bound relocationHandler,\n                                                StackManipulation exceptionHandler,\n                                                Delegator delegator) {\n                            super(adviceMethod,\n                                instrumentedType,\n                                instrumentedMethod,\n                                assigner,\n                                postProcessor,\n                                offsetMappings,\n                                methodVisitor,\n                                implementationContext,\n                                argumentHandler,\n                                methodSizeHandler,\n                                stackMapFrameHandler,\n                                suppressionHandler,\n                                relocationHandler,\n                                exceptionHandler,\n                                delegator);\n                        }\n\n                        /**\n                         * {@inheritDoc}\n                         */\n                        public void initialize() {\n                            if (adviceMethod.getReturnType().represents(boolean.class)\n                                || adviceMethod.getReturnType().represents(byte.class)\n                                || adviceMethod.getReturnType().represents(short.class)\n                                || adviceMethod.getReturnType().represents(char.class)\n                                || adviceMethod.getReturnType().represents(int.class)) {\n                                methodVisitor.visitInsn(Opcodes.ICONST_0);\n                                methodVisitor.visitVarInsn(Opcodes.ISTORE, argumentHandler.exit());\n                            } else if (adviceMethod.getReturnType().represents(long.class)) {\n                                methodVisitor.visitInsn(Opcodes.LCONST_0);\n                                methodVisitor.visitVarInsn(Opcodes.LSTORE, argumentHandler.exit());\n                            } else if (adviceMethod.getReturnType().represents(float.class)) {\n                                methodVisitor.visitInsn(Opcodes.FCONST_0);\n                                methodVisitor.visitVarInsn(Opcodes.FSTORE, argumentHandler.exit());\n                            } else if (adviceMethod.getReturnType().represents(double.class)) {\n                                methodVisitor.visitInsn(Opcodes.DCONST_0);\n                                methodVisitor.visitVarInsn(Opcodes.DSTORE, argumentHandler.exit());\n                            } else if (!adviceMethod.getReturnType().represents(void.class)) {\n                                methodVisitor.visitInsn(Opcodes.ACONST_NULL);\n                                methodVisitor.visitVarInsn(Opcodes.ASTORE, argumentHandler.exit());\n                            }\n                            methodSizeHandler.requireStackSize(adviceMethod.getReturnType().getStackSize().getSize());\n                        }\n\n                        @Override\n                        protected boolean isExitAdvice() {\n                            return true;\n                        }\n                    }\n                }\n\n                /**\n                 * A resolved dispatcher for implementing method enter advice.\n                 */\n                @HashCodeAndEqualsPlugin.Enhance\n                protected abstract static class ForMethodEnter extends Delegating.Resolved implements Dispatcher.Resolved.ForMethodEnter {\n\n                    /**\n                     * {@code true} if the first discovered line number information should be prepended to the advice code.\n                     */\n                    private final boolean prependLineNumber;\n\n                    /**\n                     * Creates a new resolved dispatcher for implementing method enter advice.\n                     *\n                     * @param adviceMethod  The represented advice method.\n                     * @param postProcessor The post processor to apply.\n                     * @param userFactories A list of user-defined factories for offset mappings.\n                     * @param exitType      The exit type or {@code void} if no exit type is defined.\n                     * @param delegator     The delegator to use.\n                     */\n                    protected ForMethodEnter(MethodDescription.InDefinedShape adviceMethod,\n                                             PostProcessor postProcessor,\n                                             List<? extends OffsetMapping.Factory<?>> userFactories,\n                                             TypeDefinition exitType,\n                                             Delegator delegator) {\n                        super(adviceMethod,\n                            postProcessor,\n                            CompoundList.of(Arrays.asList(OffsetMapping.ForArgument.Unresolved.Factory.INSTANCE,\n                                OffsetMapping.ForAllArguments.Factory.INSTANCE,\n                                OffsetMapping.ForThisReference.Factory.INSTANCE,\n                                OffsetMapping.ForField.Unresolved.Factory.INSTANCE,\n                                OffsetMapping.ForOrigin.Factory.INSTANCE,\n                                OffsetMapping.ForUnusedValue.Factory.INSTANCE,\n                                OffsetMapping.ForStubValue.INSTANCE,\n                                OffsetMapping.ForExitValue.Factory.of(exitType),\n                                new OffsetMapping.Factory.Illegal<>(Thrown.class),\n                                new OffsetMapping.Factory.Illegal<>(Enter.class),\n                                new OffsetMapping.Factory.Illegal<>(Local.class),\n                                new OffsetMapping.Factory.Illegal<>(Return.class)), userFactories),\n                            adviceMethod.getDeclaredAnnotations().ofType(OnMethodEnter.class).getValue(SUPPRESS_ENTER).resolve(TypeDescription.class),\n                            adviceMethod.getDeclaredAnnotations().ofType(OnMethodEnter.class).getValue(SKIP_ON).resolve(TypeDescription.class),\n                            delegator);\n                        prependLineNumber = adviceMethod.getDeclaredAnnotations().ofType(OnMethodEnter.class).getValue(PREPEND_LINE_NUMBER).resolve(Boolean.class);\n                    }\n\n                    /**\n                     * Resolves enter advice that only exposes the enter type if this is necessary.\n                     *\n                     * @param adviceMethod  The advice method.\n                     * @param postProcessor The post processor to apply.\n                     * @param delegator     The delegator to use.\n                     * @param userFactories A list of user-defined factories for offset mappings.\n                     * @param exitType      The exit type or {@code void} if no exit type is defined.\n                     * @param methodExit    {@code true} if exit advice is applied.\n                     * @return An appropriate enter handler.\n                     */\n                    protected static Resolved.ForMethodEnter of(MethodDescription.InDefinedShape adviceMethod,\n                                                                PostProcessor postProcessor,\n                                                                Delegator delegator,\n                                                                List<? extends OffsetMapping.Factory<?>> userFactories,\n                                                                TypeDefinition exitType,\n                                                                boolean methodExit) {\n                        return methodExit\n                            ? new WithRetainedEnterType(adviceMethod, postProcessor, userFactories, exitType, delegator)\n                            : new WithDiscardedEnterType(adviceMethod, postProcessor, userFactories, exitType, delegator);\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public boolean isPrependLineNumber() {\n                        return prependLineNumber;\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public TypeDefinition getActualAdviceType() {\n                        return adviceMethod.getReturnType();\n                    }\n\n                    @Override\n                    protected Bound resolve(TypeDescription instrumentedType,\n                                            MethodDescription instrumentedMethod,\n                                            MethodVisitor methodVisitor,\n                                            Implementation.Context implementationContext,\n                                            Assigner assigner,\n                                            ArgumentHandler.ForInstrumentedMethod argumentHandler,\n                                            MethodSizeHandler.ForInstrumentedMethod methodSizeHandler,\n                                            StackMapFrameHandler.ForInstrumentedMethod stackMapFrameHandler,\n                                            StackManipulation exceptionHandler,\n                                            RelocationHandler.Relocation relocation) {\n                        return doResolve(instrumentedType,\n                            instrumentedMethod,\n                            methodVisitor,\n                            implementationContext,\n                            assigner,\n                            argumentHandler.bindEnter(adviceMethod),\n                            methodSizeHandler.bindEnter(adviceMethod),\n                            stackMapFrameHandler.bindEnter(adviceMethod),\n                            suppressionHandler.bind(exceptionHandler),\n                            relocationHandler.bind(instrumentedMethod, relocation),\n                            exceptionHandler);\n                    }\n\n                    /**\n                     * Binds this dispatcher for resolution to a specific method.\n                     *\n                     * @param instrumentedType      A description of the instrumented type.\n                     * @param instrumentedMethod    The instrumented method that is being bound.\n                     * @param methodVisitor         The method visitor for writing to the instrumented method.\n                     * @param implementationContext The implementation context to use.\n                     * @param assigner              The assigner to use.\n                     * @param argumentHandler       A handler for accessing values on the local variable array.\n                     * @param methodSizeHandler     A handler for computing the method size requirements.\n                     * @param stackMapFrameHandler  A handler for translating and injecting stack map frames.\n                     * @param suppressionHandler    The bound suppression handler to use.\n                     * @param relocationHandler     The bound relocation handler to use.\n                     * @param exceptionHandler      The exception handler that is resolved for the instrumented method.\n                     * @return An appropriate bound advice dispatcher.\n                     */\n                    protected Bound doResolve(TypeDescription instrumentedType,\n                                              MethodDescription instrumentedMethod,\n                                              MethodVisitor methodVisitor,\n                                              Implementation.Context implementationContext,\n                                              Assigner assigner,\n                                              ArgumentHandler.ForAdvice argumentHandler,\n                                              MethodSizeHandler.ForAdvice methodSizeHandler,\n                                              StackMapFrameHandler.ForAdvice stackMapFrameHandler,\n                                              SuppressionHandler.Bound suppressionHandler,\n                                              RelocationHandler.Bound relocationHandler,\n                                              StackManipulation exceptionHandler) {\n                        List<OffsetMapping.Target> offsetMappings = new ArrayList<>(this.offsetMappings.size());\n                        for (OffsetMapping offsetMapping : this.offsetMappings.values()) {\n                            offsetMappings.add(offsetMapping.resolve(instrumentedType,\n                                instrumentedMethod,\n                                assigner,\n                                argumentHandler,\n                                OffsetMapping.Sort.ENTER));\n                        }\n                        return new AdviceMethodWriter.ForMethodEnter(adviceMethod,\n                            instrumentedType,\n                            instrumentedMethod,\n                            assigner,\n                            postProcessor,\n                            offsetMappings,\n                            methodVisitor,\n                            implementationContext,\n                            argumentHandler,\n                            methodSizeHandler,\n                            stackMapFrameHandler,\n                            suppressionHandler,\n                            relocationHandler,\n                            exceptionHandler,\n                            delegator);\n                    }\n\n                    /**\n                     * Implementation of an advice that does expose an enter type.\n                     */\n                    protected static class WithRetainedEnterType extends Delegating.Resolved.ForMethodEnter {\n\n                        /**\n                         * Creates a new resolved dispatcher for implementing method enter advice that does expose the enter type.\n                         *\n                         * @param adviceMethod  The represented advice method.\n                         * @param postProcessor The post processor to apply.\n                         * @param userFactories A list of user-defined factories for offset mappings.\n                         * @param exitType      The exit type or {@code void} if no exit type is defined.\n                         * @param delegator     The delegator to use.\n                         */\n                        protected WithRetainedEnterType(MethodDescription.InDefinedShape adviceMethod,\n                                                        PostProcessor postProcessor,\n                                                        List<? extends OffsetMapping.Factory<?>> userFactories,\n                                                        TypeDefinition exitType,\n                                                        Delegator delegator) {\n                            super(adviceMethod, postProcessor, userFactories, exitType, delegator);\n                        }\n\n                        /**\n                         * {@inheritDoc}\n                         */\n                        public TypeDefinition getAdviceType() {\n                            return adviceMethod.getReturnType();\n                        }\n                    }\n\n                    /**\n                     * Implementation of an advice that does not expose an enter type.\n                     */\n                    protected static class WithDiscardedEnterType extends Delegating.Resolved.ForMethodEnter {\n\n                        /**\n                         * Creates a new resolved dispatcher for implementing method enter advice that does not expose the enter type.\n                         *\n                         * @param adviceMethod  The represented advice method.\n                         * @param postProcessor The post processor to apply.\n                         * @param userFactories A list of user-defined factories for offset mappings.\n                         * @param exitType      The exit type or {@code void} if no exit type is defined.\n                         * @param delegator     The delegator to use.\n                         */\n                        protected WithDiscardedEnterType(MethodDescription.InDefinedShape adviceMethod,\n                                                         PostProcessor postProcessor,\n                                                         List<? extends OffsetMapping.Factory<?>> userFactories,\n                                                         TypeDefinition exitType,\n                                                         Delegator delegator) {\n                            super(adviceMethod, postProcessor, userFactories, exitType, delegator);\n                        }\n\n                        /**\n                         * {@inheritDoc}\n                         */\n                        public TypeDefinition getAdviceType() {\n                            return TypeDescription.VOID;\n                        }\n\n                        /**\n                         * {@inheritDoc}\n                         */\n                        @Override\n                        protected Bound doResolve(TypeDescription instrumentedType,\n                                                  MethodDescription instrumentedMethod,\n                                                  MethodVisitor methodVisitor,\n                                                  Context implementationContext,\n                                                  Assigner assigner,\n                                                  ArgumentHandler.ForAdvice argumentHandler,\n                                                  MethodSizeHandler.ForAdvice methodSizeHandler,\n                                                  StackMapFrameHandler.ForAdvice stackMapFrameHandler,\n                                                  SuppressionHandler.Bound suppressionHandler,\n                                                  RelocationHandler.Bound relocationHandler,\n                                                  StackManipulation exceptionHandler) {\n                            methodSizeHandler.requireLocalVariableLengthPadding(adviceMethod.getReturnType().getStackSize().getSize());\n                            return super.doResolve(instrumentedType,\n                                instrumentedMethod,\n                                methodVisitor,\n                                implementationContext,\n                                assigner,\n                                argumentHandler,\n                                methodSizeHandler,\n                                stackMapFrameHandler,\n                                suppressionHandler,\n                                relocationHandler,\n                                exceptionHandler);\n                        }\n                    }\n                }\n\n                /**\n                 * A resolved dispatcher for implementing method exit advice.\n                 */\n                @HashCodeAndEqualsPlugin.Enhance\n                protected abstract static class ForMethodExit extends Delegating.Resolved implements Dispatcher.Resolved.ForMethodExit {\n\n                    /**\n                     * {@code true} if the arguments of the instrumented method should be copied prior to execution.\n                     */\n                    private final boolean backupArguments;\n\n                    /**\n                     * Creates a new resolved dispatcher for implementing method exit advice.\n                     *\n                     * @param adviceMethod  The represented advice method.\n                     * @param postProcessor The post processor to apply.\n                     * @param namedTypes    A mapping of all available local variables by their name to their type.\n                     * @param userFactories A list of user-defined factories for offset mappings.\n                     * @param enterType     The type of the value supplied by the enter advice method or {@code void} if no such value exists.\n                     * @param delegator     The delegator to use.\n                     */\n                    protected ForMethodExit(MethodDescription.InDefinedShape adviceMethod,\n                                            PostProcessor postProcessor,\n                                            Map<String, TypeDefinition> namedTypes,\n                                            List<? extends OffsetMapping.Factory<?>> userFactories,\n                                            TypeDefinition enterType,\n                                            Delegator delegator) {\n                        super(adviceMethod,\n                            postProcessor,\n                            CompoundList.of(Arrays.asList(OffsetMapping.ForArgument.Unresolved.Factory.INSTANCE,\n                                OffsetMapping.ForAllArguments.Factory.INSTANCE,\n                                OffsetMapping.ForThisReference.Factory.INSTANCE,\n                                OffsetMapping.ForField.Unresolved.Factory.INSTANCE,\n                                OffsetMapping.ForOrigin.Factory.INSTANCE,\n                                OffsetMapping.ForUnusedValue.Factory.INSTANCE,\n                                OffsetMapping.ForStubValue.INSTANCE,\n                                OffsetMapping.ForEnterValue.Factory.of(enterType),\n                                OffsetMapping.ForExitValue.Factory.of(adviceMethod.getReturnType()),\n                                new OffsetMapping.ForLocalValue.Factory(namedTypes),\n                                OffsetMapping.ForReturnValue.Factory.INSTANCE,\n                                OffsetMapping.ForThrowable.Factory.of(adviceMethod)\n                            ), userFactories),\n                            adviceMethod.getDeclaredAnnotations().ofType(OnMethodExit.class).getValue(SUPPRESS_EXIT).resolve(TypeDescription.class),\n                            adviceMethod.getDeclaredAnnotations().ofType(OnMethodExit.class).getValue(REPEAT_ON).resolve(TypeDescription.class),\n                            delegator);\n                        backupArguments = adviceMethod.getDeclaredAnnotations().ofType(OnMethodExit.class).getValue(BACKUP_ARGUMENTS).resolve(Boolean.class);\n                    }\n\n                    /**\n                     * Resolves exit advice that handles exceptions depending on the specification of the exit advice.\n                     *\n                     * @param adviceMethod  The advice method.\n                     * @param postProcessor The post processor to apply.\n                     * @param delegator     The delegator to use.\n                     * @param namedTypes    A mapping of all available local variables by their name to their type.\n                     * @param userFactories A list of user-defined factories for offset mappings.\n                     * @param enterType     The type of the value supplied by the enter advice method or {@code void} if no such value exists.\n                     * @return An appropriate exit handler.\n                     */\n                    protected static Resolved.ForMethodExit of(MethodDescription.InDefinedShape adviceMethod,\n                                                               PostProcessor postProcessor,\n                                                               Delegator delegator,\n                                                               Map<String, TypeDefinition> namedTypes,\n                                                               List<? extends OffsetMapping.Factory<?>> userFactories,\n                                                               TypeDefinition enterType) {\n                        TypeDescription throwable = adviceMethod.getDeclaredAnnotations()\n                            .ofType(OnMethodExit.class)\n                            .getValue(ON_THROWABLE)\n                            .resolve(TypeDescription.class);\n                        return isNoExceptionHandler(throwable)\n                            ? new WithoutExceptionHandler(adviceMethod, postProcessor, namedTypes, userFactories, enterType, delegator)\n                            : new WithExceptionHandler(adviceMethod, postProcessor, namedTypes, userFactories, enterType, throwable, delegator);\n                    }\n\n                    @Override\n                    protected Bound resolve(TypeDescription instrumentedType,\n                                            MethodDescription instrumentedMethod,\n                                            MethodVisitor methodVisitor,\n                                            Implementation.Context implementationContext,\n                                            Assigner assigner,\n                                            ArgumentHandler.ForInstrumentedMethod argumentHandler,\n                                            MethodSizeHandler.ForInstrumentedMethod methodSizeHandler,\n                                            StackMapFrameHandler.ForInstrumentedMethod stackMapFrameHandler,\n                                            StackManipulation exceptionHandler,\n                                            RelocationHandler.Relocation relocation) {\n                        return doResolve(instrumentedType,\n                            instrumentedMethod,\n                            methodVisitor,\n                            implementationContext,\n                            assigner,\n                            argumentHandler.bindExit(adviceMethod, isNoExceptionHandler(getThrowable())),\n                            methodSizeHandler.bindExit(adviceMethod),\n                            stackMapFrameHandler.bindExit(adviceMethod),\n                            suppressionHandler.bind(exceptionHandler),\n                            relocationHandler.bind(instrumentedMethod, relocation),\n                            exceptionHandler);\n                    }\n\n                    /**\n                     * Binds this dispatcher for resolution to a specific method.\n                     *\n                     * @param instrumentedType      A description of the instrumented type.\n                     * @param instrumentedMethod    The instrumented method that is being bound.\n                     * @param methodVisitor         The method visitor for writing to the instrumented method.\n                     * @param implementationContext The implementation context to use.\n                     * @param assigner              The assigner to use.\n                     * @param argumentHandler       A handler for accessing values on the local variable array.\n                     * @param methodSizeHandler     A handler for computing the method size requirements.\n                     * @param stackMapFrameHandler  A handler for translating and injecting stack map frames.\n                     * @param suppressionHandler    The bound suppression handler to use.\n                     * @param relocationHandler     The bound relocation handler to use.\n                     * @param exceptionHandler      The exception handler that is resolved for the instrumented method.\n                     * @return An appropriate bound advice dispatcher.\n                     */\n                    private Bound doResolve(TypeDescription instrumentedType,\n                                            MethodDescription instrumentedMethod,\n                                            MethodVisitor methodVisitor,\n                                            Implementation.Context implementationContext,\n                                            Assigner assigner,\n                                            ArgumentHandler.ForAdvice argumentHandler,\n                                            MethodSizeHandler.ForAdvice methodSizeHandler,\n                                            StackMapFrameHandler.ForAdvice stackMapFrameHandler,\n                                            SuppressionHandler.Bound suppressionHandler,\n                                            RelocationHandler.Bound relocationHandler,\n                                            StackManipulation exceptionHandler) {\n                        List<OffsetMapping.Target> offsetMappings = new ArrayList<>(this.offsetMappings.size());\n                        for (OffsetMapping offsetMapping : this.offsetMappings.values()) {\n                            offsetMappings.add(offsetMapping.resolve(instrumentedType,\n                                instrumentedMethod,\n                                assigner,\n                                argumentHandler,\n                                OffsetMapping.Sort.EXIT));\n                        }\n                        return new AdviceMethodWriter.ForMethodExit(adviceMethod,\n                            instrumentedType,\n                            instrumentedMethod,\n                            assigner,\n                            postProcessor,\n                            offsetMappings,\n                            methodVisitor,\n                            implementationContext,\n                            argumentHandler,\n                            methodSizeHandler,\n                            stackMapFrameHandler,\n                            suppressionHandler,\n                            relocationHandler,\n                            exceptionHandler,\n                            delegator);\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public ArgumentHandler.Factory getArgumentHandlerFactory() {\n                        return backupArguments\n                            ? ArgumentHandler.Factory.COPYING\n                            : ArgumentHandler.Factory.SIMPLE;\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public TypeDefinition getAdviceType() {\n                        return adviceMethod.getReturnType();\n                    }\n\n                    /**\n                     * Implementation of exit advice that handles exceptions.\n                     */\n                    @HashCodeAndEqualsPlugin.Enhance\n                    protected static class WithExceptionHandler extends Delegating.Resolved.ForMethodExit {\n\n                        /**\n                         * The type of the handled throwable type for which this advice is invoked.\n                         */\n                        private final TypeDescription throwable;\n\n                        /**\n                         * Creates a new resolved dispatcher for implementing method exit advice that handles exceptions.\n                         *\n                         * @param adviceMethod  The represented advice method.\n                         * @param postProcessor The post processor factory to apply.\n                         * @param namedTypes    A mapping of all available local variables by their name to their type.\n                         * @param userFactories A list of user-defined factories for offset mappings.\n                         * @param enterType     The type of the value supplied by the enter advice method or\n                         *                      a description of {@code void} if no such value exists.\n                         * @param throwable     The type of the handled throwable type for which this advice is invoked.\n                         * @param delegator     The delegator to use.\n                         */\n                        protected WithExceptionHandler(MethodDescription.InDefinedShape adviceMethod,\n                                                       PostProcessor postProcessor,\n                                                       Map<String, TypeDefinition> namedTypes,\n                                                       List<? extends OffsetMapping.Factory<?>> userFactories,\n                                                       TypeDefinition enterType,\n                                                       TypeDescription throwable,\n                                                       Delegator delegator) {\n                            super(adviceMethod, postProcessor, namedTypes, userFactories, enterType, delegator);\n                            this.throwable = throwable;\n                        }\n\n                        /**\n                         * {@inheritDoc}\n                         */\n                        public TypeDescription getThrowable() {\n                            return throwable;\n                        }\n                    }\n\n                    /**\n                     * Implementation of exit advice that ignores exceptions.\n                     */\n                    protected static class WithoutExceptionHandler extends Delegating.Resolved.ForMethodExit {\n\n                        /**\n                         * Creates a new resolved dispatcher for implementing method exit advice that does not handle exceptions.\n                         *\n                         * @param adviceMethod  The represented advice method.\n                         * @param postProcessor The post processor factory to apply.\n                         * @param namedTypes    A mapping of all available local variables by their name to their type.\n                         * @param userFactories A list of user-defined factories for offset mappings.\n                         * @param enterType     The type of the value supplied by the enter advice method or\n                         *                      a description of {@code void} if no such value exists.\n                         * @param delegator     The delegator to use.\n                         */\n                        protected WithoutExceptionHandler(MethodDescription.InDefinedShape adviceMethod,\n                                                          PostProcessor postProcessor,\n                                                          Map<String, TypeDefinition> namedTypes,\n                                                          List<? extends OffsetMapping.Factory<?>> userFactories,\n                                                          TypeDefinition enterType,\n                                                          Delegator delegator) {\n                            super(adviceMethod, postProcessor, namedTypes, userFactories, enterType, delegator);\n                        }\n\n                        /**\n                         * {@inheritDoc}\n                         */\n                        public TypeDescription getThrowable() {\n                            return NoExceptionHandler.DESCRIPTION;\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n\n    /**\n     * A handler for computing the instrumented method's size.\n     */\n    protected interface MethodSizeHandler {\n\n        /**\n         * Indicates that a size is not computed but handled directly by ASM.\n         */\n        int UNDEFINED_SIZE = Short.MAX_VALUE;\n\n        /**\n         * Records a minimum stack size required by the represented advice method.\n         *\n         * @param stackSize The minimum size required by the represented advice method.\n         */\n        void requireStackSize(int stackSize);\n\n        /**\n         * Requires a minimum length of the local variable array.\n         *\n         * @param localVariableLength The minimal required length of the local variable array.\n         */\n        void requireLocalVariableLength(int localVariableLength);\n\n        /**\n         * A method size handler for the instrumented method.\n         */\n        interface ForInstrumentedMethod extends MethodSizeHandler {\n\n            /**\n             * Binds a method size handler for the enter advice.\n             *\n             * @param adviceMethod The method representing the enter advice.\n             * @return A method size handler for the enter advice.\n             */\n            ForAdvice bindEnter(MethodDescription.InDefinedShape adviceMethod);\n\n            /**\n             * Binds the method size handler for the exit advice.\n             *\n             * @param adviceMethod The method representing the exit advice.\n             * @return A method size handler for the exit advice.\n             */\n            ForAdvice bindExit(MethodDescription.InDefinedShape adviceMethod);\n\n            /**\n             * Computes a compound stack size for the advice and the translated instrumented method.\n             *\n             * @param stackSize The required stack size of the instrumented method before translation.\n             * @return The stack size required by the instrumented method and its advice methods.\n             */\n            int compoundStackSize(int stackSize);\n\n            /**\n             * Computes a compound local variable array length for the advice and the translated instrumented method.\n             *\n             * @param localVariableLength The required local variable array length of the instrumented method before translation.\n             * @return The local variable length required by the instrumented method and its advice methods.\n             */\n            int compoundLocalVariableLength(int localVariableLength);\n        }\n\n        /**\n         * A method size handler for an advice method.\n         */\n        interface ForAdvice extends MethodSizeHandler {\n\n            /**\n             * Requires additional padding for the operand stack that is required for this advice's execution.\n             *\n             * @param stackSizePadding The required padding.\n             */\n            void requireStackSizePadding(int stackSizePadding);\n\n            /**\n             * Requires additional padding for the local variable array that is required for this advice's execution.\n             *\n             * @param localVariableLengthPadding The required padding.\n             */\n            void requireLocalVariableLengthPadding(int localVariableLengthPadding);\n\n            /**\n             * Records the maximum values for stack size and local variable array which are required by the advice method\n             * for its individual execution without translation.\n             *\n             * @param stackSize           The minimum required stack size.\n             * @param localVariableLength The minimum required length of the local variable array.\n             */\n            void recordMaxima(int stackSize, int localVariableLength);\n        }\n\n        /**\n         * A non-operational method size handler.\n         */\n        enum NoOp implements ForInstrumentedMethod, ForAdvice {\n\n            /**\n             * The singleton instance.\n             */\n            INSTANCE;\n\n            /**\n             * {@inheritDoc}\n             */\n            public ForAdvice bindEnter(MethodDescription.InDefinedShape adviceMethod) {\n                return this;\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public ForAdvice bindExit(MethodDescription.InDefinedShape adviceMethod) {\n                return this;\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public int compoundStackSize(int stackSize) {\n                return UNDEFINED_SIZE;\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public int compoundLocalVariableLength(int localVariableLength) {\n                return UNDEFINED_SIZE;\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public void requireStackSize(int stackSize) {\n                /* do nothing */\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public void requireLocalVariableLength(int localVariableLength) {\n                /* do nothing */\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public void requireStackSizePadding(int stackSizePadding) {\n                /* do nothing */\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public void requireLocalVariableLengthPadding(int localVariableLengthPadding) {\n                /* do nothing */\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public void recordMaxima(int stackSize, int localVariableLength) {\n                /* do nothing */\n            }\n        }\n\n        /**\n         * A default implementation for a method size handler.\n         */\n        abstract class Default implements MethodSizeHandler.ForInstrumentedMethod {\n\n            /**\n             * The instrumented method.\n             */\n            protected final MethodDescription instrumentedMethod;\n\n            /**\n             * A list of virtual method arguments that are explicitly added before any code execution.\n             */\n            protected final List<? extends TypeDescription> initialTypes;\n\n            /**\n             * A list of virtual method arguments that are available before the instrumented method is executed.\n             */\n            protected final List<? extends TypeDescription> preMethodTypes;\n\n            /**\n             * A list of virtual method arguments that are available after the instrumented method has completed.\n             */\n            protected final List<? extends TypeDescription> postMethodTypes;\n\n            /**\n             * The maximum stack size required by a visited advice method.\n             */\n            protected int stackSize;\n\n            /**\n             * The maximum length of the local variable array required by a visited advice method.\n             */\n            protected int localVariableLength;\n\n            /**\n             * Creates a new default meta data handler that recomputes the space requirements of an instrumented method.\n             *\n             * @param instrumentedMethod The instrumented method.\n             * @param initialTypes       A list of virtual method arguments that are explicitly added before any code execution.\n             * @param preMethodTypes     A list of virtual method arguments that are available before the instrumented method is executed.\n             * @param postMethodTypes    A list of virtual method arguments that are available after the instrumented method has completed.\n             */\n            protected Default(MethodDescription instrumentedMethod,\n                              List<? extends TypeDescription> initialTypes,\n                              List<? extends TypeDescription> preMethodTypes,\n                              List<? extends TypeDescription> postMethodTypes) {\n                this.instrumentedMethod = instrumentedMethod;\n                this.initialTypes = initialTypes;\n                this.preMethodTypes = preMethodTypes;\n                this.postMethodTypes = postMethodTypes;\n            }\n\n            /**\n             * Creates a method size handler applicable for the given instrumented method.\n             *\n             * @param instrumentedMethod The instrumented method.\n             * @param initialTypes       A list of virtual method arguments that are explicitly added before any code execution.\n             * @param preMethodTypes     A list of virtual method arguments that are available before the instrumented method is executed.\n             * @param postMethodTypes    A list of virtual method arguments that are available after the instrumented method has completed.\n             * @param copyArguments      {@code true} if the original arguments are copied before invoking the instrumented method.\n             * @param writerFlags        The flags supplied to the ASM class writer.\n             * @return An appropriate method size handler.\n             */\n            protected static MethodSizeHandler.ForInstrumentedMethod of(MethodDescription instrumentedMethod,\n                                                                        List<? extends TypeDescription> initialTypes,\n                                                                        List<? extends TypeDescription> preMethodTypes,\n                                                                        List<? extends TypeDescription> postMethodTypes,\n                                                                        boolean copyArguments,\n                                                                        int writerFlags) {\n                if ((writerFlags & (ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES)) != 0) {\n                    return NoOp.INSTANCE;\n                } else if (copyArguments) {\n                    return new WithCopiedArguments(instrumentedMethod, initialTypes, preMethodTypes, postMethodTypes);\n                } else {\n                    return new WithRetainedArguments(instrumentedMethod, initialTypes, preMethodTypes, postMethodTypes);\n                }\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public MethodSizeHandler.ForAdvice bindEnter(MethodDescription.InDefinedShape adviceMethod) {\n                return new ForAdvice(adviceMethod, instrumentedMethod.getStackSize() + StackSize.of(initialTypes));\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public void requireStackSize(int stackSize) {\n                Default.this.stackSize = Math.max(this.stackSize, stackSize);\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public void requireLocalVariableLength(int localVariableLength) {\n                this.localVariableLength = Math.max(this.localVariableLength, localVariableLength);\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public int compoundStackSize(int stackSize) {\n                return Math.max(this.stackSize, stackSize);\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public int compoundLocalVariableLength(int localVariableLength) {\n                return Math.max(this.localVariableLength, localVariableLength\n                    + StackSize.of(postMethodTypes)\n                    + StackSize.of(initialTypes)\n                    + StackSize.of(preMethodTypes));\n            }\n\n            /**\n             * A method size handler that expects that the original arguments are retained.\n             */\n            protected static class WithRetainedArguments extends Default {\n\n                /**\n                 * Creates a new default method size handler that expects that the original arguments are retained.\n                 *\n                 * @param instrumentedMethod The instrumented method.\n                 * @param initialTypes       A list of virtual method arguments that are explicitly added before any code execution.\n                 * @param preMethodTypes     A list of virtual method arguments that are available before the instrumented method is executed.\n                 * @param postMethodTypes    A list of virtual method arguments that are available after the instrumented method has completed.\n                 */\n                protected WithRetainedArguments(MethodDescription instrumentedMethod,\n                                                List<? extends TypeDescription> initialTypes,\n                                                List<? extends TypeDescription> preMethodTypes,\n                                                List<? extends TypeDescription> postMethodTypes) {\n                    super(instrumentedMethod, initialTypes, preMethodTypes, postMethodTypes);\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public MethodSizeHandler.ForAdvice bindExit(MethodDescription.InDefinedShape adviceMethod) {\n                    return new ForAdvice(adviceMethod, instrumentedMethod.getStackSize()\n                        + StackSize.of(postMethodTypes)\n                        + StackSize.of(initialTypes)\n                        + StackSize.of(preMethodTypes));\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                @Override\n                public int compoundLocalVariableLength(int localVariableLength) {\n                    return Math.max(this.localVariableLength, localVariableLength\n                        + StackSize.of(postMethodTypes)\n                        + StackSize.of(initialTypes)\n                        + StackSize.of(preMethodTypes));\n                }\n            }\n\n            /**\n             * A method size handler that expects that the original arguments were copied.\n             */\n            protected static class WithCopiedArguments extends Default {\n\n                /**\n                 * Creates a new default method size handler that expects the original arguments to be copied.\n                 *\n                 * @param instrumentedMethod The instrumented method.\n                 * @param initialTypes       A list of virtual method arguments that are explicitly added before any code execution.\n                 * @param preMethodTypes     A list of virtual method arguments that are available before the instrumented method is executed.\n                 * @param postMethodTypes    A list of virtual method arguments that are available after the instrumented method has completed.\n                 */\n                protected WithCopiedArguments(MethodDescription instrumentedMethod,\n                                              List<? extends TypeDescription> initialTypes,\n                                              List<? extends TypeDescription> preMethodTypes,\n                                              List<? extends TypeDescription> postMethodTypes) {\n                    super(instrumentedMethod, initialTypes, preMethodTypes, postMethodTypes);\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                @Override\n                public MethodSizeHandler.ForAdvice bindExit(MethodDescription.InDefinedShape adviceMethod) {\n                    return new ForAdvice(adviceMethod, 2 * instrumentedMethod.getStackSize()\n                        + StackSize.of(initialTypes)\n                        + StackSize.of(preMethodTypes)\n                        + StackSize.of(postMethodTypes));\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                @Override\n                public int compoundLocalVariableLength(int localVariableLength) {\n                    return Math.max(this.localVariableLength, localVariableLength\n                        + instrumentedMethod.getStackSize()\n                        + StackSize.of(postMethodTypes)\n                        + StackSize.of(initialTypes)\n                        + StackSize.of(preMethodTypes));\n                }\n            }\n\n            /**\n             * A method size handler for an advice method.\n             */\n            protected class ForAdvice implements MethodSizeHandler.ForAdvice {\n\n                /**\n                 * The advice method.\n                 */\n                private final MethodDescription.InDefinedShape adviceMethod;\n\n                /**\n                 * The base of the local variable length that is implied by the method instrumentation prior to applying this advice method.\n                 */\n                private final int baseLocalVariableLength;\n\n                /**\n                 * The additional padding to apply to the operand stack.\n                 */\n                private int stackSizePadding;\n\n                /**\n                 * The additional padding to apply to the local variable array.\n                 */\n                private int localVariableLengthPadding;\n\n                /**\n                 * Creates a default method size handler for an advice method.\n                 *\n                 * @param adviceMethod            The advice method.\n                 * @param baseLocalVariableLength The base of the local variable length that is implied by the method instrumentation\n                 *                                prior to applying this advice method.\n                 */\n                protected ForAdvice(MethodDescription.InDefinedShape adviceMethod, int baseLocalVariableLength) {\n                    this.adviceMethod = adviceMethod;\n                    this.baseLocalVariableLength = baseLocalVariableLength;\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public void requireStackSize(int stackSize) {\n                    Default.this.requireStackSize(stackSize);\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public void requireLocalVariableLength(int localVariableLength) {\n                    Default.this.requireLocalVariableLength(localVariableLength);\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public void requireStackSizePadding(int stackSizePadding) {\n                    this.stackSizePadding = Math.max(this.stackSizePadding, stackSizePadding);\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public void requireLocalVariableLengthPadding(int localVariableLengthPadding) {\n                    this.localVariableLengthPadding = Math.max(this.localVariableLengthPadding, localVariableLengthPadding);\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public void recordMaxima(int stackSize, int localVariableLength) {\n                    Default.this.requireStackSize(stackSize + stackSizePadding);\n                    Default.this.requireLocalVariableLength(localVariableLength\n                        - adviceMethod.getStackSize()\n                        + baseLocalVariableLength\n                        + localVariableLengthPadding);\n                }\n            }\n        }\n    }\n\n    /**\n     * A handler for computing and translating stack map frames.\n     */\n    public interface StackMapFrameHandler {\n\n        /**\n         * Translates a frame.\n         *\n         * @param methodVisitor       The method visitor to write the frame to.\n         * @param type                The frame's type.\n         * @param localVariableLength The local variable length.\n         * @param localVariable       An array containing the types of the current local variables.\n         * @param stackSize           The size of the operand stack.\n         * @param stack               An array containing the types of the current operand stack.\n         */\n        void translateFrame(MethodVisitor methodVisitor, int type, int localVariableLength, Object[] localVariable, int stackSize, Object[] stack);\n\n        /**\n         * Injects a frame indicating the beginning of a return value handler for the currently handled method.\n         *\n         * @param methodVisitor The method visitor onto which to apply the stack map frame.\n         */\n        void injectReturnFrame(MethodVisitor methodVisitor);\n\n        /**\n         * Injects a frame indicating the beginning of an exception handler for the currently handled method.\n         *\n         * @param methodVisitor The method visitor onto which to apply the stack map frame.\n         */\n        void injectExceptionFrame(MethodVisitor methodVisitor);\n\n        /**\n         * Injects a frame indicating the completion of the currently handled method, i.e. all yielded types were added.\n         *\n         * @param methodVisitor The method visitor onto which to apply the stack map frame.\n         */\n        void injectCompletionFrame(MethodVisitor methodVisitor);\n\n        /**\n         * A stack map frame handler that can be used within a post processor. Emitting frames via this\n         * handler is the only legal way for a post processor to produce frames.\n         */\n        interface ForPostProcessor {\n\n            /**\n             * Injects a frame that represents the current state.\n             *\n             * @param methodVisitor The method visitor onto which to apply the stack map frame.\n             * @param stack         A list of types that are currently on the stack.\n             */\n            void injectIntermediateFrame(MethodVisitor methodVisitor, List<? extends TypeDescription> stack);\n        }\n\n        /**\n         * A stack map frame handler for an instrumented method.\n         */\n        interface ForInstrumentedMethod extends StackMapFrameHandler {\n\n            /**\n             * Binds this meta data handler for the enter advice.\n             *\n             * @param adviceMethod The enter advice method.\n             * @return An appropriate meta data handler for the enter method.\n             */\n            ForAdvice bindEnter(MethodDescription.InDefinedShape adviceMethod);\n\n            /**\n             * Binds this meta data handler for the exit advice.\n             *\n             * @param adviceMethod The exit advice method.\n             * @return An appropriate meta data handler for the enter method.\n             */\n            ForAdvice bindExit(MethodDescription.InDefinedShape adviceMethod);\n\n            /**\n             * Returns a hint to supply to a {@link ClassReader} when parsing an advice method.\n             *\n             * @return The reader hint to supply to an ASM class reader.\n             */\n            int getReaderHint();\n\n            /**\n             * Injects a frame after initialization if any initialization is performed.\n             *\n             * @param methodVisitor The method visitor to write any frames to.\n             */\n            void injectInitializationFrame(MethodVisitor methodVisitor);\n\n            /**\n             * Injects a frame before executing the instrumented method.\n             *\n             * @param methodVisitor The method visitor to write any frames to.\n             */\n            void injectStartFrame(MethodVisitor methodVisitor);\n\n            /**\n             * Injects a frame indicating the completion of the currently handled method, i.e. all yielded types were added.\n             *\n             * @param methodVisitor The method visitor onto which to apply the stack map frame.\n             */\n            void injectPostCompletionFrame(MethodVisitor methodVisitor);\n        }\n\n        /**\n         * A stack map frame handler for an advice method.\n         */\n        interface ForAdvice extends StackMapFrameHandler, ForPostProcessor, Advice.StackMapFrameHandler.ForPostProcessor {\n            /* marker interface */\n        }\n\n        /**\n         * A non-operational stack map frame handler.\n         */\n        enum NoOp implements ForInstrumentedMethod, ForAdvice {\n\n            /**\n             * The singleton instance.\n             */\n            INSTANCE;\n\n            /**\n             * {@inheritDoc}\n             */\n            public StackMapFrameHandler.ForAdvice bindEnter(MethodDescription.InDefinedShape adviceMethod) {\n                return this;\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public StackMapFrameHandler.ForAdvice bindExit(MethodDescription.InDefinedShape adviceMethod) {\n                return this;\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public int getReaderHint() {\n                return ClassReader.SKIP_FRAMES;\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public void translateFrame(MethodVisitor methodVisitor,\n                                       int type,\n                                       int localVariableLength,\n                                       Object[] localVariable,\n                                       int stackSize,\n                                       Object[] stack) {\n                /* do nothing */\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public void injectReturnFrame(MethodVisitor methodVisitor) {\n                /* do nothing */\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public void injectExceptionFrame(MethodVisitor methodVisitor) {\n                /* do nothing */\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public void injectCompletionFrame(MethodVisitor methodVisitor) {\n                /* do nothing */\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public void injectInitializationFrame(MethodVisitor methodVisitor) {\n                /* do nothing */\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public void injectStartFrame(MethodVisitor methodVisitor) {\n                /* do nothing */\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public void injectPostCompletionFrame(MethodVisitor methodVisitor) {\n                /* do nothing */\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public void injectIntermediateFrame(MethodVisitor methodVisitor, List<? extends TypeDescription> stack) {\n                /* do nothing */\n            }\n        }\n\n        /**\n         * A default implementation of a stack map frame handler for an instrumented method.\n         */\n        abstract class Default implements ForInstrumentedMethod {\n\n            /**\n             * An empty array indicating an empty frame.\n             */\n            protected static final Object[] EMPTY = new Object[0];\n\n            /**\n             * The instrumented type.\n             */\n            protected final TypeDescription instrumentedType;\n\n            /**\n             * The instrumented method.\n             */\n            protected final MethodDescription instrumentedMethod;\n\n            /**\n             * A list of virtual method arguments that are explicitly added before any code execution.\n             */\n            protected final List<? extends TypeDescription> initialTypes;\n\n            /**\n             * A list of virtual arguments that are available after the enter advice method is executed.\n             */\n            protected final List<? extends TypeDescription> latentTypes;\n\n            /**\n             * A list of virtual method arguments that are available before the instrumented method is executed.\n             */\n            protected final List<? extends TypeDescription> preMethodTypes;\n\n            /**\n             * A list of virtual method arguments that are available after the instrumented method has completed.\n             */\n            protected final List<? extends TypeDescription> postMethodTypes;\n\n            /**\n             * {@code true} if the meta data handler is expected to expand its frames.\n             */\n            protected final boolean expandFrames;\n\n            /**\n             * The current frame's size divergence from the original local variable array.\n             */\n            protected int currentFrameDivergence;\n\n            /**\n             * Creates a new default stack map frame handler.\n             *\n             * @param instrumentedType   The instrumented type.\n             * @param instrumentedMethod The instrumented method.\n             * @param initialTypes       A list of virtual method arguments that are explicitly added before any code execution.\n             * @param latentTypes        A list of virtual arguments that are available after the enter advice method is executed.\n             * @param preMethodTypes     A list of virtual method arguments that are available before the instrumented method is executed.\n             * @param postMethodTypes    A list of virtual method arguments that are available after the instrumented method has completed.\n             * @param expandFrames       {@code true} if the meta data handler is expected to expand its frames.\n             */\n            protected Default(TypeDescription instrumentedType,\n                              MethodDescription instrumentedMethod,\n                              List<? extends TypeDescription> initialTypes,\n                              List<? extends TypeDescription> latentTypes,\n                              List<? extends TypeDescription> preMethodTypes,\n                              List<? extends TypeDescription> postMethodTypes,\n                              boolean expandFrames) {\n                this.instrumentedType = instrumentedType;\n                this.instrumentedMethod = instrumentedMethod;\n                this.initialTypes = initialTypes;\n                this.latentTypes = latentTypes;\n                this.preMethodTypes = preMethodTypes;\n                this.postMethodTypes = postMethodTypes;\n                this.expandFrames = expandFrames;\n            }\n\n            /**\n             * Creates an appropriate stack map frame handler for an instrumented method.\n             *\n             * @param instrumentedType   The instrumented type.\n             * @param instrumentedMethod The instrumented method.\n             * @param initialTypes       A list of virtual method arguments that are explicitly added before any code execution.\n             * @param latentTypes        A list of virtual arguments that are available after the enter advice method is executed.\n             * @param preMethodTypes     A list of virtual method arguments that are available before the instrumented method is executed.\n             * @param postMethodTypes    A list of virtual method arguments that are available after the instrumented method has completed.\n             * @param exitAdvice         {@code true} if the current advice implies exit advice.\n             * @param copyArguments      {@code true} if the original arguments are copied before invoking the instrumented method.\n             * @param classFileVersion   The instrumented type's class file version.\n             * @param writerFlags        The flags supplied to the ASM writer.\n             * @param readerFlags        The reader flags supplied to the ASM reader.\n             * @return An appropriate stack map frame handler for an instrumented method.\n             */\n            protected static ForInstrumentedMethod of(TypeDescription instrumentedType,\n                                                      MethodDescription instrumentedMethod,\n                                                      List<? extends TypeDescription> initialTypes,\n                                                      List<? extends TypeDescription> latentTypes,\n                                                      List<? extends TypeDescription> preMethodTypes,\n                                                      List<? extends TypeDescription> postMethodTypes,\n                                                      boolean exitAdvice,\n                                                      boolean copyArguments,\n                                                      ClassFileVersion classFileVersion,\n                                                      int writerFlags,\n                                                      int readerFlags) {\n                if ((writerFlags & ClassWriter.COMPUTE_FRAMES) != 0\n                    || classFileVersion.isLessThan(ClassFileVersion.JAVA_V6)) {\n                    return NoOp.INSTANCE;\n                } else if (!exitAdvice && initialTypes.isEmpty()) {\n                    return new Trivial(instrumentedType,\n                        instrumentedMethod,\n                        latentTypes,\n                        (readerFlags & ClassReader.EXPAND_FRAMES) != 0);\n                } else if (copyArguments) {\n                    return new WithPreservedArguments.WithArgumentCopy(instrumentedType,\n                        instrumentedMethod,\n                        initialTypes,\n                        latentTypes,\n                        preMethodTypes,\n                        postMethodTypes,\n                        (readerFlags & ClassReader.EXPAND_FRAMES) != 0);\n                } else {\n                    return new WithPreservedArguments.WithoutArgumentCopy(instrumentedType,\n                        instrumentedMethod,\n                        initialTypes,\n                        latentTypes,\n                        preMethodTypes,\n                        postMethodTypes,\n                        (readerFlags & ClassReader.EXPAND_FRAMES) != 0,\n                        !instrumentedMethod.isConstructor());\n                }\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public StackMapFrameHandler.ForAdvice bindEnter(MethodDescription.InDefinedShape adviceMethod) {\n                return new ForAdvice(adviceMethod, initialTypes, latentTypes, preMethodTypes, TranslationMode.ENTER, instrumentedMethod.isConstructor()\n                    ? Initialization.UNITIALIZED\n                    : Initialization.INITIALIZED);\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public int getReaderHint() {\n                return expandFrames\n                    ? ClassReader.EXPAND_FRAMES\n                    : AsmVisitorWrapper.NO_FLAGS;\n            }\n\n            /**\n             * Translates a frame.\n             *\n             * @param methodVisitor       The method visitor to write the frame to.\n             * @param translationMode     The translation mode to apply.\n             * @param methodDescription   The method description for which the frame is written.\n             * @param additionalTypes     The additional types to consider part of the instrumented method's parameters.\n             * @param type                The frame's type.\n             * @param localVariableLength The local variable length.\n             * @param localVariable       An array containing the types of the current local variables.\n             * @param stackSize           The size of the operand stack.\n             * @param stack               An array containing the types of the current operand stack.\n             */\n            protected void translateFrame(MethodVisitor methodVisitor,\n                                          TranslationMode translationMode,\n                                          MethodDescription methodDescription,\n                                          List<? extends TypeDescription> additionalTypes,\n                                          int type,\n                                          int localVariableLength,\n                                          Object[] localVariable,\n                                          int stackSize,\n                                          Object[] stack) {\n                switch (type) {\n                    case Opcodes.F_SAME:\n                    case Opcodes.F_SAME1:\n                        break;\n                    case Opcodes.F_APPEND:\n                        currentFrameDivergence += localVariableLength;\n                        break;\n                    case Opcodes.F_CHOP:\n                        currentFrameDivergence -= localVariableLength;\n                        if (currentFrameDivergence < 0) {\n                            throw new IllegalStateException(methodDescription + \" dropped \" + Math.abs(currentFrameDivergence) + \" implicit frames\");\n                        }\n                        break;\n                    case Opcodes.F_FULL:\n                    case Opcodes.F_NEW:\n                        if (methodDescription.getParameters().size() + (methodDescription.isStatic() ? 0 : 1) > localVariableLength) {\n                            throw new IllegalStateException(\"Inconsistent frame length for \" + methodDescription + \": \" + localVariableLength);\n                        }\n                        int offset;\n                        if (methodDescription.isStatic()) {\n                            offset = 0;\n                        } else {\n                            if (!translationMode.isPossibleThisFrameValue(instrumentedType, instrumentedMethod, localVariable[0])) {\n                                throw new IllegalStateException(methodDescription + \" is inconsistent for 'this' reference: \" + localVariable[0]);\n                            }\n                            offset = 1;\n                        }\n                        for (int index = 0; index < methodDescription.getParameters().size(); index++) {\n                            if (!Initialization.INITIALIZED.toFrame(methodDescription.getParameters().get(index).getType().asErasure()).equals(localVariable[index + offset])) {\n                                throw new IllegalStateException(methodDescription + \" is inconsistent at \" + index + \": \" + localVariable[index + offset]);\n                            }\n                        }\n                        Object[] translated = new Object[localVariableLength\n                            - (methodDescription.isStatic() ? 0 : 1)\n                            - methodDescription.getParameters().size()\n                            + (instrumentedMethod.isStatic() ? 0 : 1)\n                            + instrumentedMethod.getParameters().size()\n                            + additionalTypes.size()];\n                        int index = translationMode.copy(instrumentedType, instrumentedMethod, methodDescription, localVariable, translated);\n                        for (TypeDescription typeDescription : additionalTypes) {\n                            translated[index++] = Initialization.INITIALIZED.toFrame(typeDescription);\n                        }\n                        System.arraycopy(localVariable,\n                            methodDescription.getParameters().size() + (methodDescription.isStatic() ? 0 : 1),\n                            translated,\n                            index,\n                            translated.length - index);\n                        localVariableLength = translated.length;\n                        localVariable = translated;\n                        currentFrameDivergence = translated.length - index;\n                        break;\n                    default:\n                        throw new IllegalArgumentException(\"Unexpected frame type: \" + type);\n                }\n                methodVisitor.visitFrame(type, localVariableLength, localVariable, stackSize, stack);\n            }\n\n            /**\n             * Injects a full stack map frame after the instrumented method has completed.\n             *\n             * @param methodVisitor  The method visitor onto which to write the stack map frame.\n             * @param initialization The initialization to apply when resolving a reference to the instance on which a non-static method is invoked.\n             * @param typesInArray   The types that were added to the local variable array additionally to the values of the instrumented method.\n             * @param typesOnStack   The types currently on the operand stack.\n             */\n            protected void injectFullFrame(MethodVisitor methodVisitor,\n                                           Initialization initialization,\n                                           List<? extends TypeDescription> typesInArray,\n                                           List<? extends TypeDescription> typesOnStack) {\n                Object[] localVariable = new Object[instrumentedMethod.getParameters().size()\n                    + (instrumentedMethod.isStatic() ? 0 : 1)\n                    + typesInArray.size()];\n                int index = 0;\n                if (!instrumentedMethod.isStatic()) {\n                    localVariable[index++] = initialization.toFrame(instrumentedType);\n                }\n                for (TypeDescription typeDescription : instrumentedMethod.getParameters().asTypeList().asErasures()) {\n                    localVariable[index++] = Initialization.INITIALIZED.toFrame(typeDescription);\n                }\n                for (TypeDescription typeDescription : typesInArray) {\n                    localVariable[index++] = Initialization.INITIALIZED.toFrame(typeDescription);\n                }\n                index = 0;\n                Object[] stackType = new Object[typesOnStack.size()];\n                for (TypeDescription typeDescription : typesOnStack) {\n                    stackType[index++] = Initialization.INITIALIZED.toFrame(typeDescription);\n                }\n                methodVisitor.visitFrame(expandFrames ? Opcodes.F_NEW : Opcodes.F_FULL, localVariable.length, localVariable, stackType.length, stackType);\n                currentFrameDivergence = 0;\n            }\n\n            /**\n             * A translation mode that determines how the fixed frames of the instrumented method are written.\n             */\n            protected enum TranslationMode {\n\n                /**\n                 * A translation mode that simply copies the original frames which are available when translating frames of the instrumented method.\n                 */\n                COPY {\n                    @Override\n                    protected int copy(TypeDescription instrumentedType,\n                                       MethodDescription instrumentedMethod,\n                                       MethodDescription methodDescription,\n                                       Object[] localVariable,\n                                       Object[] translated) {\n                        int length = instrumentedMethod.getParameters().size() + (instrumentedMethod.isStatic() ? 0 : 1);\n                        System.arraycopy(localVariable, 0, translated, 0, length);\n                        return length;\n                    }\n\n                    @Override\n                    protected boolean isPossibleThisFrameValue(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Object frame) {\n                        return instrumentedMethod.isConstructor() && Opcodes.UNINITIALIZED_THIS.equals(frame) || Initialization.INITIALIZED.toFrame(instrumentedType).equals(frame);\n                    }\n                },\n\n                /**\n                 * A translation mode for the enter advice that considers that the {@code this} reference might not be initialized for a constructor.\n                 */\n                ENTER {\n                    @Override\n                    protected int copy(TypeDescription instrumentedType,\n                                       MethodDescription instrumentedMethod,\n                                       MethodDescription methodDescription,\n                                       Object[] localVariable,\n                                       Object[] translated) {\n                        int index = 0;\n                        if (!instrumentedMethod.isStatic()) {\n                            translated[index++] = instrumentedMethod.isConstructor()\n                                ? Opcodes.UNINITIALIZED_THIS\n                                : Initialization.INITIALIZED.toFrame(instrumentedType);\n                        }\n                        for (TypeDescription typeDescription : instrumentedMethod.getParameters().asTypeList().asErasures()) {\n                            translated[index++] = Initialization.INITIALIZED.toFrame(typeDescription);\n                        }\n                        return index;\n                    }\n\n                    @Override\n                    protected boolean isPossibleThisFrameValue(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Object frame) {\n                        return instrumentedMethod.isConstructor()\n                            ? Opcodes.UNINITIALIZED_THIS.equals(frame)\n                            : Initialization.INITIALIZED.toFrame(instrumentedType).equals(frame);\n                    }\n                },\n\n                /**\n                 * A translation mode for an exit advice where the {@code this} reference is always initialized.\n                 */\n                EXIT {\n                    @Override\n                    protected int copy(TypeDescription instrumentedType,\n                                       MethodDescription instrumentedMethod,\n                                       MethodDescription methodDescription,\n                                       Object[] localVariable,\n                                       Object[] translated) {\n                        int index = 0;\n                        if (!instrumentedMethod.isStatic()) {\n                            translated[index++] = Initialization.INITIALIZED.toFrame(instrumentedType);\n                        }\n                        for (TypeDescription typeDescription : instrumentedMethod.getParameters().asTypeList().asErasures()) {\n                            translated[index++] = Initialization.INITIALIZED.toFrame(typeDescription);\n                        }\n                        return index;\n                    }\n\n                    @Override\n                    protected boolean isPossibleThisFrameValue(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Object frame) {\n                        return Initialization.INITIALIZED.toFrame(instrumentedType).equals(frame);\n                    }\n                };\n\n                /**\n                 * Copies the fixed parameters of the instrumented method onto the operand stack.\n                 *\n                 * @param instrumentedType   The instrumented type.\n                 * @param instrumentedMethod The instrumented method.\n                 * @param methodDescription  The method for which a frame is created.\n                 * @param localVariable      The original local variable array.\n                 * @param translated         The array containing the translated frames.\n                 * @return The amount of frames added to the translated frame array.\n                 */\n                protected abstract int copy(TypeDescription instrumentedType,\n                                            MethodDescription instrumentedMethod,\n                                            MethodDescription methodDescription,\n                                            Object[] localVariable,\n                                            Object[] translated);\n\n                /**\n                 * Checks if a variable value in a stack map frame is a legal value for describing a {@code this} reference.\n                 *\n                 * @param instrumentedType   The instrumented type.\n                 * @param instrumentedMethod The instrumented method.\n                 * @param frame              The frame value representing the {@code this} reference.\n                 * @return {@code true} if the value is a legal representation of the {@code this} reference.\n                 */\n                protected abstract boolean isPossibleThisFrameValue(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Object frame);\n            }\n\n            /**\n             * Represents the initialization state of a stack value that can either be initialized or uninitialized.\n             */\n            protected enum Initialization {\n\n                /**\n                 * Represents an uninitialized frame value within a constructor before invoking the super constructor.\n                 */\n                UNITIALIZED {\n                    /**\n                     * {@inheritDoc}\n                     */\n                    protected Object toFrame(TypeDescription typeDescription) {\n                        if (typeDescription.isPrimitive()) {\n                            throw new IllegalArgumentException(\"Cannot assume primitive uninitialized value: \" + typeDescription);\n                        }\n                        return Opcodes.UNINITIALIZED_THIS;\n                    }\n                },\n\n                /**\n                 * Represents an initialized frame value.\n                 */\n                INITIALIZED {\n                    /**\n                     * {@inheritDoc}\n                     */\n                    protected Object toFrame(TypeDescription typeDescription) {\n                        if (typeDescription.represents(boolean.class)\n                            || typeDescription.represents(byte.class)\n                            || typeDescription.represents(short.class)\n                            || typeDescription.represents(char.class)\n                            || typeDescription.represents(int.class)) {\n                            return Opcodes.INTEGER;\n                        } else if (typeDescription.represents(long.class)) {\n                            return Opcodes.LONG;\n                        } else if (typeDescription.represents(float.class)) {\n                            return Opcodes.FLOAT;\n                        } else if (typeDescription.represents(double.class)) {\n                            return Opcodes.DOUBLE;\n                        } else {\n                            return typeDescription.getInternalName();\n                        }\n                    }\n                };\n\n                /**\n                 * Initializes a frame value to its frame type.\n                 *\n                 * @param typeDescription The type being resolved.\n                 * @return The frame value.\n                 */\n                protected abstract Object toFrame(TypeDescription typeDescription);\n            }\n\n            /**\n             * A trivial stack map frame handler that applies a trivial translation for the instrumented method's stack map frames.\n             */\n            protected static class Trivial extends Default {\n\n                /**\n                 * Creates a new stack map frame handler that applies a trivial translation for the instrumented method's stack map frames.\n                 *\n                 * @param instrumentedType   The instrumented type.\n                 * @param instrumentedMethod The instrumented method.\n                 * @param latentTypes        A list of virtual arguments that are available after the enter advice method is executed.\n                 * @param expandFrames       {@code true} if the meta data handler is expected to expand its frames.\n                 */\n                protected Trivial(TypeDescription instrumentedType, MethodDescription instrumentedMethod, List<? extends TypeDescription> latentTypes, boolean expandFrames) {\n                    super(instrumentedType,\n                        instrumentedMethod,\n                        Collections.emptyList(),\n                        latentTypes,\n                        Collections.emptyList(),\n                        Collections.emptyList(),\n                        expandFrames);\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public void translateFrame(MethodVisitor methodVisitor,\n                                           int type,\n                                           int localVariableLength,\n                                           Object[] localVariable,\n                                           int stackSize,\n                                           Object[] stack) {\n                    methodVisitor.visitFrame(type, localVariableLength, localVariable, stackSize, stack);\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public StackMapFrameHandler.ForAdvice bindExit(MethodDescription.InDefinedShape adviceMethod) {\n                    throw new IllegalStateException(\"Did not expect exit advice \" + adviceMethod + \" for \" + instrumentedMethod);\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public void injectReturnFrame(MethodVisitor methodVisitor) {\n                    throw new IllegalStateException(\"Did not expect return frame for \" + instrumentedMethod);\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public void injectExceptionFrame(MethodVisitor methodVisitor) {\n                    throw new IllegalStateException(\"Did not expect exception frame for \" + instrumentedMethod);\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public void injectCompletionFrame(MethodVisitor methodVisitor) {\n                    throw new IllegalStateException(\"Did not expect completion frame for \" + instrumentedMethod);\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public void injectPostCompletionFrame(MethodVisitor methodVisitor) {\n                    throw new IllegalStateException(\"Did not expect post completion frame for \" + instrumentedMethod);\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public void injectInitializationFrame(MethodVisitor methodVisitor) {\n                    /* do nothing */\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public void injectStartFrame(MethodVisitor methodVisitor) {\n                    /* do nothing */\n                }\n            }\n\n            /**\n             * A stack map frame handler that requires the original arguments of the instrumented method to be preserved in their original form.\n             */\n            protected abstract static class WithPreservedArguments extends Default {\n\n                /**\n                 * {@code true} if a completion frame for the method bust be a full frame to reflect an initialization change.\n                 */\n                protected boolean allowCompactCompletionFrame;\n\n                /**\n                 * Creates a new stack map frame handler that requires the stack map frames of the original arguments to be preserved.\n                 *\n                 * @param instrumentedType            The instrumented type.\n                 * @param instrumentedMethod          The instrumented method.\n                 * @param initialTypes                A list of virtual method arguments that are explicitly added before any code execution.\n                 * @param latentTypes                 A list of virtual arguments that are available after the enter advice method is executed.\n                 * @param preMethodTypes              A list of virtual method arguments that are available before the instrumented method is executed.\n                 * @param postMethodTypes             A list of virtual method arguments that are available after the instrumented method has completed.\n                 * @param expandFrames                {@code true} if the meta data handler is expected to expand its frames.\n                 * @param allowCompactCompletionFrame {@code true} if a completion frame for the method bust be a full frame to reflect an initialization change.\n                 */\n                protected WithPreservedArguments(TypeDescription instrumentedType,\n                                                 MethodDescription instrumentedMethod,\n                                                 List<? extends TypeDescription> initialTypes,\n                                                 List<? extends TypeDescription> latentTypes,\n                                                 List<? extends TypeDescription> preMethodTypes,\n                                                 List<? extends TypeDescription> postMethodTypes,\n                                                 boolean expandFrames,\n                                                 boolean allowCompactCompletionFrame) {\n                    super(instrumentedType, instrumentedMethod, initialTypes, latentTypes, preMethodTypes, postMethodTypes, expandFrames);\n                    this.allowCompactCompletionFrame = allowCompactCompletionFrame;\n                }\n\n                @Override\n                //@SuppressFBWarnings(value = \"RC_REF_COMPARISON_BAD_PRACTICE\", justification = \"ASM models frames by reference comparison.\")\n                protected void translateFrame(MethodVisitor methodVisitor,\n                                              TranslationMode translationMode,\n                                              MethodDescription methodDescription,\n                                              List<? extends TypeDescription> additionalTypes,\n                                              int type,\n                                              int localVariableLength,\n                                              Object[] localVariable,\n                                              int stackSize,\n                                              Object[] stack) {\n                    if (type == Opcodes.F_FULL && localVariableLength > 0 && localVariable[0] != Opcodes.UNINITIALIZED_THIS) {\n                        allowCompactCompletionFrame = true;\n                    }\n                    super.translateFrame(methodVisitor, translationMode, methodDescription, additionalTypes, type, localVariableLength, localVariable, stackSize, stack);\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public StackMapFrameHandler.ForAdvice bindExit(MethodDescription.InDefinedShape adviceMethod) {\n                    return new ForAdvice(adviceMethod,\n                        CompoundList.of(initialTypes, preMethodTypes, postMethodTypes),\n                        Collections.emptyList(),\n                        Collections.emptyList(),\n                        TranslationMode.EXIT,\n                        Initialization.INITIALIZED);\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public void injectReturnFrame(MethodVisitor methodVisitor) {\n                    if (!expandFrames && currentFrameDivergence == 0) {\n                        if (instrumentedMethod.getReturnType().represents(void.class)) {\n                            methodVisitor.visitFrame(Opcodes.F_SAME, EMPTY.length, EMPTY, EMPTY.length, EMPTY);\n                        } else {\n                            methodVisitor.visitFrame(Opcodes.F_SAME1,\n                                EMPTY.length,\n                                EMPTY,\n                                1,\n                                new Object[]{Initialization.INITIALIZED.toFrame(instrumentedMethod.getReturnType().asErasure())});\n                        }\n                    } else {\n                        injectFullFrame(methodVisitor, Initialization.INITIALIZED, CompoundList.of(initialTypes, preMethodTypes), instrumentedMethod.getReturnType().represents(void.class)\n                            ? Collections.emptyList()\n                            : Collections.singletonList(instrumentedMethod.getReturnType().asErasure()));\n                    }\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public void injectExceptionFrame(MethodVisitor methodVisitor) {\n                    if (!expandFrames && currentFrameDivergence == 0) {\n                        methodVisitor.visitFrame(Opcodes.F_SAME1, EMPTY.length, EMPTY, 1, new Object[]{Type.getInternalName(Throwable.class)});\n                    } else {\n                        injectFullFrame(methodVisitor, Initialization.INITIALIZED, CompoundList.of(initialTypes, preMethodTypes), Collections.singletonList(TypeDescription.THROWABLE));\n                    }\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public void injectCompletionFrame(MethodVisitor methodVisitor) {\n                    if (allowCompactCompletionFrame && !expandFrames && currentFrameDivergence == 0 && postMethodTypes.size() < 4) {\n                        if (postMethodTypes.isEmpty()) {\n                            methodVisitor.visitFrame(Opcodes.F_SAME, EMPTY.length, EMPTY, EMPTY.length, EMPTY);\n                        } else {\n                            Object[] local = new Object[postMethodTypes.size()];\n                            int index = 0;\n                            for (TypeDescription typeDescription : postMethodTypes) {\n                                local[index++] = Initialization.INITIALIZED.toFrame(typeDescription);\n                            }\n                            methodVisitor.visitFrame(Opcodes.F_APPEND, local.length, local, EMPTY.length, EMPTY);\n                        }\n                    } else {\n                        injectFullFrame(methodVisitor, Initialization.INITIALIZED,\n                            CompoundList.of(initialTypes, preMethodTypes, postMethodTypes), Collections.emptyList());\n                    }\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public void injectPostCompletionFrame(MethodVisitor methodVisitor) {\n                    if (!expandFrames && currentFrameDivergence == 0) {\n                        methodVisitor.visitFrame(Opcodes.F_SAME, EMPTY.length, EMPTY, EMPTY.length, EMPTY);\n                    } else {\n                        injectFullFrame(methodVisitor, Initialization.INITIALIZED,\n                            CompoundList.of(initialTypes, preMethodTypes, postMethodTypes), Collections.emptyList());\n                    }\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public void injectInitializationFrame(MethodVisitor methodVisitor) {\n                    if (!initialTypes.isEmpty()) {\n                        if (!expandFrames && initialTypes.size() < 4) {\n                            Object[] localVariable = new Object[initialTypes.size()];\n                            int index = 0;\n                            for (TypeDescription typeDescription : initialTypes) {\n                                localVariable[index++] = Initialization.INITIALIZED.toFrame(typeDescription);\n                            }\n                            methodVisitor.visitFrame(Opcodes.F_APPEND, localVariable.length, localVariable, EMPTY.length, EMPTY);\n                        } else {\n                            Object[] localVariable = new Object[(instrumentedMethod.isStatic() ? 0 : 1)\n                                + instrumentedMethod.getParameters().size()\n                                + initialTypes.size()];\n                            int index = 0;\n                            if (instrumentedMethod.isConstructor()) {\n                                localVariable[index++] = Opcodes.UNINITIALIZED_THIS;\n                            } else if (!instrumentedMethod.isStatic()) {\n                                localVariable[index++] = Initialization.INITIALIZED.toFrame(instrumentedType);\n                            }\n                            for (TypeDescription typeDescription : instrumentedMethod.getParameters().asTypeList().asErasures()) {\n                                localVariable[index++] = Initialization.INITIALIZED.toFrame(typeDescription);\n                            }\n                            for (TypeDescription typeDescription : initialTypes) {\n                                localVariable[index++] = Initialization.INITIALIZED.toFrame(typeDescription);\n                            }\n                            methodVisitor.visitFrame(expandFrames ? Opcodes.F_NEW : Opcodes.F_FULL, localVariable.length, localVariable, EMPTY.length, EMPTY);\n                        }\n                    }\n                }\n\n                /**\n                 * A stack map frame handler that expects that the original argument frames remain preserved throughout the original invocation.\n                 */\n                protected static class WithoutArgumentCopy extends WithPreservedArguments {\n\n                    /**\n                     * Creates a new stack map frame handler that expects the original frames to be preserved.\n                     *\n                     * @param instrumentedType            The instrumented type.\n                     * @param instrumentedMethod          The instrumented method.\n                     * @param initialTypes                A list of virtual method arguments that are explicitly added before any code execution.\n                     * @param latentTypes                 A list of virtual arguments that are available after the enter advice method is executed.\n                     * @param preMethodTypes              A list of virtual method arguments that are available before the instrumented method is executed.\n                     * @param postMethodTypes             A list of virtual method arguments that are available after the instrumented method has completed.\n                     * @param expandFrames                {@code true} if the meta data handler is expected to expand its frames.\n                     * @param allowCompactCompletionFrame {@code true} if a completion frame for the method bust be a full frame to reflect an initialization change.\n                     */\n                    protected WithoutArgumentCopy(TypeDescription instrumentedType,\n                                                  MethodDescription instrumentedMethod,\n                                                  List<? extends TypeDescription> initialTypes,\n                                                  List<? extends TypeDescription> latentTypes,\n                                                  List<? extends TypeDescription> preMethodTypes,\n                                                  List<? extends TypeDescription> postMethodTypes,\n                                                  boolean expandFrames,\n                                                  boolean allowCompactCompletionFrame) {\n                        super(instrumentedType, instrumentedMethod, initialTypes, latentTypes, preMethodTypes, postMethodTypes, expandFrames, allowCompactCompletionFrame);\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public void injectStartFrame(MethodVisitor methodVisitor) {\n                        /* do nothing */\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public void translateFrame(MethodVisitor methodVisitor,\n                                               int type,\n                                               int localVariableLength,\n                                               Object[] localVariable,\n                                               int stackSize,\n                                               Object[] stack) {\n                        translateFrame(methodVisitor,\n                            TranslationMode.COPY,\n                            instrumentedMethod,\n                            CompoundList.of(initialTypes, preMethodTypes),\n                            type,\n                            localVariableLength,\n                            localVariable,\n                            stackSize,\n                            stack);\n                    }\n                }\n\n                /**\n                 * A stack map frame handler that expects that an argument copy of the original method arguments was made.\n                 */\n                protected static class WithArgumentCopy extends WithPreservedArguments {\n\n                    /**\n                     * Creates a new stack map frame handler that expects an argument copy.\n                     *\n                     * @param instrumentedType   The instrumented type.\n                     * @param instrumentedMethod The instrumented method.\n                     * @param initialTypes       A list of virtual method arguments that are explicitly added before any code execution.\n                     * @param latentTypes        The types that are given post execution of a possible enter advice.\n                     * @param preMethodTypes     A list of virtual method arguments that are available before the instrumented method is executed.\n                     * @param postMethodTypes    A list of virtual method arguments that are available after the instrumented method has completed.\n                     * @param expandFrames       {@code true} if the meta data handler is expected to expand its frames.\n                     */\n                    protected WithArgumentCopy(TypeDescription instrumentedType,\n                                               MethodDescription instrumentedMethod,\n                                               List<? extends TypeDescription> initialTypes,\n                                               List<? extends TypeDescription> latentTypes,\n                                               List<? extends TypeDescription> preMethodTypes,\n                                               List<? extends TypeDescription> postMethodTypes,\n                                               boolean expandFrames) {\n                        super(instrumentedType, instrumentedMethod, initialTypes, latentTypes, preMethodTypes, postMethodTypes, expandFrames, true);\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public void injectStartFrame(MethodVisitor methodVisitor) {\n                        if (!instrumentedMethod.isStatic() || !instrumentedMethod.getParameters().isEmpty()) {\n                            if (!expandFrames && (instrumentedMethod.isStatic() ? 0 : 1) + instrumentedMethod.getParameters().size() < 4) {\n                                Object[] localVariable = new Object[(instrumentedMethod.isStatic() ? 0 : 1) + instrumentedMethod.getParameters().size()];\n                                int index = 0;\n                                if (instrumentedMethod.isConstructor()) {\n                                    localVariable[index++] = Opcodes.UNINITIALIZED_THIS;\n                                } else if (!instrumentedMethod.isStatic()) {\n                                    localVariable[index++] = Initialization.INITIALIZED.toFrame(instrumentedType);\n                                }\n                                for (TypeDescription typeDescription : instrumentedMethod.getParameters().asTypeList().asErasures()) {\n                                    localVariable[index++] = Initialization.INITIALIZED.toFrame(typeDescription);\n                                }\n                                methodVisitor.visitFrame(Opcodes.F_APPEND, localVariable.length, localVariable, EMPTY.length, EMPTY);\n                            } else {\n                                Object[] localVariable = new Object[(instrumentedMethod.isStatic() ? 0 : 2)\n                                    + instrumentedMethod.getParameters().size() * 2\n                                    + initialTypes.size()\n                                    + preMethodTypes.size()];\n                                int index = 0;\n                                if (instrumentedMethod.isConstructor()) {\n                                    localVariable[index++] = Opcodes.UNINITIALIZED_THIS;\n                                } else if (!instrumentedMethod.isStatic()) {\n                                    localVariable[index++] = Initialization.INITIALIZED.toFrame(instrumentedType);\n                                }\n                                for (TypeDescription typeDescription : instrumentedMethod.getParameters().asTypeList().asErasures()) {\n                                    localVariable[index++] = Initialization.INITIALIZED.toFrame(typeDescription);\n                                }\n                                for (TypeDescription typeDescription : initialTypes) {\n                                    localVariable[index++] = Initialization.INITIALIZED.toFrame(typeDescription);\n                                }\n                                for (TypeDescription typeDescription : preMethodTypes) {\n                                    localVariable[index++] = Initialization.INITIALIZED.toFrame(typeDescription);\n                                }\n                                if (instrumentedMethod.isConstructor()) {\n                                    localVariable[index++] = Opcodes.UNINITIALIZED_THIS;\n                                } else if (!instrumentedMethod.isStatic()) {\n                                    localVariable[index++] = Initialization.INITIALIZED.toFrame(instrumentedType);\n                                }\n                                for (TypeDescription typeDescription : instrumentedMethod.getParameters().asTypeList().asErasures()) {\n                                    localVariable[index++] = Initialization.INITIALIZED.toFrame(typeDescription);\n                                }\n                                methodVisitor.visitFrame(expandFrames ? Opcodes.F_NEW : Opcodes.F_FULL, localVariable.length, localVariable, EMPTY.length, EMPTY);\n                            }\n                        }\n                        currentFrameDivergence = (instrumentedMethod.isStatic() ? 0 : 1) + instrumentedMethod.getParameters().size();\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    // @SuppressFBWarnings(value = \"RC_REF_COMPARISON_BAD_PRACTICE\",\n                    // justification = \"Reference equality is required by ASM\")\n                    public void translateFrame(MethodVisitor methodVisitor,\n                                               int type,\n                                               int localVariableLength,\n                                               Object[] localVariable,\n                                               int stackSize,\n                                               Object[] stack) {\n                        switch (type) {\n                            case Opcodes.F_SAME:\n                            case Opcodes.F_SAME1:\n                                break;\n                            case Opcodes.F_APPEND:\n                                currentFrameDivergence += localVariableLength;\n                                break;\n                            case Opcodes.F_CHOP:\n                                currentFrameDivergence -= localVariableLength;\n                                break;\n                            case Opcodes.F_FULL:\n                            case Opcodes.F_NEW:\n                                Object[] translated = new Object[localVariableLength\n                                    + (instrumentedMethod.isStatic() ? 0 : 1)\n                                    + instrumentedMethod.getParameters().size()\n                                    + initialTypes.size()\n                                    + preMethodTypes.size()];\n                                int index = 0;\n                                if (instrumentedMethod.isConstructor()) {\n                                    Initialization initialization = Initialization.INITIALIZED;\n                                    for (int variableIndex = 0; variableIndex < localVariableLength; variableIndex++) {\n                                        if (localVariable[variableIndex] == Opcodes.UNINITIALIZED_THIS) {\n                                            initialization = Initialization.UNITIALIZED;\n                                            break;\n                                        }\n                                    }\n                                    translated[index++] = initialization.toFrame(instrumentedType);\n                                } else if (!instrumentedMethod.isStatic()) {\n                                    translated[index++] = Initialization.INITIALIZED.toFrame(instrumentedType);\n                                }\n                                for (TypeDescription typeDescription : instrumentedMethod.getParameters().asTypeList().asErasures()) {\n                                    translated[index++] = Initialization.INITIALIZED.toFrame(typeDescription);\n                                }\n                                for (TypeDescription typeDescription : initialTypes) {\n                                    translated[index++] = Initialization.INITIALIZED.toFrame(typeDescription);\n                                }\n                                for (TypeDescription typeDescription : preMethodTypes) {\n                                    translated[index++] = Initialization.INITIALIZED.toFrame(typeDescription);\n                                }\n                                if (localVariableLength > 0) {\n                                    System.arraycopy(localVariable, 0, translated, index, localVariableLength);\n                                }\n                                localVariableLength = translated.length;\n                                localVariable = translated;\n                                currentFrameDivergence = localVariableLength;\n                                break;\n                            default:\n                                throw new IllegalArgumentException(\"Unexpected frame type: \" + type);\n                        }\n                        methodVisitor.visitFrame(type, localVariableLength, localVariable, stackSize, stack);\n                    }\n                }\n            }\n\n            /**\n             * A stack map frame handler for an advice method.\n             */\n            protected class ForAdvice implements StackMapFrameHandler.ForAdvice {\n\n                /**\n                 * The method description for which frames are translated.\n                 */\n                protected final MethodDescription.InDefinedShape adviceMethod;\n\n                /**\n                 * The types provided before execution of the advice code.\n                 */\n                protected final List<? extends TypeDescription> startTypes;\n\n                /**\n                 * The types that are given post execution of the advice.\n                 */\n                private final List<? extends TypeDescription> intermediateTypes;\n\n                /**\n                 * The types provided after execution of the advice code.\n                 */\n                protected final List<? extends TypeDescription> endTypes;\n\n                /**\n                 * The translation mode to apply for this advice method. Should be either {@link TranslationMode#ENTER} or {@link TranslationMode#EXIT}.\n                 */\n                protected final TranslationMode translationMode;\n\n                /**\n                 * The initialization to apply when resolving a reference to the instance on which a non-static method is invoked.\n                 */\n                private final Initialization initialization;\n\n                /**\n                 * {@code true} if an intermediate frame was yielded.\n                 */\n                private boolean intermedate;\n\n                /**\n                 * Creates a new meta data handler for an advice method.\n                 *\n                 * @param adviceMethod      The method description for which frames are translated.\n                 * @param startTypes        The types provided before execution of the advice code.\n                 * @param intermediateTypes The types that are given post execution of the advice.\n                 * @param endTypes          The types provided after execution of the advice code.\n                 * @param translationMode   The translation mode to apply for this advice method. Should be\n                 *                          either {@link TranslationMode#ENTER} or {@link TranslationMode#EXIT}.\n                 * @param initialization    The initialization to apply when resolving a reference to the instance on which a non-static method is invoked.\n                 */\n                protected ForAdvice(MethodDescription.InDefinedShape adviceMethod,\n                                    List<? extends TypeDescription> startTypes,\n                                    List<? extends TypeDescription> intermediateTypes,\n                                    List<? extends TypeDescription> endTypes,\n                                    TranslationMode translationMode,\n                                    Initialization initialization) {\n                    this.adviceMethod = adviceMethod;\n                    this.startTypes = startTypes;\n                    this.intermediateTypes = intermediateTypes;\n                    this.endTypes = endTypes;\n                    this.translationMode = translationMode;\n                    this.initialization = initialization;\n                    intermedate = false;\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public void translateFrame(MethodVisitor methodVisitor,\n                                           int type,\n                                           int localVariableLength,\n                                           Object[] localVariable,\n                                           int stackSize,\n                                           Object[] stack) {\n                    Default.this.translateFrame(methodVisitor,\n                        translationMode,\n                        adviceMethod,\n                        startTypes,\n                        type,\n                        localVariableLength,\n                        localVariable,\n                        stackSize,\n                        stack);\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public void injectReturnFrame(MethodVisitor methodVisitor) {\n                    if (!expandFrames && currentFrameDivergence == 0) {\n                        if (adviceMethod.getReturnType().represents(void.class)) {\n                            methodVisitor.visitFrame(Opcodes.F_SAME, EMPTY.length, EMPTY, EMPTY.length, EMPTY);\n                        } else {\n                            methodVisitor.visitFrame(Opcodes.F_SAME1,\n                                EMPTY.length,\n                                EMPTY,\n                                1,\n                                new Object[]{Initialization.INITIALIZED.toFrame(adviceMethod.getReturnType().asErasure())});\n                        }\n                    } else {\n                        injectFullFrame(methodVisitor, initialization, startTypes, adviceMethod.getReturnType().represents(void.class)\n                            ? Collections.emptyList()\n                            : Collections.singletonList(adviceMethod.getReturnType().asErasure()));\n                    }\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public void injectExceptionFrame(MethodVisitor methodVisitor) {\n                    if (!expandFrames && currentFrameDivergence == 0) {\n                        methodVisitor.visitFrame(Opcodes.F_SAME1, EMPTY.length, EMPTY, 1, new Object[]{Type.getInternalName(Throwable.class)});\n                    } else {\n                        injectFullFrame(methodVisitor, initialization, startTypes, Collections.singletonList(TypeDescription.THROWABLE));\n                    }\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public void injectCompletionFrame(MethodVisitor methodVisitor) {\n                    if (expandFrames) {\n                        injectFullFrame(methodVisitor, initialization,\n                            CompoundList.of(startTypes, endTypes), Collections.emptyList());\n                    } else if (currentFrameDivergence == 0 && (intermedate || endTypes.size() < 4)) {\n                        if (intermedate || endTypes.isEmpty()) {\n                            methodVisitor.visitFrame(Opcodes.F_SAME, EMPTY.length, EMPTY, EMPTY.length, EMPTY);\n                        } else {\n                            Object[] local = new Object[endTypes.size()];\n                            int index = 0;\n                            for (TypeDescription typeDescription : endTypes) {\n                                local[index++] = Initialization.INITIALIZED.toFrame(typeDescription);\n                            }\n                            methodVisitor.visitFrame(Opcodes.F_APPEND, local.length, local, EMPTY.length, EMPTY);\n                        }\n                    } else if (currentFrameDivergence < 3 && endTypes.isEmpty()) {\n                        methodVisitor.visitFrame(Opcodes.F_CHOP, currentFrameDivergence, EMPTY, EMPTY.length, EMPTY);\n                        currentFrameDivergence = 0;\n                    } else {\n                        injectFullFrame(methodVisitor, initialization,\n                            CompoundList.of(startTypes, endTypes), Collections.emptyList());\n                    }\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public void injectIntermediateFrame(MethodVisitor methodVisitor, List<? extends TypeDescription> stack) {\n                    if (expandFrames) {\n                        injectFullFrame(methodVisitor, initialization, CompoundList.of(startTypes, intermediateTypes), stack);\n                    } else if (intermedate && stack.size() < 2) {\n                        if (stack.isEmpty()) {\n                            methodVisitor.visitFrame(Opcodes.F_SAME, EMPTY.length, EMPTY, EMPTY.length, EMPTY);\n                        } else {\n                            methodVisitor.visitFrame(Opcodes.F_SAME1, EMPTY.length, EMPTY, 1, new Object[]{Initialization.INITIALIZED.toFrame(stack.get(0))});\n                        }\n                    } else if (currentFrameDivergence == 0\n                        && intermediateTypes.size() < 4\n                        && (stack.isEmpty() || stack.size() < 2 && intermediateTypes.isEmpty())) {\n                        if (intermediateTypes.isEmpty()) {\n                            if (stack.isEmpty()) {\n                                methodVisitor.visitFrame(Opcodes.F_SAME, EMPTY.length, EMPTY, EMPTY.length, EMPTY);\n                            } else {\n                                methodVisitor.visitFrame(Opcodes.F_SAME1, EMPTY.length, EMPTY, 1, new Object[]{Initialization.INITIALIZED.toFrame(stack.get(0))});\n                            }\n                        } else {\n                            Object[] local = new Object[intermediateTypes.size()];\n                            int index = 0;\n                            for (TypeDescription typeDescription : intermediateTypes) {\n                                local[index++] = Initialization.INITIALIZED.toFrame(typeDescription);\n                            }\n                            methodVisitor.visitFrame(Opcodes.F_APPEND, local.length, local, EMPTY.length, EMPTY);\n                        }\n                    } else if (currentFrameDivergence < 3 && intermediateTypes.isEmpty() && stack.isEmpty()) {\n                        methodVisitor.visitFrame(Opcodes.F_CHOP, currentFrameDivergence, EMPTY, EMPTY.length, EMPTY);\n                    } else {\n                        injectFullFrame(methodVisitor, initialization, CompoundList.of(startTypes, intermediateTypes), stack);\n                    }\n                    currentFrameDivergence = intermediateTypes.size() - endTypes.size();\n                    intermedate = true;\n                }\n            }\n        }\n    }\n\n\n    /**\n     * An argument handler is responsible for resolving offsets of the local variable array in the context of the applied instrumentation.\n     */\n    public interface ArgumentHandler {\n\n        /**\n         * The offset of the {@code this} reference.\n         */\n        int THIS_REFERENCE = 0;\n\n        /**\n         * Resolves an offset relative to an offset of the instrumented method.\n         *\n         * @param offset The offset to resolve.\n         * @return The resolved offset.\n         */\n        int argument(int offset);\n\n        /**\n         * Resolves the offset of the exit value of the exit advice.\n         *\n         * @return The offset of the exit value.\n         */\n        int exit();\n\n        /**\n         * Resolves the offset of the enter value of the enter advice.\n         *\n         * @return The offset of the enter value.\n         */\n        int enter();\n\n        /**\n         * Returns the offset of the local variable with the given name.\n         *\n         * @param name The name of the local variable being accessed.\n         * @return The named variable's offset.\n         */\n        int named(String name);\n\n        /**\n         * Resolves the offset of the returned value of the instrumented method.\n         *\n         * @return The offset of the returned value of the instrumented method.\n         */\n        int returned();\n\n        /**\n         * Resolves the offset of the thrown exception of the instrumented method.\n         *\n         * @return The offset of the thrown exception of the instrumented method.\n         */\n        int thrown();\n\n        /**\n         * An argument handler that is used for resolving the instrumented method.\n         */\n        interface ForInstrumentedMethod extends ArgumentHandler {\n\n            /**\n             * Resolves a local variable index.\n             *\n             * @param index The index to resolve.\n             * @return The resolved local variable index.\n             */\n            int variable(int index);\n\n            /**\n             * Prepares this argument handler for future offset access.\n             *\n             * @param methodVisitor The method visitor to which to write any potential byte code.\n             * @return The minimum stack size that is required to apply this manipulation.\n             */\n            int prepare(MethodVisitor methodVisitor);\n\n            /**\n             * Binds an advice method as enter advice for this handler.\n             *\n             * @param adviceMethod The resolved enter advice handler.\n             * @return The resolved argument handler for enter advice.\n             */\n            ForAdvice bindEnter(MethodDescription adviceMethod);\n\n            /**\n             * Binds an advice method as exit advice for this handler.\n             *\n             * @param adviceMethod  The resolved exit advice handler.\n             * @param skipThrowable {@code true} if no throwable is stored.\n             * @return The resolved argument handler for enter advice.\n             */\n            ForAdvice bindExit(MethodDescription adviceMethod, boolean skipThrowable);\n\n            /**\n             * Returns {@code true} if the original arguments are copied before invoking the instrumented method.\n             *\n             * @return {@code true} if the original arguments are copied before invoking the instrumented method.\n             */\n            boolean isCopyingArguments();\n\n            /**\n             * Returns a list of the named types in their declared order.\n             *\n             * @return A list of the named types in their declared order.\n             */\n            List<TypeDescription> getNamedTypes();\n\n            /**\n             * A default implementation of an argument handler for an instrumented method.\n             */\n            abstract class Default implements ForInstrumentedMethod {\n\n                /**\n                 * The instrumented method.\n                 */\n                protected final MethodDescription instrumentedMethod;\n\n                /**\n                 * The exit type or {@code void} if no exit type is defined.\n                 */\n                protected final TypeDefinition exitType;\n\n                /**\n                 * A mapping of all available local variables by their name to their type.\n                 */\n                protected final SortedMap<String, TypeDefinition> namedTypes;\n\n                /**\n                 * The enter type or {@code void} if no enter type is defined.\n                 */\n                protected final TypeDefinition enterType;\n\n                /**\n                 * Creates a new default argument handler for an instrumented method.\n                 *\n                 * @param instrumentedMethod The instrumented method.\n                 * @param exitType           The exit type or {@code void} if no exit type is defined.\n                 * @param namedTypes         A mapping of all available local variables by their name to their type.\n                 * @param enterType          The enter type or {@code void} if no enter type is defined.\n                 */\n                protected Default(MethodDescription instrumentedMethod,\n                                  TypeDefinition exitType,\n                                  SortedMap<String, TypeDefinition> namedTypes,\n                                  TypeDefinition enterType) {\n                    this.instrumentedMethod = instrumentedMethod;\n                    this.namedTypes = namedTypes;\n                    this.exitType = exitType;\n                    this.enterType = enterType;\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public int exit() {\n                    return instrumentedMethod.getStackSize();\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public int named(String name) {\n                    return instrumentedMethod.getStackSize()\n                        + exitType.getStackSize().getSize()\n                        + StackSize.of(namedTypes.headMap(name).values());\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public int enter() {\n                    return instrumentedMethod.getStackSize()\n                        + exitType.getStackSize().getSize()\n                        + StackSize.of(namedTypes.values());\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public int returned() {\n                    return instrumentedMethod.getStackSize()\n                        + exitType.getStackSize().getSize()\n                        + StackSize.of(namedTypes.values())\n                        + enterType.getStackSize().getSize();\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public int thrown() {\n                    return instrumentedMethod.getStackSize()\n                        + exitType.getStackSize().getSize()\n                        + StackSize.of(namedTypes.values())\n                        + enterType.getStackSize().getSize()\n                        + instrumentedMethod.getReturnType().getStackSize().getSize();\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public ForAdvice bindEnter(MethodDescription adviceMethod) {\n                    return new ForAdvice.Default.ForMethodEnter(instrumentedMethod, adviceMethod, exitType, namedTypes);\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public ForAdvice bindExit(MethodDescription adviceMethod, boolean skipThrowable) {\n                    return new ForAdvice.Default.ForMethodExit(instrumentedMethod,\n                        adviceMethod,\n                        exitType,\n                        namedTypes,\n                        enterType,\n                        skipThrowable ? StackSize.ZERO : StackSize.SINGLE);\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public List<TypeDescription> getNamedTypes() {\n                    List<TypeDescription> namedTypes = new ArrayList<>(this.namedTypes.size());\n                    for (TypeDefinition typeDefinition : this.namedTypes.values()) {\n                        namedTypes.add(typeDefinition.asErasure());\n                    }\n                    return namedTypes;\n                }\n\n                /**\n                 * A simple argument handler for an instrumented method.\n                 */\n                @HashCodeAndEqualsPlugin.Enhance\n                protected static class Simple extends Default {\n\n                    /**\n                     * Creates a new simple argument handler for an instrumented method.\n                     *\n                     * @param instrumentedMethod The instrumented method.\n                     * @param exitType           The exit type or {@code void} if no exit type is defined.\n                     * @param namedTypes         A mapping of all available local variables by their name to their type.\n                     * @param enterType          The enter type or {@code void} if no enter type is defined.\n                     */\n                    protected Simple(MethodDescription instrumentedMethod,\n                                     TypeDefinition exitType,\n                                     SortedMap<String, TypeDefinition> namedTypes,\n                                     TypeDefinition enterType) {\n                        super(instrumentedMethod, exitType, namedTypes, enterType);\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public int argument(int offset) {\n                        return offset < instrumentedMethod.getStackSize()\n                            ? offset\n                            : offset + exitType.getStackSize().getSize() + StackSize.of(namedTypes.values()) + enterType.getStackSize().getSize();\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public int variable(int index) {\n                        return index < (instrumentedMethod.isStatic() ? 0 : 1) + instrumentedMethod.getParameters().size()\n                            ? index\n                            : index + (exitType.represents(void.class) ? 0 : 1) + namedTypes.size() + (enterType.represents(void.class) ? 0 : 1);\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public boolean isCopyingArguments() {\n                        return false;\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public int prepare(MethodVisitor methodVisitor) {\n                        return 0;\n                    }\n                }\n\n                /**\n                 * An argument handler for an instrumented method that copies all arguments before executing the instrumented method.\n                 */\n                @HashCodeAndEqualsPlugin.Enhance\n                protected static class Copying extends Default {\n\n                    /**\n                     * Creates a new copying argument handler for an instrumented method.\n                     *\n                     * @param instrumentedMethod The instrumented method.\n                     * @param exitType           The exit type or {@code void} if no exit type is defined.\n                     * @param namedTypes         A mapping of all available local variables by their name to their type.\n                     * @param enterType          The enter type or {@code void} if no enter type is defined.\n                     */\n                    protected Copying(MethodDescription instrumentedMethod,\n                                      TypeDefinition exitType,\n                                      SortedMap<String, TypeDefinition> namedTypes,\n                                      TypeDefinition enterType) {\n                        super(instrumentedMethod, exitType, namedTypes, enterType);\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public int argument(int offset) {\n                        return instrumentedMethod.getStackSize()\n                            + exitType.getStackSize().getSize()\n                            + StackSize.of(namedTypes.values())\n                            + enterType.getStackSize().getSize()\n                            + offset;\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public int variable(int index) {\n                        return (instrumentedMethod.isStatic() ? 0 : 1)\n                            + instrumentedMethod.getParameters().size()\n                            + (exitType.represents(void.class) ? 0 : 1)\n                            + namedTypes.size()\n                            + (enterType.represents(void.class) ? 0 : 1)\n                            + index;\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public boolean isCopyingArguments() {\n                        return true;\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public int prepare(MethodVisitor methodVisitor) {\n                        StackSize stackSize;\n                        if (!instrumentedMethod.isStatic()) {\n                            methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);\n                            methodVisitor.visitVarInsn(Opcodes.ASTORE, instrumentedMethod.getStackSize()\n                                + exitType.getStackSize().getSize()\n                                + StackSize.of(namedTypes.values())\n                                + enterType.getStackSize().getSize());\n                            stackSize = StackSize.SINGLE;\n                        } else {\n                            stackSize = StackSize.ZERO;\n                        }\n                        for (ParameterDescription parameterDescription : instrumentedMethod.getParameters()) {\n                            Type type = Type.getType(parameterDescription.getType().asErasure().getDescriptor());\n                            methodVisitor.visitVarInsn(type.getOpcode(Opcodes.ILOAD), parameterDescription.getOffset());\n                            methodVisitor.visitVarInsn(type.getOpcode(Opcodes.ISTORE), instrumentedMethod.getStackSize()\n                                + exitType.getStackSize().getSize()\n                                + StackSize.of(namedTypes.values())\n                                + enterType.getStackSize().getSize()\n                                + parameterDescription.getOffset());\n                            stackSize = stackSize.maximum(parameterDescription.getType().getStackSize());\n                        }\n                        return stackSize.getSize();\n                    }\n                }\n            }\n        }\n\n        /**\n         * An argument handler that is used for resolving an advice method.\n         */\n        interface ForAdvice extends ArgumentHandler, Advice.ArgumentHandler {\n\n            /**\n             * Resolves an offset of the advice method.\n             *\n             * @param offset The offset to resolve.\n             * @return The resolved offset.\n             */\n            int mapped(int offset);\n\n            /**\n             * A default implementation for an argument handler for an advice method.\n             */\n            abstract class Default implements ArgumentHandler.ForAdvice {\n\n                /**\n                 * The instrumented method.\n                 */\n                protected final MethodDescription instrumentedMethod;\n\n                /**\n                 * The advice method.\n                 */\n                protected final MethodDescription adviceMethod;\n\n                /**\n                 * The enter type or {@code void} if no enter type is defined.\n                 */\n                protected final TypeDefinition exitType;\n\n                /**\n                 * A mapping of all available local variables by their name to their type.\n                 */\n                protected final SortedMap<String, TypeDefinition> namedTypes;\n\n                /**\n                 * Creates a new argument handler for an enter advice.\n                 *\n                 * @param instrumentedMethod The instrumented method.\n                 * @param adviceMethod       The advice method.\n                 * @param exitType           The exit type or {@code void} if no exit type is defined.\n                 * @param namedTypes         A mapping of all available local variables by their name to their type.\n                 */\n                protected Default(MethodDescription instrumentedMethod,\n                                  MethodDescription adviceMethod,\n                                  TypeDefinition exitType,\n                                  SortedMap<String, TypeDefinition> namedTypes) {\n                    this.instrumentedMethod = instrumentedMethod;\n                    this.adviceMethod = adviceMethod;\n                    this.exitType = exitType;\n                    this.namedTypes = namedTypes;\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public int argument(int offset) {\n                    return offset;\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public int exit() {\n                    return instrumentedMethod.getStackSize();\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public int named(String name) {\n                    return instrumentedMethod.getStackSize()\n                        + exitType.getStackSize().getSize()\n                        + StackSize.of(namedTypes.headMap(name).values());\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public int enter() {\n                    return instrumentedMethod.getStackSize()\n                        + exitType.getStackSize().getSize()\n                        + StackSize.of(namedTypes.values());\n                }\n\n                /**\n                 * An argument handler for an enter advice method.\n                 */\n                @HashCodeAndEqualsPlugin.Enhance\n                protected static class ForMethodEnter extends Default {\n\n                    /**\n                     * Creates a new argument handler for an enter advice method.\n                     *\n                     * @param instrumentedMethod The instrumented method.\n                     * @param adviceMethod       The advice method.\n                     * @param exitType           The exit type or {@code void} if no exit type is defined.\n                     * @param namedTypes         A mapping of all available local variables by their name to their type.\n                     */\n                    protected ForMethodEnter(MethodDescription instrumentedMethod,\n                                             MethodDescription adviceMethod,\n                                             TypeDefinition exitType,\n                                             SortedMap<String, TypeDefinition> namedTypes) {\n                        super(instrumentedMethod, adviceMethod, exitType, namedTypes);\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public int returned() {\n                        throw new IllegalStateException(\"Cannot resolve the return value offset during enter advice\");\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public int thrown() {\n                        throw new IllegalStateException(\"Cannot resolve the thrown value offset during enter advice\");\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public int mapped(int offset) {\n                        return instrumentedMethod.getStackSize()\n                            + exitType.getStackSize().getSize()\n                            + StackSize.of(namedTypes.values())\n                            - adviceMethod.getStackSize() + offset;\n                    }\n                }\n\n                /**\n                 * An argument handler for an exit advice method.\n                 */\n                @HashCodeAndEqualsPlugin.Enhance\n                protected static class ForMethodExit extends Default {\n\n                    /**\n                     * The enter type or {@code void} if no enter type is defined.\n                     */\n                    private final TypeDefinition enterType;\n\n                    /**\n                     * The stack size of a possibly stored throwable.\n                     */\n                    private final StackSize throwableSize;\n\n                    /**\n                     * Creates a new argument handler for an exit advice method.\n                     *\n                     * @param instrumentedMethod The instrumented method.\n                     * @param adviceMethod       The advice method.\n                     * @param exitType           The exit type or {@code void} if no exit type is defined.\n                     * @param namedTypes         A mapping of all available local variables by their name to their type.\n                     * @param enterType          The enter type or {@code void} if no enter type is defined.\n                     * @param throwableSize      The stack size of a possibly stored throwable.\n                     */\n                    protected ForMethodExit(MethodDescription instrumentedMethod,\n                                            MethodDescription adviceMethod,\n                                            TypeDefinition exitType,\n                                            SortedMap<String, TypeDefinition> namedTypes,\n                                            TypeDefinition enterType,\n                                            StackSize throwableSize) {\n                        super(instrumentedMethod, adviceMethod, exitType, namedTypes);\n                        this.enterType = enterType;\n                        this.throwableSize = throwableSize;\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public int returned() {\n                        return instrumentedMethod.getStackSize()\n                            + exitType.getStackSize().getSize()\n                            + StackSize.of(namedTypes.values())\n                            + enterType.getStackSize().getSize();\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public int thrown() {\n                        return instrumentedMethod.getStackSize()\n                            + exitType.getStackSize().getSize()\n                            + StackSize.of(namedTypes.values())\n                            + enterType.getStackSize().getSize()\n                            + instrumentedMethod.getReturnType().getStackSize().getSize();\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public int mapped(int offset) {\n                        return instrumentedMethod.getStackSize()\n                            + exitType.getStackSize().getSize()\n                            + StackSize.of(namedTypes.values())\n                            + enterType.getStackSize().getSize()\n                            + instrumentedMethod.getReturnType().getStackSize().getSize()\n                            + throwableSize.getSize()\n                            - adviceMethod.getStackSize()\n                            + offset;\n                    }\n                }\n            }\n        }\n\n        /**\n         * A factory for creating an argument handler.\n         */\n        enum Factory {\n\n            /**\n             * A factory for creating a simple argument handler.\n             */\n            SIMPLE {\n                @Override\n                protected ForInstrumentedMethod resolve(MethodDescription instrumentedMethod,\n                                                        TypeDefinition enterType,\n                                                        TypeDefinition exitType,\n                                                        SortedMap<String, TypeDefinition> namedTypes) {\n                    return new ForInstrumentedMethod.Default.Simple(instrumentedMethod,\n                        exitType,\n                        namedTypes,\n                        enterType);\n                }\n            },\n\n            /**\n             * A factory for creating an argument handler that copies all arguments before executing the instrumented method.\n             */\n            COPYING {\n                @Override\n                protected ForInstrumentedMethod resolve(MethodDescription instrumentedMethod,\n                                                        TypeDefinition enterType,\n                                                        TypeDefinition exitType,\n                                                        SortedMap<String, TypeDefinition> namedTypes) {\n                    return new ForInstrumentedMethod.Default.Copying(instrumentedMethod,\n                        exitType,\n                        namedTypes,\n                        enterType);\n                }\n            };\n\n            /**\n             * Creates an argument handler.\n             *\n             * @param instrumentedMethod The instrumented method.\n             * @param enterType          The enter type or {@code void} if no such type is defined.\n             * @param exitType           The exit type or {@code void} if no exit type is defined.\n             * @param namedTypes         A mapping of all available local variables by their name to their type.\n             * @return An argument handler for the instrumented method.\n             */\n            protected abstract ForInstrumentedMethod resolve(MethodDescription instrumentedMethod,\n                                                             TypeDefinition enterType,\n                                                             TypeDefinition exitType,\n                                                             SortedMap<String, TypeDefinition> namedTypes);\n        }\n    }\n\n\n    /**\n     * Represents an offset mapping for an advice method to an alternative offset.\n     */\n    public interface OffsetMapping {\n\n        /**\n         * Resolves an offset mapping to a given target offset.\n         *\n         * @param instrumentedType   The instrumented type.\n         * @param instrumentedMethod The instrumented method for which the mapping is to be resolved.\n         * @param assigner           The assigner to use.\n         * @param argumentHandler    The argument handler to use for resolving offsets of the local variable array of the instrumented method.\n         * @param sort               The sort of the advice method being resolved.\n         * @return A suitable target mapping.\n         */\n        Target resolve(TypeDescription instrumentedType,\n                       MethodDescription instrumentedMethod,\n                       Assigner assigner,\n                       ArgumentHandler argumentHandler,\n                       Sort sort);\n\n        /**\n         * A target offset of an offset mapping.\n         */\n        interface Target {\n\n            /**\n             * Resolves a read instruction.\n             *\n             * @return A stack manipulation that represents a reading of an advice parameter.\n             */\n            StackManipulation resolveRead();\n\n            /**\n             * Resolves a write instruction.\n             *\n             * @return A stack manipulation that represents a writing to an advice parameter.\n             */\n            StackManipulation resolveWrite();\n\n            /**\n             * Resolves an increment instruction.\n             *\n             * @param value The incrementation value.\n             * @return A stack manipulation that represents a writing to an advice parameter.\n             */\n            StackManipulation resolveIncrement(int value);\n\n            /**\n             * An adapter class for a target that only can be read.\n             */\n            abstract class AbstractReadOnlyAdapter implements Target {\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public StackManipulation resolveWrite() {\n                    throw new IllegalStateException(\"Cannot write to read-only value\");\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public StackManipulation resolveIncrement(int value) {\n                    throw new IllegalStateException(\"Cannot write to read-only value\");\n                }\n            }\n\n            /**\n             * A target for an offset mapping that represents a non-operational value. All writes are discarded and a value's\n             * default value is returned upon every read.\n             */\n            @HashCodeAndEqualsPlugin.Enhance\n            abstract class ForDefaultValue implements Target {\n\n                /**\n                 * The represented type.\n                 */\n                protected final TypeDefinition typeDefinition;\n\n                /**\n                 * A stack manipulation to apply after a read instruction.\n                 */\n                protected final StackManipulation readAssignment;\n\n                /**\n                 * Creates a new target for a default value.\n                 *\n                 * @param typeDefinition The represented type.\n                 * @param readAssignment A stack manipulation to apply after a read instruction.\n                 */\n                protected ForDefaultValue(TypeDefinition typeDefinition, StackManipulation readAssignment) {\n                    this.typeDefinition = typeDefinition;\n                    this.readAssignment = readAssignment;\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public StackManipulation resolveRead() {\n                    return new StackManipulation.Compound(DefaultValue.of(typeDefinition), readAssignment);\n                }\n\n                /**\n                 * A read-only target for a default value.\n                 */\n                public static class ReadOnly extends ForDefaultValue {\n\n                    /**\n                     * Creates a new writable target for a default value.\n                     *\n                     * @param typeDefinition The represented type.\n                     */\n                    public ReadOnly(TypeDefinition typeDefinition) {\n                        this(typeDefinition, StackManipulation.Trivial.INSTANCE);\n                    }\n\n                    /**\n                     * Creates a new -writable target for a default value.\n                     *\n                     * @param typeDefinition The represented type.\n                     * @param readAssignment A stack manipulation to apply after a read instruction.\n                     */\n                    public ReadOnly(TypeDefinition typeDefinition, StackManipulation readAssignment) {\n                        super(typeDefinition, readAssignment);\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public StackManipulation resolveWrite() {\n                        throw new IllegalStateException(\"Cannot write to read-only default value\");\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public StackManipulation resolveIncrement(int value) {\n                        throw new IllegalStateException(\"Cannot write to read-only default value\");\n                    }\n                }\n\n                /**\n                 * A read-write target for a default value.\n                 */\n                public static class ReadWrite extends ForDefaultValue {\n\n                    /**\n                     * Creates a new read-only target for a default value.\n                     *\n                     * @param typeDefinition The represented type.\n                     */\n                    public ReadWrite(TypeDefinition typeDefinition) {\n                        this(typeDefinition, StackManipulation.Trivial.INSTANCE);\n                    }\n\n                    /**\n                     * Creates a new read-only target for a default value.\n                     *\n                     * @param typeDefinition The represented type.\n                     * @param readAssignment A stack manipulation to apply after a read instruction.\n                     */\n                    public ReadWrite(TypeDefinition typeDefinition, StackManipulation readAssignment) {\n                        super(typeDefinition, readAssignment);\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public StackManipulation resolveWrite() {\n                        return Removal.of(typeDefinition);\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public StackManipulation resolveIncrement(int value) {\n                        return StackManipulation.Trivial.INSTANCE;\n                    }\n                }\n            }\n\n            /**\n             * A target for an offset mapping that represents a local variable.\n             */\n            @HashCodeAndEqualsPlugin.Enhance\n            abstract class ForVariable implements Target {\n\n                /**\n                 * The represented type.\n                 */\n                protected final TypeDefinition typeDefinition;\n\n                /**\n                 * The value's offset.\n                 */\n                protected final int offset;\n\n                /**\n                 * An assignment to execute upon reading a value.\n                 */\n                protected final StackManipulation readAssignment;\n\n                /**\n                 * Creates a new target for a local variable mapping.\n                 *\n                 * @param typeDefinition The represented type.\n                 * @param offset         The value's offset.\n                 * @param readAssignment An assignment to execute upon reading a value.\n                 */\n                protected ForVariable(TypeDefinition typeDefinition, int offset, StackManipulation readAssignment) {\n                    this.typeDefinition = typeDefinition;\n                    this.offset = offset;\n                    this.readAssignment = readAssignment;\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public StackManipulation resolveRead() {\n                    return new StackManipulation.Compound(MethodVariableAccess.of(typeDefinition).loadFrom(offset), readAssignment);\n                }\n\n                /**\n                 * A target for a read-only mapping of a local variable.\n                 */\n                public static class ReadOnly extends ForVariable {\n\n                    /**\n                     * Creates a read-only mapping for a local variable.\n                     *\n                     * @param typeDefinition The represented type.\n                     * @param offset         The value's offset.\n                     */\n                    public ReadOnly(TypeDefinition typeDefinition, int offset) {\n                        this(typeDefinition, offset, StackManipulation.Trivial.INSTANCE);\n                    }\n\n                    /**\n                     * Creates a read-only mapping for a local variable.\n                     *\n                     * @param typeDefinition The represented type.\n                     * @param offset         The value's offset.\n                     * @param readAssignment An assignment to execute upon reading a value.\n                     */\n                    public ReadOnly(TypeDefinition typeDefinition, int offset, StackManipulation readAssignment) {\n                        super(typeDefinition, offset, readAssignment);\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public StackManipulation resolveWrite() {\n                        throw new IllegalStateException(\"Cannot write to read-only parameter \" + typeDefinition + \" at \" + offset);\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public StackManipulation resolveIncrement(int value) {\n                        throw new IllegalStateException(\"Cannot write to read-only variable \" + typeDefinition + \" at \" + offset);\n                    }\n                }\n\n                /**\n                 * A target for a writable mapping of a local variable.\n                 */\n                @HashCodeAndEqualsPlugin.Enhance\n                public static class ReadWrite extends ForVariable {\n\n                    /**\n                     * A stack manipulation to apply upon a write to the variable.\n                     */\n                    private final StackManipulation writeAssignment;\n\n                    /**\n                     * Creates a new target mapping for a writable local variable.\n                     *\n                     * @param typeDefinition The represented type.\n                     * @param offset         The value's offset.\n                     */\n                    public ReadWrite(TypeDefinition typeDefinition, int offset) {\n                        this(typeDefinition, offset, StackManipulation.Trivial.INSTANCE, StackManipulation.Trivial.INSTANCE);\n                    }\n\n                    /**\n                     * Creates a new target mapping for a writable local variable.\n                     *\n                     * @param typeDefinition  The represented type.\n                     * @param offset          The value's offset.\n                     * @param readAssignment  An assignment to execute upon reading a value.\n                     * @param writeAssignment A stack manipulation to apply upon a write to the variable.\n                     */\n                    public ReadWrite(TypeDefinition typeDefinition, int offset, StackManipulation readAssignment, StackManipulation writeAssignment) {\n                        super(typeDefinition, offset, readAssignment);\n                        this.writeAssignment = writeAssignment;\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public StackManipulation resolveWrite() {\n                        return new StackManipulation.Compound(writeAssignment, MethodVariableAccess.of(typeDefinition).storeAt(offset));\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public StackManipulation resolveIncrement(int value) {\n                        return typeDefinition.represents(int.class)\n                            ? MethodVariableAccess.of(typeDefinition).increment(offset, value)\n                            : new StackManipulation.Compound(resolveRead(), IntegerConstant.forValue(1), Addition.INTEGER, resolveWrite());\n                    }\n                }\n            }\n\n            /**\n             * A target mapping for an array of all local variables.\n             */\n            @HashCodeAndEqualsPlugin.Enhance\n            abstract class ForArray implements Target {\n\n                /**\n                 * The compound target type.\n                 */\n                protected final TypeDescription.Generic target;\n\n                /**\n                 * The stack manipulations to apply upon reading a variable array.\n                 */\n                protected final List<? extends StackManipulation> valueReads;\n\n                /**\n                 * Creates a new target mapping for an array of all local variables.\n                 *\n                 * @param target     The compound target type.\n                 * @param valueReads The stack manipulations to apply upon reading a variable array.\n                 */\n                protected ForArray(TypeDescription.Generic target, List<? extends StackManipulation> valueReads) {\n                    this.target = target;\n                    this.valueReads = valueReads;\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public StackManipulation resolveRead() {\n                    return ArrayFactory.forType(target).withValues(valueReads);\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public StackManipulation resolveIncrement(int value) {\n                    throw new IllegalStateException(\"Cannot increment read-only array value\");\n                }\n\n                /**\n                 * A target mapping for a read-only target mapping for an array of local variables.\n                 */\n                public static class ReadOnly extends ForArray {\n\n                    /**\n                     * Creates a read-only target mapping for an array of all local variables.\n                     *\n                     * @param target     The compound target type.\n                     * @param valueReads The stack manipulations to apply upon reading a variable array.\n                     */\n                    public ReadOnly(TypeDescription.Generic target, List<? extends StackManipulation> valueReads) {\n                        super(target, valueReads);\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public StackManipulation resolveWrite() {\n                        throw new IllegalStateException(\"Cannot write to read-only array value\");\n                    }\n                }\n\n                /**\n                 * A target mapping for a writable target mapping for an array of local variables.\n                 */\n                @HashCodeAndEqualsPlugin.Enhance\n                public static class ReadWrite extends ForArray {\n\n                    /**\n                     * The stack manipulations to apply upon writing to a variable array.\n                     */\n                    private final List<? extends StackManipulation> valueWrites;\n\n                    /**\n                     * Creates a writable target mapping for an array of all local variables.\n                     *\n                     * @param target      The compound target type.\n                     * @param valueReads  The stack manipulations to apply upon reading a variable array.\n                     * @param valueWrites The stack manipulations to apply upon writing to a variable array.\n                     */\n                    public ReadWrite(TypeDescription.Generic target,\n                                     List<? extends StackManipulation> valueReads,\n                                     List<? extends StackManipulation> valueWrites) {\n                        super(target, valueReads);\n                        this.valueWrites = valueWrites;\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public StackManipulation resolveWrite() {\n                        return new StackManipulation.Compound(ArrayAccess.of(target).forEach(valueWrites), Removal.SINGLE);\n                    }\n                }\n            }\n\n            /**\n             * A target for an offset mapping that loads a field value.\n             */\n            @HashCodeAndEqualsPlugin.Enhance\n            abstract class ForField implements Target {\n\n                /**\n                 * The field value to load.\n                 */\n                protected final FieldDescription fieldDescription;\n\n                /**\n                 * The stack manipulation to apply upon a read.\n                 */\n                protected final StackManipulation readAssignment;\n\n                /**\n                 * Creates a new target for a field value mapping.\n                 *\n                 * @param fieldDescription The field value to load.\n                 * @param readAssignment   The stack manipulation to apply upon a read.\n                 */\n                protected ForField(FieldDescription fieldDescription, StackManipulation readAssignment) {\n                    this.fieldDescription = fieldDescription;\n                    this.readAssignment = readAssignment;\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public StackManipulation resolveRead() {\n                    return new StackManipulation.Compound(fieldDescription.isStatic()\n                        ? StackManipulation.Trivial.INSTANCE\n                        : MethodVariableAccess.loadThis(), FieldAccess.forField(fieldDescription).read(), readAssignment);\n                }\n\n                /**\n                 * A read-only mapping for a field value.\n                 */\n                public static class ReadOnly extends ForField {\n\n                    /**\n                     * Creates a new read-only mapping for a field.\n                     *\n                     * @param fieldDescription The field value to load.\n                     */\n                    public ReadOnly(FieldDescription fieldDescription) {\n                        this(fieldDescription, StackManipulation.Trivial.INSTANCE);\n                    }\n\n                    /**\n                     * Creates a new read-only mapping for a field.\n                     *\n                     * @param fieldDescription The field value to load.\n                     * @param readAssignment   The stack manipulation to apply upon a read.\n                     */\n                    public ReadOnly(FieldDescription fieldDescription, StackManipulation readAssignment) {\n                        super(fieldDescription, readAssignment);\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public StackManipulation resolveWrite() {\n                        throw new IllegalStateException(\"Cannot write to read-only field value\");\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public StackManipulation resolveIncrement(int value) {\n                        throw new IllegalStateException(\"Cannot write to read-only field value\");\n                    }\n                }\n\n                /**\n                 * A mapping for a writable field.\n                 */\n                @HashCodeAndEqualsPlugin.Enhance\n                public static class ReadWrite extends ForField {\n\n                    /**\n                     * An assignment to apply prior to a field write.\n                     */\n                    private final StackManipulation writeAssignment;\n\n                    /**\n                     * Creates a new target for a writable field.\n                     *\n                     * @param fieldDescription The field value to load.\n                     */\n                    public ReadWrite(FieldDescription fieldDescription) {\n                        this(fieldDescription, StackManipulation.Trivial.INSTANCE, StackManipulation.Trivial.INSTANCE);\n                    }\n\n                    /**\n                     * Creates a new target for a writable field.\n                     *\n                     * @param fieldDescription The field value to load.\n                     * @param readAssignment   The stack manipulation to apply upon a read.\n                     * @param writeAssignment  An assignment to apply prior to a field write.\n                     */\n                    public ReadWrite(FieldDescription fieldDescription,\n                                     StackManipulation readAssignment, StackManipulation writeAssignment) {\n                        super(fieldDescription, readAssignment);\n                        this.writeAssignment = writeAssignment;\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public StackManipulation resolveWrite() {\n                        StackManipulation preparation;\n                        if (fieldDescription.isStatic()) {\n                            preparation = StackManipulation.Trivial.INSTANCE;\n                        } else {\n                            preparation = new StackManipulation.Compound(\n                                MethodVariableAccess.loadThis(),\n                                Duplication.SINGLE.flipOver(fieldDescription.getType()),\n                                Removal.SINGLE\n                            );\n                        }\n                        return new StackManipulation.Compound(writeAssignment, preparation, FieldAccess.forField(fieldDescription).write());\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public StackManipulation resolveIncrement(int value) {\n                        return new StackManipulation.Compound(\n                            resolveRead(),\n                            IntegerConstant.forValue(value),\n                            Addition.INTEGER,\n                            resolveWrite()\n                        );\n                    }\n                }\n            }\n\n            /**\n             * A target for an offset mapping that represents a read-only stack manipulation.\n             */\n            @HashCodeAndEqualsPlugin.Enhance\n            class ForStackManipulation implements Target {\n\n                /**\n                 * The represented stack manipulation.\n                 */\n                private final StackManipulation stackManipulation;\n\n                /**\n                 * Creates a new target for an offset mapping for a stack manipulation.\n                 *\n                 * @param stackManipulation The represented stack manipulation.\n                 */\n                public ForStackManipulation(StackManipulation stackManipulation) {\n                    this.stackManipulation = stackManipulation;\n                }\n\n                /**\n                 * Creates a target for a {@link Method} or {@link Constructor} constant.\n                 *\n                 * @param methodDescription The method or constructor to represent.\n                 * @return A mapping for a method or constructor constant.\n                 */\n                public static Target of(MethodDescription.InDefinedShape methodDescription) {\n                    return new ForStackManipulation(MethodConstant.of(methodDescription));\n                }\n\n                /**\n                 * Creates a target for an offset mapping for a type constant.\n                 *\n                 * @param typeDescription The type constant to represent.\n                 * @return A mapping for a type constant.\n                 */\n                public static Target of(TypeDescription typeDescription) {\n                    return new ForStackManipulation(ClassConstant.of(typeDescription));\n                }\n\n                /**\n                 * Creates a target for an offset mapping for a constant value or {@code null}.\n                 *\n                 * @param value The constant value to represent or {@code null}.\n                 * @return An appropriate target for an offset mapping.\n                 */\n                public static Target of(Object value) {\n                    if (value == null) {\n                        return new ForStackManipulation(NullConstant.INSTANCE);\n                    } else if (value instanceof Boolean) {\n                        return new ForStackManipulation(IntegerConstant.forValue((Boolean) value));\n                    } else if (value instanceof Byte) {\n                        return new ForStackManipulation(IntegerConstant.forValue((Byte) value));\n                    } else if (value instanceof Short) {\n                        return new ForStackManipulation(IntegerConstant.forValue((Short) value));\n                    } else if (value instanceof Character) {\n                        return new ForStackManipulation(IntegerConstant.forValue((Character) value));\n                    } else if (value instanceof Integer) {\n                        return new ForStackManipulation(IntegerConstant.forValue((Integer) value));\n                    } else if (value instanceof Long) {\n                        return new ForStackManipulation(LongConstant.forValue((Long) value));\n                    } else if (value instanceof Float) {\n                        return new ForStackManipulation(FloatConstant.forValue((Float) value));\n                    } else if (value instanceof Double) {\n                        return new ForStackManipulation(DoubleConstant.forValue((Double) value));\n                    } else if (value instanceof String) {\n                        return new ForStackManipulation(new TextConstant((String) value));\n                    } else if (value instanceof Enum<?>) {\n                        return new ForStackManipulation(FieldAccess\n                            .forEnumeration(new EnumerationDescription.ForLoadedEnumeration((Enum<?>) value)));\n                    } else if (value instanceof EnumerationDescription) {\n                        return new ForStackManipulation(FieldAccess.forEnumeration((EnumerationDescription) value));\n                    } else if (value instanceof Class<?>) {\n                        return new ForStackManipulation(ClassConstant.of(TypeDescription.ForLoadedType.of((Class<?>) value)));\n                    } else if (value instanceof TypeDescription) {\n                        return new ForStackManipulation(ClassConstant.of((TypeDescription) value));\n                    } else if (JavaType.METHOD_HANDLE.isInstance(value)) {\n                        return new ForStackManipulation(new JavaConstantValue(JavaConstant.MethodHandle.ofLoaded(value)));\n                    } else if (JavaType.METHOD_TYPE.isInstance(value)) {\n                        return new ForStackManipulation(new JavaConstantValue(JavaConstant.MethodType.ofLoaded(value)));\n                    } else if (value instanceof JavaConstant) {\n                        return new ForStackManipulation(new JavaConstantValue((JavaConstant) value));\n                    } else {\n                        throw new IllegalArgumentException(\"Not a constant value: \" + value);\n                    }\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public StackManipulation resolveRead() {\n                    return stackManipulation;\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public StackManipulation resolveWrite() {\n                    throw new IllegalStateException(\"Cannot write to constant value: \" + stackManipulation);\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public StackManipulation resolveIncrement(int value) {\n                    throw new IllegalStateException(\"Cannot write to constant value: \" + stackManipulation);\n                }\n\n                /**\n                 * A constant value that can be written to.\n                 */\n                @HashCodeAndEqualsPlugin.Enhance\n                public static class Writable implements Target {\n\n                    /**\n                     * The reading stack manipulation.\n                     */\n                    private final StackManipulation read;\n\n                    /**\n                     * The writing stack manipulation.\n                     */\n                    private final StackManipulation write;\n\n                    /**\n                     * Creates a writable target.\n                     *\n                     * @param read  The reading stack manipulation.\n                     * @param write The writing stack manipulation.\n                     */\n                    public Writable(StackManipulation read, StackManipulation write) {\n                        this.read = read;\n                        this.write = write;\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public StackManipulation resolveRead() {\n                        return read;\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public StackManipulation resolveWrite() {\n                        return write;\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public StackManipulation resolveIncrement(int value) {\n                        throw new IllegalStateException(\"Cannot increment mutable constant value: \" + write);\n                    }\n                }\n            }\n        }\n\n        /**\n         * Represents a factory for creating a {@link OffsetMapping} for a given parameter for a given annotation.\n         *\n         * @param <T> The annotation type that triggers this factory.\n         */\n        interface Factory<T extends Annotation> {\n\n            /**\n             * Returns the annotation type of this factory.\n             *\n             * @return The factory's annotation type.\n             */\n            Class<T> getAnnotationType();\n\n            /**\n             * Creates a new offset mapping for the supplied parameter if possible.\n             *\n             * @param target     The parameter description for which to resolve an offset mapping.\n             * @param annotation The annotation that triggered this factory.\n             * @param adviceType {@code true} if the binding is applied using advice method delegation.\n             * @return A resolved offset mapping or {@code null} if no mapping can be resolved for this parameter.\n             */\n            OffsetMapping make(ParameterDescription.InDefinedShape target,\n                               AnnotationDescription.Loadable<T> annotation, AdviceType adviceType);\n\n            /**\n             * Describes the type of advice being applied.\n             */\n            enum AdviceType {\n\n                /**\n                 * Indicates advice where the invocation is delegated.\n                 */\n                DELEGATION(true),\n\n                /**\n                 * Indicates advice where the invocation's code is copied into the target method.\n                 */\n                INLINING(false);\n\n                /**\n                 * {@code true} if delegation is used.\n                 */\n                private final boolean delegation;\n\n                /**\n                 * Creates a new advice type.\n                 *\n                 * @param delegation {@code true} if delegation is used.\n                 */\n                AdviceType(boolean delegation) {\n                    this.delegation = delegation;\n                }\n\n                /**\n                 * Returns {@code true} if delegation is used.\n                 *\n                 * @return {@code true} if delegation is used.\n                 */\n                public boolean isDelegation() {\n                    return delegation;\n                }\n            }\n\n            /**\n             * A simple factory that binds a constant offset mapping.\n             *\n             * @param <T> The annotation type that represents this offset mapping.\n             */\n            @HashCodeAndEqualsPlugin.Enhance\n            class Simple<T extends Annotation> implements Factory<T> {\n\n                /**\n                 * The annotation type being bound.\n                 */\n                private final Class<T> annotationType;\n\n                /**\n                 * The fixed offset mapping.\n                 */\n                private final OffsetMapping offsetMapping;\n\n                /**\n                 * Creates a simple factory for a simple binding for an offset mapping.\n                 *\n                 * @param annotationType The annotation type being bound.\n                 * @param offsetMapping  The fixed offset mapping.\n                 */\n                public Simple(Class<T> annotationType, OffsetMapping offsetMapping) {\n                    this.annotationType = annotationType;\n                    this.offsetMapping = offsetMapping;\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public Class<T> getAnnotationType() {\n                    return annotationType;\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public OffsetMapping make(ParameterDescription.InDefinedShape target,\n                                          AnnotationDescription.Loadable<T> annotation, AdviceType adviceType) {\n                    return offsetMapping;\n                }\n            }\n\n            /**\n             * A factory for an annotation whose use is not permitted.\n             *\n             * @param <T> The annotation type this factory binds.\n             */\n            @HashCodeAndEqualsPlugin.Enhance\n            class Illegal<T extends Annotation> implements Factory<T> {\n\n                /**\n                 * The annotation type.\n                 */\n                private final Class<T> annotationType;\n\n                /**\n                 * Creates a factory that does not permit the usage of the represented annotation.\n                 *\n                 * @param annotationType The annotation type.\n                 */\n                public Illegal(Class<T> annotationType) {\n                    this.annotationType = annotationType;\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public Class<T> getAnnotationType() {\n                    return annotationType;\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable<T> annotation, AdviceType adviceType) {\n                    throw new IllegalStateException(\"Usage of \" + annotationType + \" is not allowed on \" + target);\n                }\n            }\n        }\n\n        /**\n         * Describes the sort of the executed advice.\n         */\n        enum Sort {\n\n            /**\n             * Indicates that an offset is mapped for an enter advice.\n             */\n            ENTER {\n                @Override\n                public boolean isPremature(MethodDescription methodDescription) {\n                    return methodDescription.isConstructor();\n                }\n            },\n\n            /**\n             * Indicates that an offset is mapped for an exit advice.\n             */\n            EXIT {\n                @Override\n                public boolean isPremature(MethodDescription methodDescription) {\n                    return false;\n                }\n            };\n\n            /**\n             * Checks if an advice is executed in a premature state, i.e. the instrumented method is a constructor where the super constructor is not\n             * yet invoked. In this case, the {@code this} reference is not yet initialized and therefore not available.\n             *\n             * @param methodDescription The instrumented method.\n             * @return {@code true} if the advice is executed premature for the instrumented method.\n             */\n            public abstract boolean isPremature(MethodDescription methodDescription);\n        }\n\n        /**\n         * An offset mapping for a given parameter of the instrumented method.\n         */\n        @HashCodeAndEqualsPlugin.Enhance\n        abstract class ForArgument implements OffsetMapping {\n\n            /**\n             * The type expected by the advice method.\n             */\n            protected final TypeDescription.Generic target;\n\n            /**\n             * Determines if the parameter is to be treated as read-only.\n             */\n            protected final boolean readOnly;\n\n            /**\n             * The typing to apply when assigning values.\n             */\n            private final Assigner.Typing typing;\n\n            /**\n             * Creates a new offset mapping for a parameter of the instrumented method.\n             *\n             * @param target   The type expected by the advice method.\n             * @param readOnly Determines if the parameter is to be treated as read-only.\n             * @param typing   The typing to apply.\n             */\n            protected ForArgument(TypeDescription.Generic target, boolean readOnly, Assigner.Typing typing) {\n                this.target = target;\n                this.readOnly = readOnly;\n                this.typing = typing;\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public Target resolve(TypeDescription instrumentedType,\n                                  MethodDescription instrumentedMethod,\n                                  Assigner assigner,\n                                  ArgumentHandler argumentHandler,\n                                  Sort sort) {\n                ParameterDescription parameterDescription = resolve(instrumentedMethod);\n                StackManipulation readAssignment = assigner.assign(parameterDescription.getType(), target, typing);\n                if (!readAssignment.isValid()) {\n                    throw new IllegalStateException(\"Cannot assign \" + parameterDescription + \" to \" + target);\n                } else if (readOnly) {\n                    return new Target.ForVariable.ReadOnly(parameterDescription.getType(), argumentHandler.argument(parameterDescription.getOffset()), readAssignment);\n                } else {\n                    StackManipulation writeAssignment = assigner.assign(target, parameterDescription.getType(), typing);\n                    if (!writeAssignment.isValid()) {\n                        throw new IllegalStateException(\"Cannot assign \" + parameterDescription + \" to \" + target);\n                    }\n                    return new Target.ForVariable.ReadWrite(parameterDescription.getType(), argumentHandler.argument(parameterDescription.getOffset()), readAssignment, writeAssignment);\n                }\n            }\n\n            /**\n             * Resolves the bound parameter.\n             *\n             * @param instrumentedMethod The instrumented method.\n             * @return The bound parameter.\n             */\n            protected abstract ParameterDescription resolve(MethodDescription instrumentedMethod);\n\n            /**\n             * An offset mapping for a parameter of the instrumented method with a specific index.\n             */\n            @HashCodeAndEqualsPlugin.Enhance\n            public static class Unresolved extends ForArgument {\n\n                /**\n                 * The index of the parameter.\n                 */\n                private final int index;\n\n                /**\n                 * {@code true} if the parameter binding is optional.\n                 */\n                private final boolean optional;\n\n                /**\n                 * Creates a new offset binding for a parameter with a given index.\n                 *\n                 * @param target     The target type.\n                 * @param annotation The annotation that triggers this binding.\n                 */\n                protected Unresolved(TypeDescription.Generic target, AnnotationDescription.Loadable<Argument> annotation) {\n                    this(target, annotation.getValue(Factory.ARGUMENT_READ_ONLY).resolve(Boolean.class),\n                        annotation.getValue(Factory.ARGUMENT_TYPING)\n                            .load(Argument.class.getClassLoader()).resolve(Assigner.Typing.class),\n                        annotation.getValue(Factory.ARGUMENT_VALUE).resolve(Integer.class),\n                        annotation.getValue(Factory.ARGUMENT_OPTIONAL).resolve(Boolean.class));\n                }\n\n                /**\n                 * Creates a new offset binding for a parameter with a given index.\n                 *\n                 * @param parameterDescription The parameter triggering this binding.\n                 */\n                protected Unresolved(ParameterDescription parameterDescription) {\n                    this(parameterDescription.getType(),\n                        true, Assigner.Typing.STATIC, parameterDescription.getIndex());\n                }\n\n                /**\n                 * Creates a non-optional offset binding for a parameter with a given index.\n                 *\n                 * @param target   The type expected by the advice method.\n                 * @param readOnly Determines if the parameter is to be treated as read-only.\n                 * @param typing   The typing to apply.\n                 * @param index    The index of the parameter.\n                 */\n                public Unresolved(TypeDescription.Generic target,\n                                  boolean readOnly, Assigner.Typing typing, int index) {\n                    this(target, readOnly, typing, index, false);\n                }\n\n                /**\n                 * Creates a new offset binding for a parameter with a given index.\n                 *\n                 * @param target   The type expected by the advice method.\n                 * @param readOnly Determines if the parameter is to be treated as read-only.\n                 * @param typing   The typing to apply.\n                 * @param index    The index of the parameter.\n                 * @param optional {@code true} if the parameter binding is optional.\n                 */\n                public Unresolved(TypeDescription.Generic target,\n                                  boolean readOnly, Assigner.Typing typing, int index, boolean optional) {\n                    super(target, readOnly, typing);\n                    this.index = index;\n                    this.optional = optional;\n                }\n\n                @Override\n                protected ParameterDescription resolve(MethodDescription instrumentedMethod) {\n                    ParameterList<?> parameters = instrumentedMethod.getParameters();\n                    if (parameters.size() <= index) {\n                        throw new IllegalStateException(instrumentedMethod + \" does not define an index \" + index);\n                    } else {\n                        return parameters.get(index);\n                    }\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                @Override\n                public Target resolve(TypeDescription instrumentedType,\n                                      MethodDescription instrumentedMethod,\n                                      Assigner assigner,\n                                      ArgumentHandler argumentHandler,\n                                      Sort sort) {\n                    if (optional && instrumentedMethod.getParameters().size() <= index) {\n                        return readOnly\n                            ? new Target.ForDefaultValue.ReadOnly(target)\n                            : new Target.ForDefaultValue.ReadWrite(target);\n                    }\n                    return super.resolve(instrumentedType, instrumentedMethod, assigner, argumentHandler, sort);\n                }\n\n                /**\n                 * A factory for a mapping of a parameter of the instrumented method.\n                 */\n                protected enum Factory implements OffsetMapping.Factory<Argument> {\n\n                    /**\n                     * The singleton instance.\n                     */\n                    INSTANCE;\n\n                    /**\n                     * A description of the {@link Argument#value()} method.\n                     */\n                    private static final MethodDescription.InDefinedShape ARGUMENT_VALUE;\n\n                    /**\n                     * A description of the {@link Argument#readOnly()} method.\n                     */\n                    private static final MethodDescription.InDefinedShape ARGUMENT_READ_ONLY;\n\n                    /**\n                     * A description of the {@link Argument#typing()} method.\n                     */\n                    private static final MethodDescription.InDefinedShape ARGUMENT_TYPING;\n\n                    /**\n                     * A description of the {@link Argument#optional()} method.\n                     */\n                    private static final MethodDescription.InDefinedShape ARGUMENT_OPTIONAL;\n\n                    /*\n                     * Resolves annotation properties.\n                     */\n                    static {\n                        MethodList<MethodDescription.InDefinedShape> methods = TypeDescription\n                            .ForLoadedType.of(Argument.class).getDeclaredMethods();\n                        ARGUMENT_VALUE = methods.filter(named(\"value\")).getOnly();\n                        ARGUMENT_READ_ONLY = methods.filter(named(\"readOnly\")).getOnly();\n                        ARGUMENT_TYPING = methods.filter(named(\"typing\")).getOnly();\n                        ARGUMENT_OPTIONAL = methods.filter(named(\"optional\")).getOnly();\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public Class<Argument> getAnnotationType() {\n                        return Argument.class;\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public OffsetMapping make(ParameterDescription.InDefinedShape target,\n                                              AnnotationDescription.Loadable<Argument> annotation,\n                                              AdviceType adviceType) {\n                        if (adviceType.isDelegation()\n                            && !annotation.getValue(ARGUMENT_READ_ONLY).resolve(Boolean.class)) {\n                            throw new IllegalStateException(\"Cannot define writable field access for \"\n                                + target + \" when using delegation\");\n                        } else {\n                            return new ForArgument.Unresolved(target.getType(), annotation);\n                        }\n                    }\n                }\n            }\n\n            /**\n             * An offset mapping for a specific parameter of the instrumented method.\n             */\n            @HashCodeAndEqualsPlugin.Enhance\n            public static class Resolved extends ForArgument {\n\n                /**\n                 * The parameter being bound.\n                 */\n                private final ParameterDescription parameterDescription;\n\n                /**\n                 * Creates an offset mapping that binds a parameter of the instrumented method.\n                 *\n                 * @param target               The type expected by the advice method.\n                 * @param readOnly             Determines if the parameter is to be treated as read-only.\n                 * @param typing               The typing to apply.\n                 * @param parameterDescription The parameter being bound.\n                 */\n                public Resolved(TypeDescription.Generic target, boolean readOnly, Assigner.Typing typing, ParameterDescription parameterDescription) {\n                    super(target, readOnly, typing);\n                    this.parameterDescription = parameterDescription;\n                }\n\n                @Override\n                protected ParameterDescription resolve(MethodDescription instrumentedMethod) {\n                    if (!parameterDescription.getDeclaringMethod().equals(instrumentedMethod)) {\n                        throw new IllegalStateException(parameterDescription + \" is not a parameter of \" + instrumentedMethod);\n                    }\n                    return parameterDescription;\n                }\n\n                /**\n                 * A factory for a parameter argument of the instrumented method.\n                 *\n                 * @param <T> The type of the bound annotation.\n                 */\n                @HashCodeAndEqualsPlugin.Enhance\n                public static class Factory<T extends Annotation> implements OffsetMapping.Factory<T> {\n\n                    /**\n                     * The annotation type.\n                     */\n                    private final Class<T> annotationType;\n\n                    /**\n                     * The bound parameter.\n                     */\n                    private final ParameterDescription parameterDescription;\n\n                    /**\n                     * {@code true} if the factory should create a read-only binding.\n                     */\n                    private final boolean readOnly;\n\n                    /**\n                     * The typing to use.\n                     */\n                    private final Assigner.Typing typing;\n\n                    /**\n                     * Creates a new factory for binding a parameter of the instrumented method with read-only semantics and static typing.\n                     *\n                     * @param annotationType       The annotation type.\n                     * @param parameterDescription The bound parameter.\n                     */\n                    public Factory(Class<T> annotationType, ParameterDescription parameterDescription) {\n                        this(annotationType, parameterDescription, true, Assigner.Typing.STATIC);\n                    }\n\n                    /**\n                     * Creates a new factory for binding a parameter of the instrumented method.\n                     *\n                     * @param annotationType       The annotation type.\n                     * @param parameterDescription The bound parameter.\n                     * @param readOnly             {@code true} if the factory should create a read-only binding.\n                     * @param typing               The typing to use.\n                     */\n                    public Factory(Class<T> annotationType, ParameterDescription parameterDescription,\n                                   boolean readOnly, Assigner.Typing typing) {\n                        this.annotationType = annotationType;\n                        this.parameterDescription = parameterDescription;\n                        this.readOnly = readOnly;\n                        this.typing = typing;\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public Class<T> getAnnotationType() {\n                        return annotationType;\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public OffsetMapping make(ParameterDescription.InDefinedShape target,\n                                              AnnotationDescription.Loadable<T> annotation,\n                                              AdviceType adviceType) {\n                        return new Resolved(target.getType(), readOnly, typing, parameterDescription);\n                    }\n                }\n            }\n        }\n\n        /**\n         * An offset mapping that provides access to the {@code this} reference of the instrumented method.\n         */\n        @HashCodeAndEqualsPlugin.Enhance\n        class ForThisReference implements OffsetMapping {\n\n            /**\n             * The type that the advice method expects for the {@code this} reference.\n             */\n            private final TypeDescription.Generic target;\n\n            /**\n             * Determines if the parameter is to be treated as read-only.\n             */\n            private final boolean readOnly;\n\n            /**\n             * The typing to apply.\n             */\n            private final Assigner.Typing typing;\n\n            /**\n             * {@code true} if the parameter should be bound to {@code null} if the instrumented method is static.\n             */\n            private final boolean optional;\n\n            /**\n             * Creates a new offset mapping for a {@code this} reference.\n             *\n             * @param target     The type that the advice method expects for the {@code this} reference.\n             * @param annotation The mapped annotation.\n             */\n            protected ForThisReference(TypeDescription.Generic target, AnnotationDescription.Loadable<This> annotation) {\n                this(target,\n                    annotation.getValue(Factory.THIS_READ_ONLY).resolve(Boolean.class),\n                    annotation.getValue(Factory.THIS_TYPING).load(This.class.getClassLoader()).resolve(Assigner.Typing.class),\n                    annotation.getValue(Factory.THIS_OPTIONAL).resolve(Boolean.class));\n            }\n\n            /**\n             * Creates a new offset mapping for a {@code this} reference.\n             *\n             * @param target   The type that the advice method expects for the {@code this} reference.\n             * @param readOnly Determines if the parameter is to be treated as read-only.\n             * @param typing   The typing to apply.\n             * @param optional {@code true} if the parameter should be bound to {@code null} if the instrumented method is static.\n             */\n            public ForThisReference(TypeDescription.Generic target,\n                                    boolean readOnly, Assigner.Typing typing, boolean optional) {\n                this.target = target;\n                this.readOnly = readOnly;\n                this.typing = typing;\n                this.optional = optional;\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public Target resolve(TypeDescription instrumentedType,\n                                  MethodDescription instrumentedMethod,\n                                  Assigner assigner,\n                                  ArgumentHandler argumentHandler,\n                                  Sort sort) {\n                if (instrumentedMethod.isStatic() || sort.isPremature(instrumentedMethod)) {\n                    if (optional) {\n                        return readOnly\n                            ? new Target.ForDefaultValue.ReadOnly(instrumentedType)\n                            : new Target.ForDefaultValue.ReadWrite(instrumentedType);\n                    } else {\n                        throw new IllegalStateException(\n                            \"Cannot map this reference for static method or constructor start: \" + instrumentedMethod);\n                    }\n                }\n                StackManipulation readAssignment = assigner.assign(instrumentedType.asGenericType(), target, typing);\n                if (!readAssignment.isValid()) {\n                    throw new IllegalStateException(\"Cannot assign \" + instrumentedType + \" to \" + target);\n                } else if (readOnly) {\n                    return new Target.ForVariable.ReadOnly(instrumentedType.asGenericType(),\n                        argumentHandler.argument(ArgumentHandler.THIS_REFERENCE), readAssignment);\n                } else {\n                    StackManipulation writeAssignment = assigner.assign(target, instrumentedType.asGenericType(), typing);\n                    if (!writeAssignment.isValid()) {\n                        throw new IllegalStateException(\"Cannot assign \" + target + \" to \" + instrumentedType);\n                    }\n                    return new Target.ForVariable.ReadWrite(instrumentedType.asGenericType(),\n                        argumentHandler.argument(ArgumentHandler.THIS_REFERENCE), readAssignment, writeAssignment);\n                }\n            }\n\n            /**\n             * A factory for creating a {@link ForThisReference} offset mapping.\n             */\n            protected enum Factory implements OffsetMapping.Factory<This> {\n\n                /**\n                 * The singleton instance.\n                 */\n                INSTANCE;\n\n                /**\n                 * A description of the {@link This#readOnly()} method.\n                 */\n                private static final MethodDescription.InDefinedShape THIS_READ_ONLY;\n\n                /**\n                 * A description of the {@link This#typing()} method.\n                 */\n                private static final MethodDescription.InDefinedShape THIS_TYPING;\n\n                /**\n                 * A description of the {@link This#optional()} method.\n                 */\n                private static final MethodDescription.InDefinedShape THIS_OPTIONAL;\n\n                /*\n                 * Resolves annotation properties.\n                 */\n                static {\n                    MethodList<MethodDescription.InDefinedShape> methods = TypeDescription.ForLoadedType.of(This.class).getDeclaredMethods();\n                    THIS_READ_ONLY = methods.filter(named(\"readOnly\")).getOnly();\n                    THIS_TYPING = methods.filter(named(\"typing\")).getOnly();\n                    THIS_OPTIONAL = methods.filter(named(\"optional\")).getOnly();\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public Class<This> getAnnotationType() {\n                    return This.class;\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public OffsetMapping make(ParameterDescription.InDefinedShape target,\n                                          AnnotationDescription.Loadable<This> annotation,\n                                          AdviceType adviceType) {\n                    if (adviceType.isDelegation() && !annotation.getValue(THIS_READ_ONLY).resolve(Boolean.class)) {\n                        throw new IllegalStateException(\"Cannot write to this reference for \" + target + \" in read-only context\");\n                    } else {\n                        return new ForThisReference(target.getType(), annotation);\n                    }\n                }\n            }\n        }\n\n        /**\n         * An offset mapping that maps an array containing all arguments of the instrumented method.\n         */\n        @HashCodeAndEqualsPlugin.Enhance\n        class ForAllArguments implements OffsetMapping {\n\n            /**\n             * The component target type.\n             */\n            private final TypeDescription.Generic target;\n\n            /**\n             * {@code true} if the array is read-only.\n             */\n            private final boolean readOnly;\n\n            /**\n             * The typing to apply.\n             */\n            private final Assigner.Typing typing;\n\n            /**\n             * {@code true} if a {@code null} value should be assigned if the\n             * instrumented method does not declare any parameters.\n             */\n            private final boolean nullIfEmpty;\n\n            /**\n             * Creates a new offset mapping for an array containing all arguments.\n             *\n             * @param target     The component target type.\n             * @param annotation The mapped annotation.\n             */\n            protected ForAllArguments(TypeDescription.Generic target,\n                                      AnnotationDescription.Loadable<AllArguments> annotation) {\n                this(target, annotation.getValue(Factory.ALL_ARGUMENTS_READ_ONLY).resolve(Boolean.class),\n                    annotation.getValue(Factory.ALL_ARGUMENTS_TYPING)\n                        .load(AllArguments.class.getClassLoader()).resolve(Assigner.Typing.class),\n                    annotation.getValue(Factory.ALL_ARGUMENTS_NULL_IF_EMPTY).resolve(Boolean.class));\n            }\n\n            /**\n             * Creates a new offset mapping for an array containing all arguments.\n             *\n             * @param target      The component target type.\n             * @param readOnly    {@code true} if the array is read-only.\n             * @param typing      The typing to apply.\n             * @param nullIfEmpty {@code true} if a {@code null} value should be assigned if the\n             *                    instrumented method does not declare any parameters.\n             */\n            public ForAllArguments(TypeDescription.Generic target, boolean readOnly, Assigner.Typing typing, boolean nullIfEmpty) {\n                this.target = target;\n                this.readOnly = readOnly;\n                this.typing = typing;\n                this.nullIfEmpty = nullIfEmpty;\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public Target resolve(TypeDescription instrumentedType,\n                                  MethodDescription instrumentedMethod,\n                                  Assigner assigner,\n                                  ArgumentHandler argumentHandler,\n                                  Sort sort) {\n                if (nullIfEmpty && instrumentedMethod.getParameters().isEmpty()) {\n                    return readOnly\n                        ? new Target.ForStackManipulation(NullConstant.INSTANCE)\n                        : new Target.ForStackManipulation.Writable(NullConstant.INSTANCE, Removal.SINGLE);\n                }\n                List<StackManipulation> valueReads = new ArrayList(instrumentedMethod.getParameters().size());\n                for (ParameterDescription parameterDescription : instrumentedMethod.getParameters()) {\n                    StackManipulation readAssignment = assigner.assign(parameterDescription.getType(), target, typing);\n                    if (!readAssignment.isValid()) {\n                        throw new IllegalStateException(\"Cannot assign \" + parameterDescription + \" to \" + target);\n                    }\n                    valueReads.add(new StackManipulation.Compound(MethodVariableAccess.of(parameterDescription.getType())\n                        .loadFrom(argumentHandler.argument(parameterDescription.getOffset())), readAssignment));\n                }\n                if (readOnly) {\n                    return new Target.ForArray.ReadOnly(target, valueReads);\n                } else {\n                    List<StackManipulation> valueWrites = new ArrayList(instrumentedMethod.getParameters().size());\n                    for (ParameterDescription parameterDescription : instrumentedMethod.getParameters()) {\n                        StackManipulation writeAssignment = assigner.assign(target, parameterDescription.getType(), typing);\n                        if (!writeAssignment.isValid()) {\n                            throw new IllegalStateException(\"Cannot assign \" + target + \" to \" + parameterDescription);\n                        }\n                        valueWrites.add(new StackManipulation.Compound(writeAssignment,\n                            MethodVariableAccess.of(parameterDescription.getType())\n                                .storeAt(argumentHandler.argument(parameterDescription.getOffset()))));\n                    }\n                    return new Target.ForArray.ReadWrite(target, valueReads, valueWrites);\n                }\n            }\n\n            /**\n             * A factory for an offset mapping that maps all arguments values of the instrumented method.\n             */\n            protected enum Factory implements OffsetMapping.Factory<AllArguments> {\n\n                /**\n                 * The singleton instance.\n                 */\n                INSTANCE;\n\n                /**\n                 * A description of the {@link AllArguments#readOnly()} method.\n                 */\n                private static final MethodDescription.InDefinedShape ALL_ARGUMENTS_READ_ONLY;\n\n                /**\n                 * A description of the {@link AllArguments#typing()} method.\n                 */\n                private static final MethodDescription.InDefinedShape ALL_ARGUMENTS_TYPING;\n\n                /**\n                 * A description of the {@link AllArguments#nullIfEmpty()} method.\n                 */\n                private static final MethodDescription.InDefinedShape ALL_ARGUMENTS_NULL_IF_EMPTY;\n\n                /*\n                 * Resolves annotation properties.\n                 */\n                static {\n                    MethodList<MethodDescription.InDefinedShape> methods = TypeDescription.ForLoadedType.of(AllArguments.class).getDeclaredMethods();\n                    ALL_ARGUMENTS_READ_ONLY = methods.filter(named(\"readOnly\")).getOnly();\n                    ALL_ARGUMENTS_TYPING = methods.filter(named(\"typing\")).getOnly();\n                    ALL_ARGUMENTS_NULL_IF_EMPTY = methods.filter(named(\"nullIfEmpty\")).getOnly();\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public Class<AllArguments> getAnnotationType() {\n                    return AllArguments.class;\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public OffsetMapping make(ParameterDescription.InDefinedShape target,\n                                          AnnotationDescription.Loadable<AllArguments> annotation,\n                                          AdviceType adviceType) {\n                    if (!target.getType().represents(Object.class) && !target.getType().isArray()) {\n                        throw new IllegalStateException(\"Cannot use AllArguments annotation on a non-array type\");\n                    } else if (adviceType.isDelegation() && !annotation.getValue(ALL_ARGUMENTS_READ_ONLY).resolve(Boolean.class)) {\n                        throw new IllegalStateException(\"Cannot define writable field access for \" + target);\n                    } else {\n                        return new ForAllArguments(target.getType().represents(Object.class)\n                            ? TypeDescription.Generic.OBJECT\n                            : target.getType().getComponentType(), annotation);\n                    }\n                }\n            }\n        }\n\n        /**\n         * Maps the declaring type of the instrumented method.\n         */\n        enum ForInstrumentedType implements OffsetMapping {\n\n            /**\n             * The singleton instance.\n             */\n            INSTANCE;\n\n            /**\n             * {@inheritDoc}\n             */\n            public Target resolve(TypeDescription instrumentedType,\n                                  MethodDescription instrumentedMethod,\n                                  Assigner assigner,\n                                  ArgumentHandler argumentHandler,\n                                  Sort sort) {\n                return Target.ForStackManipulation.of(instrumentedType);\n            }\n        }\n\n        /**\n         * Maps a constant representing the instrumented method.\n         */\n        enum ForInstrumentedMethod implements OffsetMapping {\n\n            /**\n             * A constant that must be a {@link Method} instance.\n             */\n            METHOD {\n                @Override\n                protected boolean isRepresentable(MethodDescription instrumentedMethod) {\n                    return instrumentedMethod.isMethod();\n                }\n            },\n\n            /**\n             * A constant that must be a {@link Constructor} instance.\n             */\n            CONSTRUCTOR {\n                @Override\n                protected boolean isRepresentable(MethodDescription instrumentedMethod) {\n                    return instrumentedMethod.isConstructor();\n                }\n            },\n\n            /**\n             * A constant that must be a {@code java.lang.reflect.Executable} instance.\n             */\n            EXECUTABLE {\n                @Override\n                protected boolean isRepresentable(MethodDescription instrumentedMethod) {\n                    return true;\n                }\n            };\n\n            /**\n             * {@inheritDoc}\n             */\n            public Target resolve(TypeDescription instrumentedType,\n                                  MethodDescription instrumentedMethod,\n                                  Assigner assigner,\n                                  ArgumentHandler argumentHandler,\n                                  Sort sort) {\n                if (!isRepresentable(instrumentedMethod)) {\n                    throw new IllegalStateException(\"Cannot represent \" + instrumentedMethod + \" as given method constant\");\n                }\n                return Target.ForStackManipulation.of(instrumentedMethod.asDefined());\n            }\n\n            /**\n             * Checks if the supplied method is representable for the assigned offset mapping.\n             *\n             * @param instrumentedMethod The instrumented method to represent.\n             * @return {@code true} if this method is representable.\n             */\n            protected abstract boolean isRepresentable(MethodDescription instrumentedMethod);\n        }\n\n        /**\n         * An offset mapping for a field.\n         */\n        @HashCodeAndEqualsPlugin.Enhance\n        abstract class ForField implements OffsetMapping {\n\n            /**\n             * The {@link FieldValue#value()} method.\n             */\n            private static final MethodDescription.InDefinedShape VALUE;\n\n            /**\n             * The {@link FieldValue#declaringType()}} method.\n             */\n            private static final MethodDescription.InDefinedShape DECLARING_TYPE;\n\n            /**\n             * The {@link FieldValue#readOnly()}} method.\n             */\n            private static final MethodDescription.InDefinedShape READ_ONLY;\n\n            /**\n             * The {@link FieldValue#typing()}} method.\n             */\n            private static final MethodDescription.InDefinedShape TYPING;\n\n            /*\n             * Looks up all annotation properties to avoid loading of the declaring field type.\n             */\n            static {\n                MethodList<MethodDescription.InDefinedShape> methods = TypeDescription.ForLoadedType\n                    .of(FieldValue.class).getDeclaredMethods();\n                VALUE = methods.filter(named(\"value\")).getOnly();\n                DECLARING_TYPE = methods.filter(named(\"declaringType\")).getOnly();\n                READ_ONLY = methods.filter(named(\"readOnly\")).getOnly();\n                TYPING = methods.filter(named(\"typing\")).getOnly();\n            }\n\n            /**\n             * The expected type that the field can be assigned to.\n             */\n            private final TypeDescription.Generic target;\n\n            /**\n             * {@code true} if this mapping is read-only.\n             */\n            private final boolean readOnly;\n\n            /**\n             * The typing to apply.\n             */\n            private final Assigner.Typing typing;\n\n            /**\n             * Creates an offset mapping for a field.\n             *\n             * @param target   The target type.\n             * @param readOnly {@code true} if this mapping is read-only.\n             * @param typing   The typing to apply.\n             */\n            public ForField(TypeDescription.Generic target, boolean readOnly, Assigner.Typing typing) {\n                this.target = target;\n                this.readOnly = readOnly;\n                this.typing = typing;\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public Target resolve(TypeDescription instrumentedType,\n                                  MethodDescription instrumentedMethod,\n                                  Assigner assigner,\n                                  ArgumentHandler argumentHandler,\n                                  Sort sort) {\n                FieldDescription fieldDescription = resolve(instrumentedType, instrumentedMethod);\n                if (!fieldDescription.isStatic() && instrumentedMethod.isStatic()) {\n                    throw new IllegalStateException(\"Cannot read non-static field \" + fieldDescription + \" from static method \" + instrumentedMethod);\n                } else if (sort.isPremature(instrumentedMethod) && !fieldDescription.isStatic()) {\n                    throw new IllegalStateException(\"Cannot access non-static field before calling constructor: \" + instrumentedMethod);\n                }\n                StackManipulation readAssignment = assigner.assign(fieldDescription.getType(), target, typing);\n                if (!readAssignment.isValid()) {\n                    throw new IllegalStateException(\"Cannot assign \" + fieldDescription + \" to \" + target);\n                } else if (readOnly) {\n                    return new Target.ForField.ReadOnly(fieldDescription, readAssignment);\n                } else {\n                    StackManipulation writeAssignment = assigner.assign(target, fieldDescription.getType(), typing);\n                    if (!writeAssignment.isValid()) {\n                        throw new IllegalStateException(\"Cannot assign \" + target + \" to \" + fieldDescription);\n                    }\n                    return new Target.ForField.ReadWrite(fieldDescription.asDefined(), readAssignment, writeAssignment);\n                }\n            }\n\n            /**\n             * Resolves the field being bound.\n             *\n             * @param instrumentedType   The instrumented type.\n             * @param instrumentedMethod The instrumented method.\n             * @return The field being bound.\n             */\n            protected abstract FieldDescription resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod);\n\n            /**\n             * An offset mapping for a field that is resolved from the instrumented type by its name.\n             */\n            @HashCodeAndEqualsPlugin.Enhance\n            public abstract static class Unresolved extends ForField {\n\n                /**\n                 * Indicates that a name should be extracted from an accessor method.\n                 */\n                protected static final String BEAN_PROPERTY = \"\";\n\n                /**\n                 * The name of the field.\n                 */\n                private final String name;\n\n                /**\n                 * Creates an offset mapping for a field that is not yet resolved.\n                 *\n                 * @param target   The target type.\n                 * @param readOnly {@code true} if this mapping is read-only.\n                 * @param typing   The typing to apply.\n                 * @param name     The name of the field.\n                 */\n                public Unresolved(TypeDescription.Generic target, boolean readOnly, Assigner.Typing typing, String name) {\n                    super(target, readOnly, typing);\n                    this.name = name;\n                }\n\n                @Override\n                protected FieldDescription resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod) {\n                    FieldLocator locator = fieldLocator(instrumentedType);\n                    FieldLocator.Resolution resolution = name.equals(BEAN_PROPERTY)\n                        ? resolveAccessor(locator, instrumentedMethod)\n                        : locator.locate(name);\n                    if (!resolution.isResolved()) {\n                        throw new IllegalStateException(\"Cannot locate field named \" + name + \" for \" + instrumentedType);\n                    } else {\n                        return resolution.getField();\n                    }\n                }\n\n                /**\n                 * Resolves a field locator for a potential accessor method.\n                 *\n                 * @param fieldLocator      The field locator to use.\n                 * @param methodDescription The method description that is the potential accessor.\n                 * @return A resolution for a field locator.\n                 */\n                private static FieldLocator.Resolution resolveAccessor(FieldLocator fieldLocator, MethodDescription methodDescription) {\n                    String fieldName;\n                    if (isSetter().matches(methodDescription)) {\n                        fieldName = methodDescription.getInternalName().substring(3);\n                    } else if (isGetter().matches(methodDescription)) {\n                        fieldName = methodDescription.getInternalName().substring(methodDescription.getInternalName().startsWith(\"is\") ? 2 : 3);\n                    } else {\n                        return FieldLocator.Resolution.Illegal.INSTANCE;\n                    }\n                    return fieldLocator.locate(Character.toLowerCase(fieldName.charAt(0)) + fieldName.substring(1));\n                }\n\n                /**\n                 * Returns a field locator for this instance.\n                 *\n                 * @param instrumentedType The instrumented type.\n                 * @return An appropriate field locator.\n                 */\n                protected abstract FieldLocator fieldLocator(TypeDescription instrumentedType);\n\n                /**\n                 * An offset mapping for a field with an implicit declaring type.\n                 */\n                public static class WithImplicitType extends Unresolved {\n\n                    /**\n                     * Creates an offset mapping for a field with an implicit declaring type.\n                     *\n                     * @param target     The target type.\n                     * @param annotation The annotation to represent.\n                     */\n                    protected WithImplicitType(TypeDescription.Generic target, AnnotationDescription.Loadable<FieldValue> annotation) {\n                        this(target,\n                            annotation.getValue(READ_ONLY).resolve(Boolean.class),\n                            annotation.getValue(TYPING).load(Assigner.Typing.class.getClassLoader()).resolve(Assigner.Typing.class),\n                            annotation.getValue(VALUE).resolve(String.class));\n                    }\n\n                    /**\n                     * Creates an offset mapping for a field with an implicit declaring type.\n                     *\n                     * @param target   The target type.\n                     * @param name     The name of the field.\n                     * @param readOnly {@code true} if the field is read-only.\n                     * @param typing   The typing to apply.\n                     */\n                    public WithImplicitType(TypeDescription.Generic target, boolean readOnly, Assigner.Typing typing, String name) {\n                        super(target, readOnly, typing, name);\n                    }\n\n                    @Override\n                    protected FieldLocator fieldLocator(TypeDescription instrumentedType) {\n                        return new FieldLocator.ForClassHierarchy(instrumentedType);\n                    }\n                }\n\n                /**\n                 * An offset mapping for a field with an explicit declaring type.\n                 */\n                @HashCodeAndEqualsPlugin.Enhance\n                public static class WithExplicitType extends Unresolved {\n\n                    /**\n                     * The type declaring the field.\n                     */\n                    private final TypeDescription declaringType;\n\n                    /**\n                     * Creates an offset mapping for a field with an explicit declaring type.\n                     *\n                     * @param target        The target type.\n                     * @param annotation    The annotation to represent.\n                     * @param declaringType The field's declaring type.\n                     */\n                    protected WithExplicitType(TypeDescription.Generic target,\n                                               AnnotationDescription.Loadable<FieldValue> annotation,\n                                               TypeDescription declaringType) {\n                        this(target,\n                            annotation.getValue(READ_ONLY).resolve(Boolean.class),\n                            annotation.getValue(TYPING).load(Assigner.Typing.class.getClassLoader()).resolve(Assigner.Typing.class),\n                            annotation.getValue(VALUE).resolve(String.class),\n                            declaringType);\n                    }\n\n                    /**\n                     * Creates an offset mapping for a field with an explicit declaring type.\n                     *\n                     * @param target        The target type.\n                     * @param name          The name of the field.\n                     * @param readOnly      {@code true} if the field is read-only.\n                     * @param typing        The typing to apply.\n                     * @param declaringType The field's declaring type.\n                     */\n                    public WithExplicitType(TypeDescription.Generic target,\n                                            boolean readOnly,\n                                            Assigner.Typing typing,\n                                            String name,\n                                            TypeDescription declaringType) {\n                        super(target, readOnly, typing, name);\n                        this.declaringType = declaringType;\n                    }\n\n                    @Override\n                    protected FieldLocator fieldLocator(TypeDescription instrumentedType) {\n                        if (!declaringType.represents(TargetType.class) && !instrumentedType.isAssignableTo(declaringType)) {\n                            throw new IllegalStateException(declaringType + \" is no super type of \" + instrumentedType);\n                        }\n                        return new FieldLocator.ForExactType(TargetType.resolve(declaringType, instrumentedType));\n                    }\n                }\n\n                /**\n                 * A factory for a {@link Unresolved} offset mapping.\n                 */\n                protected enum Factory implements OffsetMapping.Factory<FieldValue> {\n\n                    /**\n                     * The singleton instance.\n                     */\n                    INSTANCE;\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public Class<FieldValue> getAnnotationType() {\n                        return FieldValue.class;\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public OffsetMapping make(ParameterDescription.InDefinedShape target,\n                                              AnnotationDescription.Loadable<FieldValue> annotation,\n                                              AdviceType adviceType) {\n                        if (adviceType.isDelegation() && !annotation.getValue(ForField.READ_ONLY).resolve(Boolean.class)) {\n                            throw new IllegalStateException(\"Cannot write to field for \" + target + \" in read-only context\");\n                        } else {\n                            TypeDescription declaringType = annotation.getValue(DECLARING_TYPE).resolve(TypeDescription.class);\n                            return declaringType.represents(void.class)\n                                ? new WithImplicitType(target.getType(), annotation)\n                                : new WithExplicitType(target.getType(), annotation, declaringType);\n                        }\n                    }\n                }\n            }\n\n            /**\n             * A binding for an offset mapping that represents a specific field.\n             */\n            @HashCodeAndEqualsPlugin.Enhance\n            public static class Resolved extends ForField {\n\n                /**\n                 * The accessed field.\n                 */\n                private final FieldDescription fieldDescription;\n\n                /**\n                 * Creates a resolved offset mapping for a field.\n                 *\n                 * @param target           The target type.\n                 * @param readOnly         {@code true} if this mapping is read-only.\n                 * @param typing           The typing to apply.\n                 * @param fieldDescription The accessed field.\n                 */\n                public Resolved(TypeDescription.Generic target, boolean readOnly, Assigner.Typing typing, FieldDescription fieldDescription) {\n                    super(target, readOnly, typing);\n                    this.fieldDescription = fieldDescription;\n                }\n\n                @Override\n                protected FieldDescription resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod) {\n                    if (!fieldDescription.isStatic() && !fieldDescription.getDeclaringType().asErasure().isAssignableFrom(instrumentedType)) {\n                        throw new IllegalStateException(fieldDescription + \" is no member of \" + instrumentedType);\n                    } else if (!fieldDescription.isAccessibleTo(instrumentedType)) {\n                        throw new IllegalStateException(\"Cannot access \" + fieldDescription + \" from \" + instrumentedType);\n                    }\n                    return fieldDescription;\n                }\n\n                /**\n                 * A factory that binds a field.\n                 *\n                 * @param <T> The annotation type this factory binds.\n                 */\n                @HashCodeAndEqualsPlugin.Enhance\n                public static class Factory<T extends Annotation> implements OffsetMapping.Factory<T> {\n\n                    /**\n                     * The annotation type.\n                     */\n                    private final Class<T> annotationType;\n\n                    /**\n                     * The field to be bound.\n                     */\n                    private final FieldDescription fieldDescription;\n\n                    /**\n                     * {@code true} if this factory should create a read-only binding.\n                     */\n                    private final boolean readOnly;\n\n                    /**\n                     * The typing to use.\n                     */\n                    private final Assigner.Typing typing;\n\n                    /**\n                     * Creates a new factory for binding a specific field with read-only semantics and static typing.\n                     *\n                     * @param annotationType   The annotation type.\n                     * @param fieldDescription The field to bind.\n                     */\n                    public Factory(Class<T> annotationType, FieldDescription fieldDescription) {\n                        this(annotationType, fieldDescription, true, Assigner.Typing.STATIC);\n                    }\n\n                    /**\n                     * Creates a new factory for binding a specific field.\n                     *\n                     * @param annotationType   The annotation type.\n                     * @param fieldDescription The field to bind.\n                     * @param readOnly         {@code true} if this factory should create a read-only binding.\n                     * @param typing           The typing to use.\n                     */\n                    public Factory(Class<T> annotationType, FieldDescription fieldDescription, boolean readOnly, Assigner.Typing typing) {\n                        this.annotationType = annotationType;\n                        this.fieldDescription = fieldDescription;\n                        this.readOnly = readOnly;\n                        this.typing = typing;\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public Class<T> getAnnotationType() {\n                        return annotationType;\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public OffsetMapping make(ParameterDescription.InDefinedShape target,\n                                              AnnotationDescription.Loadable<T> annotation,\n                                              AdviceType adviceType) {\n                        return new Resolved(target.getType(), readOnly, typing, fieldDescription);\n                    }\n                }\n            }\n        }\n\n        /**\n         * An offset mapping for the {@link Advice.Origin} annotation.\n         */\n        @HashCodeAndEqualsPlugin.Enhance\n        class ForOrigin implements OffsetMapping {\n\n            /**\n             * The delimiter character.\n             */\n            private static final char DELIMITER = '#';\n\n            /**\n             * The escape character.\n             */\n            private static final char ESCAPE = '\\\\';\n\n            /**\n             * The renderers to apply.\n             */\n            private final List<Renderer> renderers;\n\n            /**\n             * Creates a new offset mapping for an origin value.\n             *\n             * @param renderers The renderers to apply.\n             */\n            public ForOrigin(List<Renderer> renderers) {\n                this.renderers = renderers;\n            }\n\n            /**\n             * Parses a pattern of an origin annotation.\n             *\n             * @param pattern The supplied pattern.\n             * @return An appropriate offset mapping.\n             */\n            public static OffsetMapping parse(String pattern) {\n                if (pattern.equals(Origin.DEFAULT)) {\n                    return new ForOrigin(Collections.singletonList(Renderer.ForStringRepresentation.INSTANCE));\n                } else {\n                    List<Renderer> renderers = new ArrayList<>(pattern.length());\n                    int from = 0;\n                    for (int to = pattern.indexOf(DELIMITER); to != -1; to = pattern.indexOf(DELIMITER, from)) {\n                        if (to != 0 && pattern.charAt(to - 1) == ESCAPE && (to == 1 || pattern.charAt(to - 2) != ESCAPE)) {\n                            renderers.add(new Renderer.ForConstantValue(pattern.substring(from, Math.max(0, to - 1)) + DELIMITER));\n                            from = to + 1;\n                            continue;\n                        } else if (pattern.length() == to + 1) {\n                            throw new IllegalStateException(\"Missing sort descriptor for \" + pattern + \" at index \" + to);\n                        }\n                        renderers.add(new Renderer.ForConstantValue(pattern.substring(from, to).replace(\"\" + ESCAPE + ESCAPE, \"\" + ESCAPE)));\n                        switch (pattern.charAt(to + 1)) {\n                            case Renderer.ForMethodName.SYMBOL:\n                                renderers.add(Renderer.ForMethodName.INSTANCE);\n                                break;\n                            case Renderer.ForTypeName.SYMBOL:\n                                renderers.add(Renderer.ForTypeName.INSTANCE);\n                                break;\n                            case Renderer.ForDescriptor.SYMBOL:\n                                renderers.add(Renderer.ForDescriptor.INSTANCE);\n                                break;\n                            case Renderer.ForReturnTypeName.SYMBOL:\n                                renderers.add(Renderer.ForReturnTypeName.INSTANCE);\n                                break;\n                            case Renderer.ForJavaSignature.SYMBOL:\n                                renderers.add(Renderer.ForJavaSignature.INSTANCE);\n                                break;\n                            case Renderer.ForPropertyName.SYMBOL:\n                                renderers.add(Renderer.ForPropertyName.INSTANCE);\n                                break;\n                            default:\n                                throw new IllegalStateException(\"Illegal sort descriptor \" + pattern.charAt(to + 1) + \" for \" + pattern);\n                        }\n                        from = to + 2;\n                    }\n                    renderers.add(new Renderer.ForConstantValue(pattern.substring(from)));\n                    return new ForOrigin(renderers);\n                }\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public Target resolve(TypeDescription instrumentedType,\n                                  MethodDescription instrumentedMethod,\n                                  Assigner assigner,\n                                  ArgumentHandler argumentHandler,\n                                  Sort sort) {\n                StringBuilder stringBuilder = new StringBuilder();\n                for (Renderer renderer : renderers) {\n                    stringBuilder.append(renderer.apply(instrumentedType, instrumentedMethod));\n                }\n                return Target.ForStackManipulation.of(stringBuilder.toString());\n            }\n\n            /**\n             * A renderer for an origin pattern element.\n             */\n            public interface Renderer {\n\n                /**\n                 * Returns a string representation for this renderer.\n                 *\n                 * @param instrumentedType   The instrumented type.\n                 * @param instrumentedMethod The instrumented method.\n                 * @return The string representation.\n                 */\n                String apply(TypeDescription instrumentedType, MethodDescription instrumentedMethod);\n\n                /**\n                 * A renderer for a method's internal name.\n                 */\n                enum ForMethodName implements Renderer {\n\n                    /**\n                     * The singleton instance.\n                     */\n                    INSTANCE;\n\n                    /**\n                     * The method name symbol.\n                     */\n                    public static final char SYMBOL = 'm';\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public String apply(TypeDescription instrumentedType, MethodDescription instrumentedMethod) {\n                        return instrumentedMethod.getInternalName();\n                    }\n                }\n\n                /**\n                 * A renderer for a method declaring type's binary name.\n                 */\n                enum ForTypeName implements Renderer {\n\n                    /**\n                     * The singleton instance.\n                     */\n                    INSTANCE;\n\n                    /**\n                     * The type name symbol.\n                     */\n                    public static final char SYMBOL = 't';\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public String apply(TypeDescription instrumentedType, MethodDescription instrumentedMethod) {\n                        return instrumentedType.getName();\n                    }\n                }\n\n                /**\n                 * A renderer for a method descriptor.\n                 */\n                enum ForDescriptor implements Renderer {\n\n                    /**\n                     * The singleton instance.\n                     */\n                    INSTANCE;\n\n                    /**\n                     * The descriptor symbol.\n                     */\n                    public static final char SYMBOL = 'd';\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public String apply(TypeDescription instrumentedType, MethodDescription instrumentedMethod) {\n                        return instrumentedMethod.getDescriptor();\n                    }\n                }\n\n                /**\n                 * A renderer for a method's Java signature in binary form.\n                 */\n                enum ForJavaSignature implements Renderer {\n\n                    /**\n                     * The singleton instance.\n                     */\n                    INSTANCE;\n\n                    /**\n                     * The signature symbol.\n                     */\n                    public static final char SYMBOL = 's';\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public String apply(TypeDescription instrumentedType, MethodDescription instrumentedMethod) {\n                        StringBuilder stringBuilder = new StringBuilder(\"(\");\n                        boolean comma = false;\n                        for (TypeDescription typeDescription : instrumentedMethod.getParameters().asTypeList().asErasures()) {\n                            if (comma) {\n                                stringBuilder.append(',');\n                            } else {\n                                comma = true;\n                            }\n                            stringBuilder.append(typeDescription.getName());\n                        }\n                        return stringBuilder.append(')').toString();\n                    }\n                }\n\n                /**\n                 * A renderer for a method's return type in binary form.\n                 */\n                enum ForReturnTypeName implements Renderer {\n\n                    /**\n                     * The singleton instance.\n                     */\n                    INSTANCE;\n\n                    /**\n                     * The return type symbol.\n                     */\n                    public static final char SYMBOL = 'r';\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public String apply(TypeDescription instrumentedType, MethodDescription instrumentedMethod) {\n                        return instrumentedMethod.getReturnType().asErasure().getName();\n                    }\n                }\n\n                /**\n                 * A renderer for a method's {@link Object#toString()} representation.\n                 */\n                enum ForStringRepresentation implements Renderer {\n\n                    /**\n                     * The singleton instance.\n                     */\n                    INSTANCE;\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public String apply(TypeDescription instrumentedType, MethodDescription instrumentedMethod) {\n                        return instrumentedMethod.toString();\n                    }\n                }\n\n                /**\n                 * A renderer for a constant value.\n                 */\n                @HashCodeAndEqualsPlugin.Enhance\n                class ForConstantValue implements Renderer {\n\n                    /**\n                     * The constant value.\n                     */\n                    private final String value;\n\n                    /**\n                     * Creates a new renderer for a constant value.\n                     *\n                     * @param value The constant value.\n                     */\n                    public ForConstantValue(String value) {\n                        this.value = value;\n                    }\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public String apply(TypeDescription instrumentedType, MethodDescription instrumentedMethod) {\n                        return value;\n                    }\n                }\n\n                /**\n                 * A renderer for a property name.\n                 */\n                enum ForPropertyName implements Renderer {\n\n                    /**\n                     * The singleton instance.\n                     */\n                    INSTANCE;\n\n                    /**\n                     * The signature symbol.\n                     */\n                    public static final char SYMBOL = 'p';\n\n                    /**\n                     * {@inheritDoc}\n                     */\n                    public String apply(TypeDescription instrumentedType, MethodDescription instrumentedMethod) {\n                        return FieldAccessor.FieldNameExtractor.ForBeanProperty.INSTANCE.resolve(instrumentedMethod);\n                    }\n                }\n            }\n\n            /**\n             * A factory for a method origin.\n             */\n            protected enum Factory implements OffsetMapping.Factory<Origin> {\n\n                /**\n                 * The singleton instance.\n                 */\n                INSTANCE;\n\n                /**\n                 * A description of the {@link Origin#value()} method.\n                 */\n                private static final MethodDescription.InDefinedShape ORIGIN_VALUE = TypeDescription.ForLoadedType.of(Origin.class)\n                    .getDeclaredMethods()\n                    .filter(named(\"value\"))\n                    .getOnly();\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public Class<Origin> getAnnotationType() {\n                    return Origin.class;\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public OffsetMapping make(ParameterDescription.InDefinedShape target,\n                                          AnnotationDescription.Loadable<Origin> annotation,\n                                          AdviceType adviceType) {\n                    if (target.getType().asErasure().represents(Class.class)) {\n                        return OffsetMapping.ForInstrumentedType.INSTANCE;\n                    } else if (target.getType().asErasure().represents(Method.class)) {\n                        return OffsetMapping.ForInstrumentedMethod.METHOD;\n                    } else if (target.getType().asErasure().represents(Constructor.class)) {\n                        return OffsetMapping.ForInstrumentedMethod.CONSTRUCTOR;\n                    } else if (JavaType.EXECUTABLE.getTypeStub().equals(target.getType().asErasure())) {\n                        return OffsetMapping.ForInstrumentedMethod.EXECUTABLE;\n                    } else if (target.getType().asErasure().isAssignableFrom(String.class)) {\n                        return ForOrigin.parse(annotation.getValue(ORIGIN_VALUE).resolve(String.class));\n                    } else {\n                        throw new IllegalStateException(\"Non-supported type \" + target.getType() + \" for @Origin annotation\");\n                    }\n                }\n            }\n        }\n\n        /**\n         * An offset mapping for a parameter where assignments are fully ignored and that always return the parameter type's default value.\n         */\n        @HashCodeAndEqualsPlugin.Enhance\n        class ForUnusedValue implements OffsetMapping {\n\n            /**\n             * The unused type.\n             */\n            private final TypeDefinition target;\n\n            /**\n             * Creates a new offset mapping for an unused type.\n             *\n             * @param target The unused type.\n             */\n            public ForUnusedValue(TypeDefinition target) {\n                this.target = target;\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public Target resolve(TypeDescription instrumentedType,\n                                  MethodDescription instrumentedMethod,\n                                  Assigner assigner,\n                                  ArgumentHandler argumentHandler,\n                                  Sort sort) {\n                return new Target.ForDefaultValue.ReadWrite(target);\n            }\n\n            /**\n             * A factory for an offset mapping for an unused value.\n             */\n            protected enum Factory implements OffsetMapping.Factory<Unused> {\n\n                /**\n                 * A factory for representing an unused value.\n                 */\n                INSTANCE;\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public Class<Unused> getAnnotationType() {\n                    return Unused.class;\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public OffsetMapping make(ParameterDescription.InDefinedShape target,\n                                          AnnotationDescription.Loadable<Unused> annotation,\n                                          AdviceType adviceType) {\n                    return new ForUnusedValue(target.getType());\n                }\n            }\n        }\n\n        /**\n         * An offset mapping for a parameter where assignments are fully ignored and that is assigned a boxed version of the instrumented\n         * method's return value or {@code null} if the return type is not primitive or {@code void}.\n         */\n        enum ForStubValue implements OffsetMapping, Factory<StubValue> {\n\n            /**\n             * The singleton instance.\n             */\n            INSTANCE;\n\n            /**\n             * {@inheritDoc}\n             */\n            public Target resolve(TypeDescription instrumentedType,\n                                  MethodDescription instrumentedMethod,\n                                  Assigner assigner,\n                                  ArgumentHandler argumentHandler,\n                                  Sort sort) {\n                return new Target.ForDefaultValue.ReadOnly(instrumentedMethod.getReturnType(), assigner.assign(instrumentedMethod.getReturnType(),\n                    TypeDescription.Generic.OBJECT,\n                    Assigner.Typing.DYNAMIC));\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public Class<StubValue> getAnnotationType() {\n                return StubValue.class;\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public OffsetMapping make(ParameterDescription.InDefinedShape target,\n                                      AnnotationDescription.Loadable<StubValue> annotation,\n                                      AdviceType adviceType) {\n                if (!target.getType().represents(Object.class)) {\n                    throw new IllegalStateException(\"Cannot use StubValue on non-Object parameter type \" + target);\n                } else {\n                    return this;\n                }\n            }\n        }\n\n        /**\n         * An offset mapping that provides access to the value that is returned by the enter advice.\n         */\n        @HashCodeAndEqualsPlugin.Enhance\n        class ForEnterValue implements OffsetMapping {\n\n            /**\n             * The represented target type.\n             */\n            private final TypeDescription.Generic target;\n\n            /**\n             * The enter type.\n             */\n            private final TypeDescription.Generic enterType;\n\n            /**\n             * {@code true} if the annotated value is read-only.\n             */\n            private final boolean readOnly;\n\n            /**\n             * The typing to apply.\n             */\n            private final Assigner.Typing typing;\n\n            /**\n             * Creates a new offset mapping for the enter type.\n             *\n             * @param target     The represented target type.\n             * @param enterType  The enter type.\n             * @param annotation The represented annotation.\n             */\n            protected ForEnterValue(TypeDescription.Generic target, TypeDescription.Generic enterType, AnnotationDescription.Loadable<Enter> annotation) {\n                this(target,\n                    enterType,\n                    annotation.getValue(Factory.ENTER_READ_ONLY).resolve(Boolean.class),\n                    annotation.getValue(Factory.ENTER_TYPING).load(Enter.class.getClassLoader()).resolve(Assigner.Typing.class));\n            }\n\n            /**\n             * Creates a new offset mapping for the enter type.\n             *\n             * @param target    The represented target type.\n             * @param enterType The enter type.\n             * @param readOnly  {@code true} if the annotated value is read-only.\n             * @param typing    The typing to apply.\n             */\n            public ForEnterValue(TypeDescription.Generic target, TypeDescription.Generic enterType, boolean readOnly, Assigner.Typing typing) {\n                this.target = target;\n                this.enterType = enterType;\n                this.readOnly = readOnly;\n                this.typing = typing;\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public Target resolve(TypeDescription instrumentedType,\n                                  MethodDescription instrumentedMethod,\n                                  Assigner assigner,\n                                  ArgumentHandler argumentHandler,\n                                  Sort sort) {\n                StackManipulation readAssignment = assigner.assign(enterType, target, typing);\n                if (!readAssignment.isValid()) {\n                    throw new IllegalStateException(\"Cannot assign \" + enterType + \" to \" + target);\n                } else if (readOnly) {\n                    return new Target.ForVariable.ReadOnly(target, argumentHandler.enter(), readAssignment);\n                } else {\n                    StackManipulation writeAssignment = assigner.assign(target, enterType, typing);\n                    if (!writeAssignment.isValid()) {\n                        throw new IllegalStateException(\"Cannot assign \" + target + \" to \" + enterType);\n                    }\n                    return new Target.ForVariable.ReadWrite(target, argumentHandler.enter(), readAssignment, writeAssignment);\n                }\n            }\n\n            /**\n             * A factory for creating a {@link ForEnterValue} offset mapping.\n             */\n            @HashCodeAndEqualsPlugin.Enhance\n            protected static class Factory implements OffsetMapping.Factory<Enter> {\n\n                /**\n                 * A description of the {@link Argument#readOnly()} method.\n                 */\n                private static final MethodDescription.InDefinedShape ENTER_READ_ONLY;\n\n                /**\n                 * A description of the {@link Argument#typing()} method.\n                 */\n                private static final MethodDescription.InDefinedShape ENTER_TYPING;\n\n                /*\n                 * Resolves annotation properties.\n                 */\n                static {\n                    MethodList<MethodDescription.InDefinedShape> methods = TypeDescription.ForLoadedType.of(Enter.class).getDeclaredMethods();\n                    ENTER_READ_ONLY = methods.filter(named(\"readOnly\")).getOnly();\n                    ENTER_TYPING = methods.filter(named(\"typing\")).getOnly();\n                }\n\n                /**\n                 * The supplied type of the enter advice.\n                 */\n                private final TypeDefinition enterType;\n\n                /**\n                 * Creates a new factory for creating a {@link ForEnterValue} offset mapping.\n                 *\n                 * @param enterType The supplied type of the enter method.\n                 */\n                protected Factory(TypeDefinition enterType) {\n                    this.enterType = enterType;\n                }\n\n                /**\n                 * Creates a new factory for creating a {@link ForEnterValue} offset mapping.\n                 *\n                 * @param typeDefinition The supplied type of the enter advice.\n                 * @return An appropriate offset mapping factory.\n                 */\n                protected static OffsetMapping.Factory<Enter> of(TypeDefinition typeDefinition) {\n                    return typeDefinition.represents(void.class)\n                        ? new Illegal(Enter.class)\n                        : new Factory(typeDefinition);\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public Class<Enter> getAnnotationType() {\n                    return Enter.class;\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public OffsetMapping make(ParameterDescription.InDefinedShape target,\n                                          AnnotationDescription.Loadable<Enter> annotation,\n                                          AdviceType adviceType) {\n                    if (adviceType.isDelegation() && !annotation.getValue(ENTER_READ_ONLY).resolve(Boolean.class)) {\n                        throw new IllegalStateException(\"Cannot use writable \" + target + \" on read-only parameter\");\n                    } else {\n                        return new ForEnterValue(target.getType(), enterType.asGenericType(), annotation);\n                    }\n                }\n            }\n        }\n\n        /**\n         * An offset mapping that provides access to the value that is returned by the exit advice.\n         */\n        @HashCodeAndEqualsPlugin.Enhance\n        class ForExitValue implements OffsetMapping {\n\n            /**\n             * The represented target type.\n             */\n            private final TypeDescription.Generic target;\n\n            /**\n             * The exit type.\n             */\n            private final TypeDescription.Generic exitType;\n\n            /**\n             * {@code true} if the annotated value is read-only.\n             */\n            private final boolean readOnly;\n\n            /**\n             * The typing to apply.\n             */\n            private final Assigner.Typing typing;\n\n            /**\n             * Creates a new offset mapping for the exit type.\n             *\n             * @param target     The represented target type.\n             * @param exitType   The exit type.\n             * @param annotation The represented annotation.\n             */\n            protected ForExitValue(TypeDescription.Generic target, TypeDescription.Generic exitType, AnnotationDescription.Loadable<Exit> annotation) {\n                this(target,\n                    exitType,\n                    annotation.getValue(Factory.EXIT_READ_ONLY).resolve(Boolean.class),\n                    annotation.getValue(Factory.EXIT_TYPING).load(Exit.class.getClassLoader()).resolve(Assigner.Typing.class));\n            }\n\n            /**\n             * Creates a new offset mapping for the enter type.\n             *\n             * @param target   The represented target type.\n             * @param exitType The exit type.\n             * @param readOnly {@code true} if the annotated value is read-only.\n             * @param typing   The typing to apply.\n             */\n            public ForExitValue(TypeDescription.Generic target, TypeDescription.Generic exitType, boolean readOnly, Assigner.Typing typing) {\n                this.target = target;\n                this.exitType = exitType;\n                this.readOnly = readOnly;\n                this.typing = typing;\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public Target resolve(TypeDescription instrumentedType,\n                                  MethodDescription instrumentedMethod,\n                                  Assigner assigner,\n                                  ArgumentHandler argumentHandler,\n                                  Sort sort) {\n                StackManipulation readAssignment = assigner.assign(exitType, target, typing);\n                if (!readAssignment.isValid()) {\n                    throw new IllegalStateException(\"Cannot assign \" + exitType + \" to \" + target);\n                } else if (readOnly) {\n                    return new Target.ForVariable.ReadOnly(target, argumentHandler.exit(), readAssignment);\n                } else {\n                    StackManipulation writeAssignment = assigner.assign(target, exitType, typing);\n                    if (!writeAssignment.isValid()) {\n                        throw new IllegalStateException(\"Cannot assign \" + target + \" to \" + exitType);\n                    }\n                    return new Target.ForVariable.ReadWrite(target, argumentHandler.exit(), readAssignment, writeAssignment);\n                }\n            }\n\n            /**\n             * A factory for creating a {@link ForExitValue} offset mapping.\n             */\n            @HashCodeAndEqualsPlugin.Enhance\n            protected static class Factory implements OffsetMapping.Factory<Exit> {\n\n                /**\n                 * A description of the {@link Exit#readOnly()} method.\n                 */\n                private static final MethodDescription.InDefinedShape EXIT_READ_ONLY;\n\n                /**\n                 * A description of the {@link Exit#typing()} method.\n                 */\n                private static final MethodDescription.InDefinedShape EXIT_TYPING;\n\n                /*\n                 * Resolves annotation properties.\n                 */\n                static {\n                    MethodList<MethodDescription.InDefinedShape> methods = TypeDescription.ForLoadedType.of(Exit.class).getDeclaredMethods();\n                    EXIT_READ_ONLY = methods.filter(named(\"readOnly\")).getOnly();\n                    EXIT_TYPING = methods.filter(named(\"typing\")).getOnly();\n                }\n\n                /**\n                 * The supplied type of the exit advice.\n                 */\n                private final TypeDefinition exitType;\n\n                /**\n                 * Creates a new factory for creating a {@link ForExitValue} offset mapping.\n                 *\n                 * @param exitType The supplied type of the exit advice.\n                 */\n                protected Factory(TypeDefinition exitType) {\n                    this.exitType = exitType;\n                }\n\n                /**\n                 * Creates a new factory for creating a {@link ForExitValue} offset mapping.\n                 *\n                 * @param typeDefinition The supplied type of the enter method.\n                 * @return An appropriate offset mapping factory.\n                 */\n                protected static OffsetMapping.Factory<Exit> of(TypeDefinition typeDefinition) {\n                    return typeDefinition.represents(void.class)\n                        ? new Illegal(Exit.class)\n                        : new Factory(typeDefinition);\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public Class<Exit> getAnnotationType() {\n                    return Exit.class;\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public OffsetMapping make(ParameterDescription.InDefinedShape target,\n                                          AnnotationDescription.Loadable<Exit> annotation,\n                                          AdviceType adviceType) {\n                    if (adviceType.isDelegation() && !annotation.getValue(EXIT_READ_ONLY).resolve(Boolean.class)) {\n                        throw new IllegalStateException(\"Cannot use writable \" + target + \" on read-only parameter\");\n                    } else {\n                        return new ForExitValue(target.getType(), exitType.asGenericType(), annotation);\n                    }\n                }\n            }\n        }\n\n        /**\n         * An offset mapping that provides access to a named local variable that is declared by the advice methods via {@link Local}.\n         */\n        @HashCodeAndEqualsPlugin.Enhance\n        class ForLocalValue implements OffsetMapping {\n\n            /**\n             * The variable's target type.\n             */\n            private final TypeDescription.Generic target;\n\n            /**\n             * The local variable's type.\n             */\n            private final TypeDescription.Generic localType;\n\n            /**\n             * The local variable's name.\n             */\n            private final String name;\n\n            /**\n             * Creates an offset mapping for a local variable that is declared by the advice methods via {@link Local}.\n             *\n             * @param target    The variable's target type.\n             * @param localType The local variable's type.\n             * @param name      The local variable's name.\n             */\n            public ForLocalValue(TypeDescription.Generic target, TypeDescription.Generic localType, String name) {\n                this.target = target;\n                this.localType = localType;\n                this.name = name;\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public Target resolve(TypeDescription instrumentedType,\n                                  MethodDescription instrumentedMethod,\n                                  Assigner assigner,\n                                  ArgumentHandler argumentHandler,\n                                  Sort sort) {\n                StackManipulation readAssignment = assigner.assign(localType, target, Assigner.Typing.STATIC);\n                StackManipulation writeAssignment = assigner.assign(target, localType, Assigner.Typing.STATIC);\n                if (!readAssignment.isValid() || !writeAssignment.isValid()) {\n                    throw new IllegalStateException(\"Cannot assign \" + localType + \" to \" + target);\n                } else {\n                    return new Target.ForVariable.ReadWrite(target, argumentHandler.named(name), readAssignment, writeAssignment);\n                }\n            }\n\n            /**\n             * A factory for an offset mapping for a local variable that is declared by the advice methods via {@link Local}.\n             */\n            @HashCodeAndEqualsPlugin.Enhance\n            protected static class Factory implements OffsetMapping.Factory<Local> {\n\n                /**\n                 * A description of the {@link Local#value()} method.\n                 */\n                protected static final MethodDescription.InDefinedShape LOCAL_VALUE = TypeDescription.ForLoadedType.of(Local.class)\n                    .getDeclaredMethods()\n                    .filter(named(\"value\"))\n                    .getOnly();\n\n                /**\n                 * The mapping of type names to their type that are available.\n                 */\n                private final Map<String, TypeDefinition> namedTypes;\n\n                /**\n                 * Creates a factory for a {@link Local} variable mapping.\n                 *\n                 * @param namedTypes The mapping of type names to their type that are available.\n                 */\n                protected Factory(Map<String, TypeDefinition> namedTypes) {\n                    this.namedTypes = namedTypes;\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public Class<Local> getAnnotationType() {\n                    return Local.class;\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public OffsetMapping make(ParameterDescription.InDefinedShape target,\n                                          AnnotationDescription.Loadable<Local> annotation,\n                                          AdviceType adviceType) {\n                    String name = annotation.getValue(LOCAL_VALUE).resolve(String.class);\n                    TypeDefinition namedType = namedTypes.get(name);\n                    if (namedType == null) {\n                        throw new IllegalStateException(\"Named local variable is unknown: \" + name);\n                    }\n                    return new ForLocalValue(target.getType(), namedType.asGenericType(), name);\n                }\n            }\n        }\n\n        /**\n         * An offset mapping that provides access to the value that is returned by the instrumented method.\n         */\n        @HashCodeAndEqualsPlugin.Enhance\n        class ForReturnValue implements OffsetMapping {\n\n            /**\n             * The type that the advice method expects for the return value.\n             */\n            private final TypeDescription.Generic target;\n\n            /**\n             * Determines if the parameter is to be treated as read-only.\n             */\n            private final boolean readOnly;\n\n            /**\n             * The typing to apply.\n             */\n            private final Assigner.Typing typing;\n\n            /**\n             * Creates a new offset mapping for a return value.\n             *\n             * @param target     The type that the advice method expects for the return value.\n             * @param annotation The annotation being bound.\n             */\n            protected ForReturnValue(TypeDescription.Generic target, AnnotationDescription.Loadable<Return> annotation) {\n                this(target,\n                    annotation.getValue(Factory.RETURN_READ_ONLY).resolve(Boolean.class),\n                    annotation.getValue(Factory.RETURN_TYPING).load(Return.class.getClassLoader()).resolve(Assigner.Typing.class));\n            }\n\n            /**\n             * Creates a new offset mapping for a return value.\n             *\n             * @param target   The type that the advice method expects for the return value.\n             * @param readOnly Determines if the parameter is to be treated as read-only.\n             * @param typing   The typing to apply.\n             */\n            public ForReturnValue(TypeDescription.Generic target, boolean readOnly, Assigner.Typing typing) {\n                this.target = target;\n                this.readOnly = readOnly;\n                this.typing = typing;\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public Target resolve(TypeDescription instrumentedType,\n                                  MethodDescription instrumentedMethod,\n                                  Assigner assigner,\n                                  ArgumentHandler argumentHandler,\n                                  Sort sort) {\n                StackManipulation readAssignment = assigner.assign(instrumentedMethod.getReturnType(), target, typing);\n                if (!readAssignment.isValid()) {\n                    throw new IllegalStateException(\"Cannot assign \" + instrumentedMethod.getReturnType() + \" to \" + target);\n                } else if (readOnly) {\n                    return instrumentedMethod.getReturnType().represents(void.class)\n                        ? new Target.ForDefaultValue.ReadOnly(target)\n                        : new Target.ForVariable.ReadOnly(instrumentedMethod.getReturnType(), argumentHandler.returned(), readAssignment);\n                } else {\n                    StackManipulation writeAssignment = assigner.assign(target, instrumentedMethod.getReturnType(), typing);\n                    if (!writeAssignment.isValid()) {\n                        throw new IllegalStateException(\"Cannot assign \" + target + \" to \" + instrumentedMethod.getReturnType());\n                    }\n                    return instrumentedMethod.getReturnType().represents(void.class)\n                        ? new Target.ForDefaultValue.ReadWrite(target)\n                        : new Target.ForVariable.ReadWrite(instrumentedMethod.getReturnType(), argumentHandler.returned(), readAssignment, writeAssignment);\n                }\n            }\n\n            /**\n             * A factory for creating a {@link ForReturnValue} offset mapping.\n             */\n            protected enum Factory implements OffsetMapping.Factory<Return> {\n\n                /**\n                 * The singleton instance.\n                 */\n                INSTANCE;\n\n                /**\n                 * A description of the {@link Return#readOnly()} method.\n                 */\n                private static final MethodDescription.InDefinedShape RETURN_READ_ONLY;\n\n                /**\n                 * A description of the {@link Return#typing()} method.\n                 */\n                private static final MethodDescription.InDefinedShape RETURN_TYPING;\n\n                /*\n                 * Resolves annotation properties.\n                 */\n                static {\n                    MethodList<MethodDescription.InDefinedShape> methods = TypeDescription.ForLoadedType.of(Return.class).getDeclaredMethods();\n                    RETURN_READ_ONLY = methods.filter(named(\"readOnly\")).getOnly();\n                    RETURN_TYPING = methods.filter(named(\"typing\")).getOnly();\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public Class<Return> getAnnotationType() {\n                    return Return.class;\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public OffsetMapping make(ParameterDescription.InDefinedShape target,\n                                          AnnotationDescription.Loadable<Return> annotation,\n                                          AdviceType adviceType) {\n                    if (adviceType.isDelegation() && !annotation.getValue(RETURN_READ_ONLY).resolve(Boolean.class)) {\n                        throw new IllegalStateException(\"Cannot write return value for \" + target + \" in read-only context\");\n                    } else {\n                        return new ForReturnValue(target.getType(), annotation);\n                    }\n                }\n            }\n        }\n\n        /**\n         * An offset mapping for accessing a {@link Throwable} of the instrumented method.\n         */\n        @HashCodeAndEqualsPlugin.Enhance\n        class ForThrowable implements OffsetMapping {\n\n            /**\n             * The type of parameter that is being accessed.\n             */\n            private final TypeDescription.Generic target;\n\n            /**\n             * {@code true} if the parameter is read-only.\n             */\n            private final boolean readOnly;\n\n            /**\n             * The typing to apply.\n             */\n            private final Assigner.Typing typing;\n\n            /**\n             * Creates a new offset mapping for access of the exception that is thrown by the instrumented method..\n             *\n             * @param target     The type of parameter that is being accessed.\n             * @param annotation The annotation to bind.\n             */\n            protected ForThrowable(TypeDescription.Generic target, AnnotationDescription.Loadable<Thrown> annotation) {\n                this(target,\n                    annotation.getValue(Factory.THROWN_READ_ONLY).resolve(Boolean.class),\n                    annotation.getValue(Factory.THROWN_TYPING).load(Thrown.class.getClassLoader()).resolve(Assigner.Typing.class));\n            }\n\n            /**\n             * Creates a new offset mapping for access of the exception that is thrown by the instrumented method..\n             *\n             * @param target   The type of parameter that is being accessed.\n             * @param readOnly {@code true} if the parameter is read-only.\n             * @param typing   The typing to apply.\n             */\n            public ForThrowable(TypeDescription.Generic target, boolean readOnly, Assigner.Typing typing) {\n                this.target = target;\n                this.readOnly = readOnly;\n                this.typing = typing;\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public Target resolve(TypeDescription instrumentedType,\n                                  MethodDescription instrumentedMethod,\n                                  Assigner assigner,\n                                  ArgumentHandler argumentHandler,\n                                  Sort sort) {\n                StackManipulation readAssignment = assigner.assign(TypeDescription.THROWABLE.asGenericType(), target, typing);\n                if (!readAssignment.isValid()) {\n                    throw new IllegalStateException(\"Cannot assign Throwable to \" + target);\n                } else if (readOnly) {\n                    return new Target.ForVariable.ReadOnly(TypeDescription.THROWABLE, argumentHandler.thrown(), readAssignment);\n                } else {\n                    StackManipulation writeAssignment = assigner.assign(target, TypeDescription.THROWABLE.asGenericType(), typing);\n                    if (!writeAssignment.isValid()) {\n                        throw new IllegalStateException(\"Cannot assign \" + target + \" to Throwable\");\n                    }\n                    return new Target.ForVariable.ReadWrite(TypeDescription.THROWABLE, argumentHandler.thrown(), readAssignment, writeAssignment);\n                }\n            }\n\n            /**\n             * A factory for accessing an exception that was thrown by the instrumented method.\n             */\n            protected enum Factory implements OffsetMapping.Factory<Thrown> {\n\n                /**\n                 * The singleton instance.\n                 */\n                INSTANCE;\n\n                /**\n                 * A description of the {@link Thrown#readOnly()} method.\n                 */\n                private static final MethodDescription.InDefinedShape THROWN_READ_ONLY;\n\n                /**\n                 * A description of the {@link Thrown#typing()} method.\n                 */\n                private static final MethodDescription.InDefinedShape THROWN_TYPING;\n\n                /*\n                 * Resolves annotation properties.\n                 */\n                static {\n                    MethodList<MethodDescription.InDefinedShape> methods = TypeDescription.ForLoadedType.of(Thrown.class).getDeclaredMethods();\n                    THROWN_READ_ONLY = methods.filter(named(\"readOnly\")).getOnly();\n                    THROWN_TYPING = methods.filter(named(\"typing\")).getOnly();\n                }\n\n                /**\n                 * Resolves an appropriate offset mapping factory for the {@link Thrown} parameter annotation.\n                 *\n                 * @param adviceMethod The exit advice method, annotated with {@link OnMethodExit}.\n                 * @return An appropriate offset mapping factory.\n                 */\n                @SuppressWarnings(\"unchecked\") // In absence of @SafeVarargs\n                protected static OffsetMapping.Factory<?> of(MethodDescription.InDefinedShape adviceMethod) {\n                    return isNoExceptionHandler(adviceMethod.getDeclaredAnnotations()\n                        .ofType(OnMethodExit.class)\n                        .getValue(ON_THROWABLE)\n                        .resolve(TypeDescription.class))\n                        ? new OffsetMapping.Factory.Illegal(Thrown.class) : Factory.INSTANCE;\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public Class<Thrown> getAnnotationType() {\n                    return Thrown.class;\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public OffsetMapping make(ParameterDescription.InDefinedShape target,\n                                          AnnotationDescription.Loadable<Thrown> annotation,\n                                          AdviceType adviceType) {\n                    if (adviceType.isDelegation() && !annotation.getValue(THROWN_READ_ONLY).resolve(Boolean.class)) {\n                        throw new IllegalStateException(\"Cannot use writable \" + target + \" on read-only parameter\");\n                    } else {\n                        return new ForThrowable(target.getType(), annotation);\n                    }\n                }\n            }\n        }\n\n        /**\n         * An offset mapping for binding a stack manipulation.\n         */\n        @HashCodeAndEqualsPlugin.Enhance\n        class ForStackManipulation implements OffsetMapping {\n\n            /**\n             * The stack manipulation that loads the bound value.\n             */\n            private final StackManipulation stackManipulation;\n\n            /**\n             * The type of the loaded value.\n             */\n            private final TypeDescription.Generic typeDescription;\n\n            /**\n             * The target type of the annotated parameter.\n             */\n            private final TypeDescription.Generic targetType;\n\n            /**\n             * The typing to apply.\n             */\n            private final Assigner.Typing typing;\n\n            /**\n             * Creates an offset mapping that binds a stack manipulation.\n             *\n             * @param stackManipulation The stack manipulation that loads the bound value.\n             * @param typeDescription   The type of the loaded value.\n             * @param targetType        The target type of the annotated parameter.\n             * @param typing            The typing to apply.\n             */\n            public ForStackManipulation(StackManipulation stackManipulation,\n                                        TypeDescription.Generic typeDescription,\n                                        TypeDescription.Generic targetType,\n                                        Assigner.Typing typing) {\n                this.stackManipulation = stackManipulation;\n                this.typeDescription = typeDescription;\n                this.targetType = targetType;\n                this.typing = typing;\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public Target resolve(TypeDescription instrumentedType,\n                                  MethodDescription instrumentedMethod,\n                                  Assigner assigner,\n                                  ArgumentHandler argumentHandler,\n                                  Sort sort) {\n                StackManipulation assignment = assigner.assign(typeDescription, targetType, typing);\n                if (!assignment.isValid()) {\n                    throw new IllegalStateException(\"Cannot assign \" + typeDescription + \" to \" + targetType);\n                }\n                return new Target.ForStackManipulation(new StackManipulation.Compound(stackManipulation, assignment));\n            }\n\n            public ForStackManipulation with(StackManipulation stackManipulation) {\n                return new ForStackManipulation(stackManipulation, this.typeDescription, this.targetType, this.typing);\n            }\n\n            public StackManipulation getStackManipulation() {\n                return this.stackManipulation;\n            }\n\n            /**\n             * A factory that binds a stack manipulation.\n             *\n             * @param <T> The annotation type this factory binds.\n             */\n            @HashCodeAndEqualsPlugin.Enhance\n            public static class Factory<T extends Annotation> implements OffsetMapping.Factory<T> {\n\n                /**\n                 * The annotation type.\n                 */\n                private final Class<T> annotationType;\n\n                /**\n                 * The stack manipulation that loads the bound value.\n                 */\n                private final StackManipulation stackManipulation;\n\n                /**\n                 * The type of the loaded value.\n                 */\n                private final TypeDescription.Generic typeDescription;\n\n                /**\n                 * Creates a new factory for binding a type description.\n                 *\n                 * @param annotationType  The annotation type.\n                 * @param typeDescription The type to bind.\n                 */\n                public Factory(Class<T> annotationType, TypeDescription typeDescription) {\n                    this(annotationType, ClassConstant.of(typeDescription), TypeDescription.CLASS.asGenericType());\n                }\n\n                /**\n                 * Creates a new factory for binding an enumeration.\n                 *\n                 * @param annotationType         The annotation type.\n                 * @param enumerationDescription The enumeration to bind.\n                 */\n                public Factory(Class<T> annotationType, EnumerationDescription enumerationDescription) {\n                    this(annotationType, FieldAccess.forEnumeration(enumerationDescription), enumerationDescription.getEnumerationType().asGenericType());\n                }\n\n                /**\n                 * Creates a new factory for binding a stack manipulation.\n                 *\n                 * @param annotationType    The annotation type.\n                 * @param stackManipulation The stack manipulation that loads the bound value.\n                 * @param typeDescription   The type of the loaded value.\n                 */\n                public Factory(Class<T> annotationType, StackManipulation stackManipulation, TypeDescription.Generic typeDescription) {\n                    this.annotationType = annotationType;\n                    this.stackManipulation = stackManipulation;\n                    this.typeDescription = typeDescription;\n                }\n\n                /**\n                 * Creates a binding for a fixed {@link String}, a primitive value or a method handle or type.\n                 *\n                 * @param annotationType The annotation type.\n                 * @param value          The primitive (wrapper) value, {@link String} value, method handle or type to bind.\n                 * @param <S>            The annotation type.\n                 * @return A factory for creating an offset mapping that binds the supplied value.\n                 */\n                public static <S extends Annotation> OffsetMapping.Factory<S> of(Class<S> annotationType, Object value) {\n                    StackManipulation stackManipulation;\n                    TypeDescription typeDescription;\n                    if (value == null) {\n                        return new OfDefaultValue(annotationType);\n                    } else if (value instanceof Boolean) {\n                        stackManipulation = IntegerConstant.forValue((Boolean) value);\n                        typeDescription = TypeDescription.ForLoadedType.of(boolean.class);\n                    } else if (value instanceof Byte) {\n                        stackManipulation = IntegerConstant.forValue((Byte) value);\n                        typeDescription = TypeDescription.ForLoadedType.of(byte.class);\n                    } else if (value instanceof Short) {\n                        stackManipulation = IntegerConstant.forValue((Short) value);\n                        typeDescription = TypeDescription.ForLoadedType.of(short.class);\n                    } else if (value instanceof Character) {\n                        stackManipulation = IntegerConstant.forValue((Character) value);\n                        typeDescription = TypeDescription.ForLoadedType.of(char.class);\n                    } else if (value instanceof Integer) {\n                        stackManipulation = IntegerConstant.forValue((Integer) value);\n                        typeDescription = TypeDescription.ForLoadedType.of(int.class);\n                    } else if (value instanceof Long) {\n                        stackManipulation = LongConstant.forValue((Long) value);\n                        typeDescription = TypeDescription.ForLoadedType.of(long.class);\n                    } else if (value instanceof Float) {\n                        stackManipulation = FloatConstant.forValue((Float) value);\n                        typeDescription = TypeDescription.ForLoadedType.of(float.class);\n                    } else if (value instanceof Double) {\n                        stackManipulation = DoubleConstant.forValue((Double) value);\n                        typeDescription = TypeDescription.ForLoadedType.of(double.class);\n                    } else if (value instanceof String) {\n                        stackManipulation = new TextConstant((String) value);\n                        typeDescription = TypeDescription.STRING;\n                    } else if (value instanceof Class<?>) {\n                        stackManipulation = ClassConstant.of(TypeDescription.ForLoadedType.of((Class<?>) value));\n                        typeDescription = TypeDescription.CLASS;\n                    } else if (value instanceof TypeDescription) {\n                        stackManipulation = ClassConstant.of((TypeDescription) value);\n                        typeDescription = TypeDescription.CLASS;\n                    } else if (value instanceof Enum<?>) {\n                        stackManipulation = FieldAccess.forEnumeration(new EnumerationDescription.ForLoadedEnumeration((Enum<?>) value));\n                        typeDescription = TypeDescription.ForLoadedType.of(((Enum<?>) value).getDeclaringClass());\n                    } else if (value instanceof EnumerationDescription) {\n                        stackManipulation = FieldAccess.forEnumeration((EnumerationDescription) value);\n                        typeDescription = ((EnumerationDescription) value).getEnumerationType();\n                    } else if (JavaType.METHOD_HANDLE.isInstance(value)) {\n                        JavaConstant constant = JavaConstant.MethodHandle.ofLoaded(value);\n                        stackManipulation = new JavaConstantValue(constant);\n                        typeDescription = constant.getTypeDescription();\n                    } else if (JavaType.METHOD_TYPE.isInstance(value)) {\n                        JavaConstant constant = JavaConstant.MethodType.ofLoaded(value);\n                        stackManipulation = new JavaConstantValue(constant);\n                        typeDescription = constant.getTypeDescription();\n                    } else if (value instanceof JavaConstant) {\n                        stackManipulation = new JavaConstantValue((JavaConstant) value);\n                        typeDescription = ((JavaConstant) value).getTypeDescription();\n                    } else {\n                        throw new IllegalStateException(\"Not a constant value: \" + value);\n                    }\n                    return new Factory(annotationType, stackManipulation, typeDescription.asGenericType());\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public Class<T> getAnnotationType() {\n                    return annotationType;\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public OffsetMapping make(ParameterDescription.InDefinedShape target,\n                                          AnnotationDescription.Loadable<T> annotation,\n                                          AdviceType adviceType) {\n                    return new ForStackManipulation(stackManipulation, typeDescription, target.getType(), Assigner.Typing.STATIC);\n                }\n            }\n\n            /**\n             * A factory for binding the annotated parameter's default value.\n             *\n             * @param <T> The annotation type this factory binds.\n             */\n            @HashCodeAndEqualsPlugin.Enhance\n            public static class OfDefaultValue<T extends Annotation> implements OffsetMapping.Factory<T> {\n\n                /**\n                 * The annotation type.\n                 */\n                private final Class<T> annotationType;\n\n                /**\n                 * Creates a factory for an offset mapping tat binds the parameter's default value.\n                 *\n                 * @param annotationType The annotation type.\n                 */\n                public OfDefaultValue(Class<T> annotationType) {\n                    this.annotationType = annotationType;\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public Class<T> getAnnotationType() {\n                    return annotationType;\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable<T> annotation, AdviceType adviceType) {\n                    return new ForStackManipulation(DefaultValue.of(target.getType()), target.getType(), target.getType(), Assigner.Typing.STATIC);\n                }\n            }\n\n            /**\n             * A factory for binding an annotation's property.\n             *\n             * @param <T> The annotation type this factory binds.\n             */\n            @HashCodeAndEqualsPlugin.Enhance\n            public static class OfAnnotationProperty<T extends Annotation> implements OffsetMapping.Factory<T> {\n\n                /**\n                 * The annotation type.\n                 */\n                private final Class<T> annotationType;\n\n                /**\n                 * The annotation property.\n                 */\n                private final MethodDescription.InDefinedShape property;\n\n                /**\n                 * Creates a factory for binding an annotation property.\n                 *\n                 * @param annotationType The annotation type.\n                 * @param property       The annotation property.\n                 */\n                protected OfAnnotationProperty(Class<T> annotationType, MethodDescription.InDefinedShape property) {\n                    this.annotationType = annotationType;\n                    this.property = property;\n                }\n\n                /**\n                 * Creates a factory for an offset mapping that binds an annotation property.\n                 *\n                 * @param annotationType The annotation type to bind.\n                 * @param property       The property to bind.\n                 * @param <S>            The annotation type.\n                 * @return A factory for binding a property of the annotation type.\n                 */\n                public static <S extends Annotation> OffsetMapping.Factory<S> of(Class<S> annotationType, String property) {\n                    if (!annotationType.isAnnotation()) {\n                        throw new IllegalArgumentException(\"Not an annotation type: \" + annotationType);\n                    }\n                    try {\n                        return new OfAnnotationProperty(annotationType, new MethodDescription.ForLoadedMethod(annotationType.getMethod(property)));\n                    } catch (NoSuchMethodException exception) {\n                        throw new IllegalArgumentException(\"Cannot find a property \" + property + \" on \" + annotationType, exception);\n                    }\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public Class<T> getAnnotationType() {\n                    return annotationType;\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable<T> annotation, AdviceType adviceType) {\n                    Object value = annotation.getValue(property).resolve();\n                    OffsetMapping.Factory<T> factory;\n                    if (value instanceof TypeDescription) {\n                        factory = new Factory(annotationType, (TypeDescription) value);\n                    } else if (value instanceof EnumerationDescription) {\n                        factory = new Factory(annotationType, (EnumerationDescription) value);\n                    } else if (value instanceof AnnotationDescription) {\n                        throw new IllegalStateException(\"Cannot bind annotation as fixed value for \" + property);\n                    } else {\n                        factory = Factory.of(annotationType, value);\n                    }\n                    return factory.make(target, annotation, adviceType);\n                }\n            }\n\n            /**\n             * Uses dynamic method invocation for binding an annotated parameter to a value.\n             *\n             * @param <T> The annotation type this factory binds.\n             */\n            @HashCodeAndEqualsPlugin.Enhance\n            public static class OfDynamicInvocation<T extends Annotation> implements OffsetMapping.Factory<T> {\n\n                /**\n                 * The annotation type.\n                 */\n                private final Class<T> annotationType;\n\n                /**\n                 * The bootstrap method or constructor.\n                 */\n                private final MethodDescription.InDefinedShape bootstrapMethod;\n\n                /**\n                 * The arguments to the bootstrap method.\n                 */\n                private final List<? extends JavaConstant> arguments;\n\n                /**\n                 * Creates a new factory for a dynamic invocation.\n                 *\n                 * @param annotationType  The annotation type.\n                 * @param bootstrapMethod The bootstrap method or constructor.\n                 * @param arguments       The arguments to the bootstrap method.\n                 */\n                public OfDynamicInvocation(Class<T> annotationType,\n                                           MethodDescription.InDefinedShape bootstrapMethod,\n                                           List<? extends JavaConstant> arguments) {\n                    this.annotationType = annotationType;\n                    this.bootstrapMethod = bootstrapMethod;\n                    this.arguments = arguments;\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public Class<T> getAnnotationType() {\n                    return annotationType;\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable<T> annotation, AdviceType adviceType) {\n                    if (!target.getType().isInterface()) {\n                        throw new IllegalArgumentException(target.getType() + \" is not an interface\");\n                    } else if (!target.getType().getInterfaces().isEmpty()) {\n                        throw new IllegalArgumentException(target.getType() + \" must not extend other interfaces\");\n                    } else if (!target.getType().isPublic()) {\n                        throw new IllegalArgumentException(target.getType() + \" is mot public\");\n                    }\n                    MethodList<?> methodCandidates = target.getType().getDeclaredMethods().filter(isAbstract());\n                    if (methodCandidates.size() != 1) {\n                        throw new IllegalArgumentException(target.getType() + \" must declare exactly one abstract method\");\n                    }\n                    return new ForStackManipulation(MethodInvocation.invoke(bootstrapMethod).dynamic(methodCandidates.getOnly().getInternalName(),\n                        target.getType().asErasure(),\n                        methodCandidates.getOnly().getParameters().asTypeList().asErasures(),\n                        arguments), target.getType(), target.getType(), Assigner.Typing.STATIC);\n                }\n            }\n        }\n\n        /**\n         * An offset mapping that loads a serialized value.\n         */\n        @HashCodeAndEqualsPlugin.Enhance\n        class ForSerializedValue implements OffsetMapping {\n\n            /**\n             * The type of the serialized value as it is used.\n             */\n            private final TypeDescription.Generic target;\n\n            /**\n             * The class type of the serialized value.\n             */\n            private final TypeDescription typeDescription;\n\n            /**\n             * The stack manipulation deserializing the represented value.\n             */\n            private final StackManipulation deserialization;\n\n            /**\n             * Creates a new offset mapping for a serialized value.\n             *\n             * @param target          The type of the serialized value as it is used.\n             * @param typeDescription The class type of the serialized value.\n             * @param deserialization The stack manipulation deserializing the represented value.\n             */\n            public ForSerializedValue(TypeDescription.Generic target, TypeDescription typeDescription, StackManipulation deserialization) {\n                this.target = target;\n                this.typeDescription = typeDescription;\n                this.deserialization = deserialization;\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            public Target resolve(TypeDescription instrumentedType,\n                                  MethodDescription instrumentedMethod,\n                                  Assigner assigner,\n                                  ArgumentHandler argumentHandler,\n                                  Sort sort) {\n                StackManipulation assignment = assigner.assign(typeDescription.asGenericType(), target, Assigner.Typing.DYNAMIC);\n                if (!assignment.isValid()) {\n                    throw new IllegalStateException(\"Cannot assign \" + typeDescription + \" to \" + target);\n                }\n                return new Target.ForStackManipulation(new StackManipulation.Compound(deserialization, assignment));\n            }\n\n            /**\n             * A factory for loading a deserialized value.\n             *\n             * @param <T> The annotation type this factory binds.\n             */\n            @HashCodeAndEqualsPlugin.Enhance\n            public static class Factory<T extends Annotation> implements OffsetMapping.Factory<T> {\n\n                /**\n                 * The annotation type.\n                 */\n                private final Class<T> annotationType;\n\n                /**\n                 * The type description as which to treat the deserialized value.\n                 */\n                private final TypeDescription typeDescription;\n\n                /**\n                 * The stack manipulation that loads the represented value.\n                 */\n                private final StackManipulation deserialization;\n\n                /**\n                 * Creates a factory for loading a deserialized value.\n                 *\n                 * @param annotationType  The annotation type.\n                 * @param typeDescription The type description as which to treat the deserialized value.\n                 * @param deserialization The stack manipulation that loads the represented value.\n                 */\n                protected Factory(Class<T> annotationType,\n                                  TypeDescription typeDescription, StackManipulation deserialization) {\n                    this.annotationType = annotationType;\n                    this.typeDescription = typeDescription;\n                    this.deserialization = deserialization;\n                }\n\n                /**\n                 * Creates a factory for an offset mapping that loads the provided value.\n                 *\n                 * @param annotationType The annotation type to be bound.\n                 * @param target         The instance representing the value to be deserialized.\n                 * @param targetType     The target type as which to use the target value.\n                 * @param <S>            The annotation type the created factory binds.\n                 * @return An appropriate offset mapping factory.\n                 */\n                public static <S extends Annotation> OffsetMapping.Factory<S> of(Class<S> annotationType,\n                                                                                 Serializable target,\n                                                                                 Class<?> targetType) {\n                    if (!targetType.isInstance(target)) {\n                        throw new IllegalArgumentException(target + \" is no instance of \" + targetType);\n                    }\n                    return new Factory(annotationType, TypeDescription.ForLoadedType.of(targetType),\n                        SerializedConstant.of(target));\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public Class<T> getAnnotationType() {\n                    return annotationType;\n                }\n\n                /**\n                 * {@inheritDoc}\n                 */\n                public OffsetMapping make(ParameterDescription.InDefinedShape target,\n                                          AnnotationDescription.Loadable<T> annotation, AdviceType adviceType) {\n                    return new ForSerializedValue(target.getType(), typeDescription, deserialization);\n                }\n            }\n        }\n    }\n\n    @Documented\n    @Retention(RetentionPolicy.RUNTIME)\n    @java.lang.annotation.Target(ElementType.METHOD)\n    public @interface OnMethodExitNoException {\n\n        /**\n         * <p>\n         * Determines if the execution of the instrumented method should be repeated. This does not include any enter advice.\n         * </p>\n         * <p>\n         * When specifying a non-primitive type, this method's return value that is subject to an {@code instanceof} check where\n         * the instrumented method is only executed, if the returned instance is {@code not} an instance of the specified class.\n         * Alternatively, it is possible to specify either {@link OnDefaultValue} or {@link OnNonDefaultValue} where the instrumented\n         * method is only repeated if the advice method returns a default or non-default value of the advice method's return type.\n         * It is illegal to specify a primitive type as an argument whereas setting the value to {@code void} indicates that the\n         * instrumented method should never be repeated.\n         * </p>\n         * <p>\n         * <b>Important</b>: Constructors cannot be repeated.\n         * </p>\n         *\n         * @return A value defining what return values of the advice method indicate that the instrumented method\n         * should be repeated or {@code void} if the instrumented method should never be repeated.\n         */\n        Class<?> repeatOn() default void.class;\n\n        /**\n         * Indicates a {@link Throwable} super type for which this exit advice is invoked if it was thrown from the instrumented method.\n         * If an exception is thrown, it is available via the {@link Thrown} parameter annotation. If a method returns exceptionally,\n         * any parameter annotated with {@link Return} is assigned the parameter type's default value.\n         *\n         * @return The type of {@link Throwable} for which this exit advice handler is invoked.\n         */\n        Class<? extends Throwable> onThrowable() default NoExceptionHandler.class;\n\n        /**\n         * <p>\n         * If {@code true}, all arguments of the instrumented method are copied before execution. Doing so, parameter reassignments applied\n         * by the instrumented are not effective during the execution of the annotated exit advice.\n         * </p>\n         * <p>\n         * Disabling this option can cause problems with the translation of stack map frames (meta data that is embedded in a Java class) if these\n         * frames become inconsistent with the original arguments of the instrumented method. In this case, the original arguments are no longer\n         * available to the exit advice such that Byte Buddy must abort the instrumentation with an error. If the instrumented method does not issue\n         * a stack map frame due to a lack of branching instructions, Byte Buddy might not be able to discover such an inconsistency what can cause\n         * a {@link VerifyError} instead of a Byte Buddy-issued exception as those inconsistencies are not discovered.\n         * </p>\n         *\n         * @return {@code true} if a backup of all method arguments should be made.\n         */\n        boolean backupArguments() default true;\n\n        /**\n         * Determines if the annotated method should be inlined into the instrumented method or invoked from it. When a method\n         * is inlined, its byte code is copied into the body of the target method. this makes it is possible to execute code\n         * with the visibility privileges of the instrumented method while loosing the privileges of the declared method methods.\n         * When a method is not inlined, it is invoked similarly to a common Java method call. Note that it is not possible to\n         * set breakpoints within a method when it is inlined as no debugging information is copied from the advice method into\n         * the instrumented method.\n         *\n         * @return {@code true} if the annotated method should be inlined into the instrumented method.\n         */\n        boolean inline() default true;\n\n        /**\n         * Indicates that this advice should suppress any {@link Throwable} type being thrown during the advice's execution. By default,\n         * any such exception is silently suppressed. Custom behavior can be configured by using {@link Advice#withExceptionHandler(StackManipulation)}.\n         *\n         * @return The type of {@link Throwable} to suppress.\n         * @see Advice#withExceptionPrinting()\n         */\n        Class<? extends Throwable> suppress() default NoExceptionHandler.class;\n    }\n\n    /**\n     * A marker class that indicates that an advice method does not suppress any {@link Throwable}.\n     */\n    public static class NoExceptionHandler extends Throwable {\n\n        /**\n         * The class's serial version UID.\n         */\n        private static final long serialVersionUID = 1L;\n\n        /**\n         * A description of the {@link NoExceptionHandler} type.\n         */\n        private static final TypeDescription DESCRIPTION = TypeDescription.ForLoadedType.of(NoExceptionHandler.class);\n\n        /**\n         * A private constructor as this class is not supposed to be invoked.\n         */\n        private NoExceptionHandler() {\n            throw new UnsupportedOperationException(\"This class only serves as a marker type and should not be instantiated\");\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/megaease/easeagent/core/plugin/transformer/advice/AgentForAdvice.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.core.plugin.transformer.advice;\n\nimport net.bytebuddy.asm.Advice;\nimport net.bytebuddy.asm.AsmVisitorWrapper;\nimport net.bytebuddy.build.HashCodeAndEqualsPlugin;\nimport net.bytebuddy.description.method.MethodDescription;\nimport net.bytebuddy.description.type.TypeDescription;\nimport net.bytebuddy.dynamic.ClassFileLocator;\nimport net.bytebuddy.dynamic.DynamicType;\nimport net.bytebuddy.implementation.bytecode.assign.Assigner;\nimport net.bytebuddy.matcher.ElementMatcher;\nimport net.bytebuddy.matcher.LatentMatcher;\nimport net.bytebuddy.pool.TypePool;\nimport net.bytebuddy.utility.CompoundList;\nimport net.bytebuddy.utility.JavaModule;\n\nimport java.util.*;\n\nimport static net.bytebuddy.agent.builder.AgentBuilder.*;\n\n@SuppressWarnings(\"unused\")\npublic class AgentForAdvice extends Transformer.ForAdvice {\n    /**\n     * The advice to use.\n     */\n    private final AgentAdvice.WithCustomMapping advice;\n\n    /**\n     * The exception handler to register for the advice.\n     */\n    private final Advice.ExceptionHandler exceptionHandler;\n\n    /**\n     * The assigner to use for the advice.\n     */\n    private final Assigner assigner;\n\n    /**\n     * The class file locator to query for the advice class.\n     */\n    private final ClassFileLocator classFileLocator;\n\n    /**\n     * The pool strategy to use for looking up an advice.\n     */\n    private final PoolStrategy poolStrategy;\n\n    /**\n     * The location strategy to use for class loaders when resolving advice classes.\n     */\n    private final LocationStrategy locationStrategy;\n\n    /**\n     * The advice entries to apply.\n     */\n    private final List<Entry> entries;\n\n    public AgentForAdvice() {\n        this(new AgentAdvice.WithCustomMapping());\n    }\n\n    public AgentForAdvice(AgentAdvice.WithCustomMapping advice) {\n        this(advice,\n            Advice.ExceptionHandler.Default.SUPPRESSING,\n            Assigner.DEFAULT,\n            ClassFileLocator.NoOp.INSTANCE,\n            PoolStrategy.Default.FAST,\n            LocationStrategy.ForClassLoader.STRONG,\n            Collections.emptyList());\n    }\n\n    protected AgentForAdvice(AgentAdvice.WithCustomMapping advice,\n                             Advice.ExceptionHandler exceptionHandler,\n                             Assigner assigner,\n                             ClassFileLocator classFileLocator,\n                             PoolStrategy poolStrategy,\n                             LocationStrategy locationStrategy,\n                             List<Entry> entries) {\n        this.advice = advice;\n        this.exceptionHandler = exceptionHandler;\n        this.assigner = assigner;\n        this.classFileLocator = classFileLocator;\n        this.poolStrategy = poolStrategy;\n        this.locationStrategy = locationStrategy;\n        this.entries = entries;\n    }\n\n    /**\n     * Includes the supplied class loaders as a source for looking up an advice class or its dependencies.\n     * Note that the supplied class loaders are queried for types before the class loader of the instrumented class.\n     *\n     * @param classLoader The class loaders to include when looking up classes in their order. Duplicates are filtered.\n     * @return A new instance of this advice transformer that considers the supplied class loaders as a lookup source.\n     */\n    @Override\n    public AgentForAdvice include(ClassLoader... classLoader) {\n        Set<ClassFileLocator> classFileLocators = new LinkedHashSet<>();\n        for (ClassLoader aClassLoader : classLoader) {\n            classFileLocators.add(ClassFileLocator.ForClassLoader.of(aClassLoader));\n        }\n        return include(new ArrayList<>(classFileLocators));\n    }\n\n    /**\n     * Includes the supplied class file locators as a source for looking up an advice class or its dependencies.\n     * Note that the supplied class loaders are queried for types before the class loader of the instrumented class.\n     *\n     * @param classFileLocator The class file locators to include when looking up classes in their order. Duplicates are filtered.\n     * @return A new instance of this advice transformer that considers the supplied class file locators as a lookup source.\n     */\n    @Override\n    public AgentForAdvice include(ClassFileLocator... classFileLocator) {\n        return include(Arrays.asList(classFileLocator));\n    }\n\n    /**\n     * Includes the supplied class file locators as a source for looking up an advice class or its dependencies.\n     * Note that the supplied class loaders are queried for types before the class loader of the instrumented class.\n     *\n     * @param classFileLocators The class file locators to include when looking up classes in their order. Duplicates are filtered.\n     * @return A new instance of this advice transformer that considers the supplied class file locators as a lookup source.\n     */\n    @Override\n    public AgentForAdvice include(List<? extends ClassFileLocator> classFileLocators) {\n        return new AgentForAdvice(advice,\n            exceptionHandler,\n            assigner,\n            new ClassFileLocator.Compound(CompoundList.of(classFileLocator, classFileLocators)),\n            poolStrategy,\n            locationStrategy,\n            entries);\n    }\n\n    /**\n     * Applies the given advice class onto all methods that satisfy the supplied matcher.\n     *\n     * @param matcher The matcher to determine what methods the advice should be applied to.\n     * @param name    The fully-qualified, binary name of the advice class.\n     * @return A new instance of this advice transformer that applies the given advice to all matched methods of an instrumented type.\n     */\n    @Override\n    public AgentForAdvice advice(ElementMatcher<? super MethodDescription> matcher, String name) {\n        return advice(new LatentMatcher.Resolved<>(matcher), name);\n    }\n\n    /**\n     * Applies the given advice class onto all methods that satisfy the supplied matcher.\n     *\n     * @param matcher The matcher to determine what methods the advice should be applied to.\n     * @param name    The fully-qualified, binary name of the advice class.\n     * @return A new instance of this advice transformer that applies the given advice to all matched methods of an instrumented type.\n     */\n    @Override\n    public AgentForAdvice advice(LatentMatcher<? super MethodDescription> matcher, String name) {\n        return new AgentForAdvice(advice,\n            exceptionHandler,\n            assigner,\n            classFileLocator,\n            poolStrategy,\n            locationStrategy,\n            CompoundList.of(entries, new ForUnifiedAdvice(matcher, name)));\n    }\n\n    @Override\n    public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder,\n                                            TypeDescription typeDescription,\n                                            ClassLoader classLoader,\n                                            JavaModule module) {\n        ClassFileLocator classFileLocator = new ClassFileLocator.Compound(this.classFileLocator,\n            locationStrategy.classFileLocator(classLoader, module));\n\n        TypePool typePool = poolStrategy.typePool(classFileLocator, classLoader);\n        AsmVisitorWrapper.ForDeclaredMethods asmVisitorWrapper = new AsmVisitorWrapper.ForDeclaredMethods();\n        for (Entry entry : entries) {\n            asmVisitorWrapper = asmVisitorWrapper.invokable(entry.getMatcher().resolve(typeDescription),\n                entry.resolve(advice, typePool, classFileLocator)\n                    .withAssigner(assigner)\n                    .withExceptionHandler(exceptionHandler));\n        }\n        return builder.visit(asmVisitorWrapper);\n    }\n\n    @HashCodeAndEqualsPlugin.Enhance\n    protected abstract static class Entry {\n        /**\n         * The matcher for advised methods.\n         */\n        private final LatentMatcher<? super MethodDescription> matcher;\n\n        /**\n         * Creates a new entry.\n         *\n         * @param matcher The matcher for advised methods.\n         */\n        protected Entry(LatentMatcher<? super MethodDescription> matcher) {\n            this.matcher = matcher;\n        }\n\n        /**\n         * Returns the matcher for advised methods.\n         *\n         * @return The matcher for advised methods.\n         */\n        protected LatentMatcher<? super MethodDescription> getMatcher() {\n            return matcher;\n        }\n\n        /**\n         * Resolves the advice for this entry.\n         *\n         * @param advice           The advice configuration.\n         * @param typePool         The type pool to use.\n         * @param classFileLocator The class file locator to use.\n         * @return The resolved advice.\n         */\n        protected abstract AgentAdvice resolve(AgentAdvice.WithCustomMapping advice,\n                                               TypePool typePool, ClassFileLocator classFileLocator);\n    }\n\n    @HashCodeAndEqualsPlugin.Enhance\n    protected static class ForUnifiedAdvice extends Entry {\n        /**\n         * The name of the advice class.\n         */\n        protected final String name;\n\n        /**\n         * Creates a new entry for an advice class where both the (optional) entry and exit advice methods are declared by the same class.\n         *\n         * @param matcher The matcher for advised methods.\n         * @param name    The name of the advice class.\n         */\n        protected ForUnifiedAdvice(LatentMatcher<? super MethodDescription> matcher, String name) {\n            super(matcher);\n            this.name = name;\n        }\n\n        @Override\n        protected AgentAdvice resolve(AgentAdvice.WithCustomMapping advice, TypePool typePool, ClassFileLocator classFileLocator) {\n            return advice.to(typePool.describe(name).resolve(), classFileLocator);\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/megaease/easeagent/core/plugin/transformer/advice/AgentJavaConstantValue.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.core.plugin.transformer.advice;\n\nimport net.bytebuddy.implementation.Implementation;\nimport net.bytebuddy.implementation.bytecode.constant.JavaConstantValue;\nimport net.bytebuddy.jar.asm.MethodVisitor;\nimport net.bytebuddy.utility.JavaConstant;\n\npublic class AgentJavaConstantValue extends JavaConstantValue {\n    private final MethodIdentityJavaConstant constant;\n    private final int pointcutIndex;\n\n    /**\n     * Creates a constant pool value representing a {@link JavaConstant}.\n     *\n     * @param constant The instance to load onto the operand stack.\n     */\n    public AgentJavaConstantValue(MethodIdentityJavaConstant constant, int pointcutIndex) {\n        super(constant);\n        this.constant = constant;\n        this.pointcutIndex = pointcutIndex;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {\n        Integer index = (Integer) constant.accept(Visitor.INSTANCE);\n        methodVisitor.visitLdcInsn(index);\n        return constant.getTypeDescription().getStackSize().toIncreasingSize();\n    }\n\n    public MethodIdentityJavaConstant getConstant() {\n        return this.constant;\n    }\n\n    public int getPointcutIndex() {\n        return this.pointcutIndex;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/megaease/easeagent/core/plugin/transformer/advice/BypassMethodVisitor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.core.plugin.transformer.advice;\n\nimport com.megaease.easeagent.core.plugin.transformer.advice.AgentAdvice.OffsetMapping;\nimport net.bytebuddy.jar.asm.MethodVisitor;\nimport net.bytebuddy.utility.OpenedClassReader;\n\nimport java.util.Map;\n\npublic class BypassMethodVisitor extends MethodVisitor {\n    public BypassMethodVisitor(MethodVisitor visitor, Map<Integer, OffsetMapping> offsetMappings) {\n        super(OpenedClassReader.ASM_API, visitor);\n\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/megaease/easeagent/core/plugin/transformer/advice/MethodIdentityJavaConstant.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.core.plugin.transformer.advice;\n\nimport lombok.Data;\nimport net.bytebuddy.description.type.TypeDescription;\nimport net.bytebuddy.utility.JavaConstant;\n\n@Data\npublic class MethodIdentityJavaConstant implements JavaConstant {\n    private Integer identity;\n\n    public MethodIdentityJavaConstant(int value) {\n        this.identity = value;\n    }\n\n    @Override\n    public Object toDescription() {\n        return this.identity;\n    }\n\n    @Override\n    public TypeDescription getTypeDescription() {\n        return TypeDescription.ForLoadedType.of(int.class);\n    }\n\n    @Override\n    public <T> T accept(Visitor<T> visitor) {\n        return (T) this.identity;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/megaease/easeagent/core/plugin/transformer/classloader/CompoundClassloader.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.core.plugin.transformer.classloader;\n\nimport com.google.common.cache.Cache;\nimport com.google.common.cache.CacheBuilder;\nimport com.megaease.easeagent.core.plugin.matcher.MethodTransformation;\nimport com.megaease.easeagent.log4j2.Logger;\nimport com.megaease.easeagent.log4j2.LoggerFactory;\n\npublic class CompoundClassloader {\n    private static final Logger log = LoggerFactory.getLogger(MethodTransformation.class);\n    private static final Cache<ClassLoader, Boolean> CACHE = CacheBuilder.newBuilder().weakKeys().build();\n\n\n    public static boolean checkClassloaderExist(ClassLoader loader) {\n        if (CACHE.getIfPresent(loader) == null) {\n            CACHE.put(loader, true);\n            return false;\n        }\n        return true;\n    }\n\n    public static ClassLoader compound(ClassLoader parent, ClassLoader external) {\n        if (external == null || checkClassloaderExist(external)) {\n            return parent;\n        }\n\n        try {\n            parent.getClass().getDeclaredMethod(\"add\", ClassLoader.class).invoke(parent, external);\n        } catch (Exception e) {\n            log.warn(\"{}, this may be a bug if it was running in production\", e.toString());\n        }\n        return parent;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/megaease/easeagent/core/utils/AgentArray.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.core.utils;\n\nimport com.google.auto.service.AutoService;\nimport com.megaease.easeagent.plugin.AppendBootstrapLoader;\n\nimport java.util.Arrays;\nimport java.util.Objects;\nimport java.util.Spliterator;\nimport java.util.Spliterators;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.function.Consumer;\n\n@AutoService(AppendBootstrapLoader.class)\n@SuppressWarnings(\"unchecked\")\npublic class AgentArray<E> {\n    private static final int DEFAULT_INIT_SIZE = 256;\n    private final Object lock = new Object();\n    private Object[] a;\n    private final AtomicInteger size = new AtomicInteger(0);\n\n    public AgentArray() {\n        this(DEFAULT_INIT_SIZE);\n    }\n\n    public AgentArray(int capacity) {\n        a = new Object[capacity];\n        // don't use the first slot\n        a[0] = lock;\n        size.incrementAndGet();\n    }\n\n    public int size() {\n        return size.get();\n    }\n\n    public Object[] toArray() {\n        return a.clone();\n    }\n\n    public int add(E element) {\n        int current = size.get();\n        while (!size.compareAndSet(current, current + 1)) {\n            current = size.get();\n        }\n        ensureCapacity(current + 1);\n        a[current] = element;\n\n        return current;\n    }\n\n    public E get(int index) {\n        if (index >= size()) {\n            return null;\n        }\n        return (E) a[index];\n    }\n\n    public E getUncheck(int index) {\n        return (E) a[index];\n    }\n\n    /**\n     * set element for index, can't override existed value\n     *\n     * @return return null, when successful, otherwise return element already existed\n     */\n    public E putIfAbsent(int index, E element) {\n        ensureCapacity(index + 1);\n        E oldValue;\n\n        synchronized (lock) {\n            oldValue = (E) a[index];\n            if (oldValue == null) {\n                a[index] = element;\n            } else {\n                return oldValue;\n            }\n        }\n\n        int currentSize = size.get();\n        if (currentSize < index + 1) {\n            while (!size.compareAndSet(currentSize, index + 1)) {\n                currentSize = size.get();\n                if (currentSize > index + 1) {\n                    break;\n                }\n            }\n        }\n        return null;\n    }\n\n    /**\n     * replace the element at the index by another value\n     * when the slot of the index is null, it will fail to replace,\n     * and please use set function instead of replace\n     *\n     * @return original value,\n     */\n    public E replace(int index, E element) {\n        ensureCapacity(index + 1);\n        E oldValue;\n\n        synchronized (lock) {\n            oldValue = (E) a[index];\n            if (oldValue == null) {\n                return null;\n            } else {\n                a[index] = element;\n            }\n        }\n        return oldValue;\n    }\n\n    public int indexOf(Object o) {\n        int length = size();\n        Object s;\n        if (o == null) {\n            for (int i = 0; i < length; i++) {\n                s = a[i];\n                if (s == null) {\n                    return i;\n                }\n            }\n        } else {\n            for (int i = 0; i < length; i++) {\n                s = a[i];\n                if (o.equals(s)) {\n                    return i;\n                }\n                return i;\n            }\n        }\n        return -1;\n    }\n\n    public boolean contains(Object o) {\n        return indexOf(o) != -1;\n    }\n\n    public Spliterator<E> spliterator() {\n        return Spliterators.spliterator(a, Spliterator.ORDERED);\n    }\n\n    public void forEach(Consumer<? super E> action) {\n        Objects.requireNonNull(action);\n        E e;\n        for (int idx = 0; idx < size.get(); idx++) {\n            e = (E) a[idx];\n            action.accept(e);\n        }\n    }\n\n    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;\n\n    /**\n     * Increases the capacity to ensure that it can hold at least the\n     * number of elements specified by the minimum capacity argument.\n     *\n     * @param minCapacity the desired minimum capacity\n     */\n    private synchronized void grow(int minCapacity) {\n        // overflow-conscious code\n        int oldCapacity = a.length;\n        int newCapacity = oldCapacity + (oldCapacity >> 1);\n        if (newCapacity - minCapacity < 0) {\n            newCapacity = minCapacity;\n        }\n        if (newCapacity - MAX_ARRAY_SIZE > 0) {\n            newCapacity = hugeCapacity(minCapacity);\n        }\n        // minCapacity is usually close to size, so this is a win:\n        this.a = Arrays.copyOf(a, newCapacity);\n    }\n\n    private static int hugeCapacity(int minCapacity) {\n        // overflow\n        if (minCapacity < 0) {\n            throw new OutOfMemoryError();\n        }\n        return (minCapacity > MAX_ARRAY_SIZE) ?\n            Integer.MAX_VALUE :\n            MAX_ARRAY_SIZE;\n    }\n\n    /**\n     * Increases the capacity of this <tt>ArrayList</tt> instance, if\n     * necessary, to ensure that it can hold at least the number of elements\n     * specified by the minimum capacity argument.\n     *\n     * @param minCapacity the desired minimum capacity\n     */\n    private void ensureCapacity(int minCapacity) {\n        if (minCapacity - a.length > 0) {\n            grow(minCapacity);\n        }\n    }\n\n    private String outOfBoundsMsg(int index) {\n        return \"Index: \" + index + \", Size: \" + size;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/megaease/easeagent/core/utils/ContextUtils.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.core.utils;\n\nimport com.google.auto.service.AutoService;\nimport com.megaease.easeagent.plugin.AppendBootstrapLoader;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.utils.SystemClock;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n@AutoService(AppendBootstrapLoader.class)\npublic class ContextUtils {\n\n    private ContextUtils() {\n    }\n\n    private static final String BEGIN_TIME = ContextUtils.class.getSimpleName() + \".beginTime\";\n    private static final String END_TIME = ContextUtils.class.getSimpleName() + \".endTime\";\n\n    private static void setBeginTime(Map<Object, Object> context) {\n        context.put(BEGIN_TIME, SystemClock.now());\n    }\n\n    public static void setEndTime(Map<Object, Object> context) {\n        context.put(END_TIME, SystemClock.now());\n    }\n\n    public static Long getBeginTime(Map<Object, Object> context) {\n        return (Long) context.get(BEGIN_TIME);\n    }\n\n    public static Long getEndTime(Map<Object, Object> context) {\n        Long endTime = (Long) context.get(END_TIME);\n        if (endTime == null) {\n            setEndTime(context);\n            endTime = (Long) context.get(END_TIME);\n        }\n        return endTime;\n    }\n\n    public static long getDuration(Map<Object, Object> context) {\n        return getEndTime(context) - getBeginTime(context);\n    }\n\n    public static void setBeginTime(Context context) {\n        context.put(BEGIN_TIME, SystemClock.now());\n    }\n\n    public static void setEndTime(Context context) {\n        context.put(END_TIME, SystemClock.now());\n    }\n\n    public static Long getBeginTime(Context context) {\n        return (Long) context.get(BEGIN_TIME);\n    }\n\n    public static Long getEndTime(Context context) {\n        Long endTime = (Long) context.get(END_TIME);\n        if (endTime == null) {\n            setEndTime(context);\n            endTime = (Long) context.get(END_TIME);\n        }\n        return endTime;\n    }\n\n    public static Map<Object, Object> createContext() {\n        HashMap<Object, Object> map = new HashMap<>();\n        setBeginTime(map);\n        return map;\n    }\n\n    /**\n     * Get data from context\n     *\n     * @param context Store data\n     * @param key     key is the type of data. Like {@code value.getClass()}\n     * @param <T>     The type of data\n     * @return data\n     */\n    @SuppressWarnings(\"unchecked\")\n    public static <T> T getFromContext(Map<Object, Object> context, Object key) {\n        return (T) context.get(key);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/megaease/easeagent/core/utils/JsonUtil.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.core.utils;\n\nimport com.fasterxml.jackson.core.JsonParser;\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.core.type.TypeReference;\nimport com.fasterxml.jackson.databind.DeserializationFeature;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.megaease.easeagent.log4j2.Logger;\nimport com.megaease.easeagent.log4j2.LoggerFactory;\n\nimport java.util.Map;\n\npublic class JsonUtil {\n\n    private JsonUtil() {\n    }\n\n    static final ObjectMapper MAPPER = new ObjectMapper();\n    private static final Logger logger = LoggerFactory.getLogger(JsonUtil.class);\n\n    static {\n        MAPPER.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);\n        MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);\n    }\n\n    public static String toJson(Object obj) {\n        try {\n            return MAPPER.writeValueAsString(obj);\n        } catch (JsonProcessingException e) {\n            logger.warn(\"data to json error: {}\", e.getMessage());\n            return null;\n        }\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    public static Map<String, Object> toMap(String json) {\n        try {\n            return MAPPER.readValue(json, Map.class);\n        } catch (JsonProcessingException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    public static <T> T toObject(String json, TypeReference<T> valueTypeRef) {\n        try {\n            return MAPPER.readValue(json, valueTypeRef);\n        } catch (JsonProcessingException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/com/megaease/easeagent/core/utils/MutableObject.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.core.utils;\n\npublic interface MutableObject<T> {\n\n    static <S> MutableObject<S> wrap(S object) {\n        return new DefaultMutableObject<>(object);\n    }\n\n    static <S> MutableObject<S> nullMutableObject() {\n        return new DefaultMutableObject<>(null);\n    }\n\n    T getValue();\n\n    void setValue(T t);\n\n    class DefaultMutableObject<T> implements MutableObject<T> {\n        private T value;\n\n        protected DefaultMutableObject(T t) {\n            this.value = t;\n        }\n\n        public T getValue() {\n            return value;\n        }\n\n        public void setValue(T value) {\n            this.value = value;\n        }\n\n        /**\n         * <p>\n         * Compares this object against the specified object. The result is {@code true} if and only if the argument\n         * is not {@code null} and is a {@code MutableObject} object that contains the same {@code T}\n         * value as this object.\n         * </p>\n         *\n         * @param obj the object to compare with, {@code null} returns {@code false}\n         * @return {@code true} if the objects are the same;\n         * {@code true} if the objects have equivalent {@code value} fields;\n         * {@code false} otherwise.\n         */\n        @Override\n        public boolean equals(final Object obj) {\n            if (obj == null) {\n                return false;\n            }\n            if (this == obj) {\n                return true;\n            }\n            if (this.getClass().equals(obj.getClass())) {\n                final MutableObject<?> that = (MutableObject<?>) obj;\n                return this.value.equals(that.getValue());\n            }\n            return false;\n        }\n\n        /**\n         * Returns the value's hash code or {@code 0} if the value is {@code null}.\n         *\n         * @return the value's hash code or {@code 0} if the value is {@code null}.\n         */\n        @Override\n        public int hashCode() {\n            return value == null ? 0 : value.hashCode();\n        }\n\n\n        /**\n         * Returns the String value of this mutable.\n         *\n         * @return the mutable value as a string\n         */\n        @Override\n        public String toString() {\n            return value == null ? \"null\" : value.toString();\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/megaease/easeagent/core/utils/ServletUtils.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.core.utils;\n\nimport lombok.SneakyThrows;\n\nimport javax.servlet.http.HttpServletRequest;\nimport java.net.URLDecoder;\nimport java.util.*;\n\npublic class ServletUtils {\n\n    private static final String UNKNOWN = \"unknown\";\n\n    private ServletUtils() {\n    }\n\n    public static final String BEST_MATCHING_PATTERN_ATTRIBUTE = \"org.springframework.web.servlet.HandlerMapping.bestMatchingPattern\";\n\n    public static String getHttpRouteAttributeFromRequest(HttpServletRequest request) {\n        Object httpRoute = request.getAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE);\n        return httpRoute != null ? httpRoute.toString() : \"\";\n    }\n\n    public static String getRemoteHost(HttpServletRequest request) {\n        if (request == null) {\n            return UNKNOWN;\n        }\n        String ip = request.getHeader(\"x-forwarded-for\");\n        if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {\n            ip = request.getHeader(\"Proxy-Client-IP\");\n        }\n        if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {\n            ip = request.getHeader(\"WL-Proxy-Client-IP\");\n        }\n        if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {\n            ip = request.getRemoteAddr();\n        }\n        return \"0:0:0:0:0:0:0:1\".equals(ip) ? \"127.0.0.1\" : ip;\n    }\n\n    public static Map<String, String> getHeaders(HttpServletRequest httpServletRequest) {\n        Enumeration<String> headerNames = httpServletRequest.getHeaderNames();\n        Map<String, String> map = new HashMap<>();\n        while (headerNames.hasMoreElements()) {\n            String name = headerNames.nextElement();\n            String value = httpServletRequest.getHeader(name);\n            map.put(name, value);\n        }\n        return map;\n    }\n\n    @SneakyThrows\n    public static Map<String, List<String>> getQueries(HttpServletRequest httpServletRequest) {\n        Map<String, List<String>> map = new HashMap<>();\n        String queryString = httpServletRequest.getQueryString();\n        if (queryString == null || queryString.isEmpty()) {\n            return map;\n        }\n        String[] pairs = queryString.split(\"&\");\n        for (String pair : pairs) {\n            int idx = pair.indexOf(\"=\");\n            String key = idx > 0 ? URLDecoder.decode(pair.substring(0, idx), \"UTF-8\") : pair;\n            if (!map.containsKey(key)) {\n                map.put(key, new LinkedList<>());\n            }\n            String value = idx > 0 && pair.length() > idx + 1 ? URLDecoder.decode(pair.substring(idx + 1), \"UTF-8\") : null;\n            map.get(key).add(value);\n        }\n        return map;\n    }\n\n    public static Map<String, String> getQueries4SingleValue(HttpServletRequest httpServletRequest) {\n        Map<String, List<String>> map = getQueries(httpServletRequest);\n        Map<String, String> singleValueMap = new HashMap<>();\n        map.forEach((key, values) -> {\n            if (values != null && !values.isEmpty()) {\n                singleValueMap.put(key, values.get(0));\n            }\n        });\n        return singleValueMap;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/megaease/easeagent/core/utils/TextUtils.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.core.utils;\n\nimport com.megaease.easeagent.plugin.utils.common.DataSize;\n\nimport java.nio.charset.StandardCharsets;\n\npublic class TextUtils {\n\n    private TextUtils() {\n    }\n\n    public static String cutStrByDataSize(String str, DataSize size) {\n        byte[] now = str.getBytes(StandardCharsets.UTF_8);\n        if (now.length <= size.toBytes()) {\n            return str;\n        }\n        String tmp = new String(now, 0, (int) size.toBytes(), StandardCharsets.UTF_8);\n        char unstable = tmp.charAt(tmp.length() - 1);\n        char old = str.charAt(tmp.length() - 1);\n        if (unstable == old) {\n            return tmp;\n        }\n        return new String(tmp.toCharArray(), 0, tmp.length() - 1);\n    }\n\n    public static boolean hasText(String val) {\n        return val != null && val.trim().length() > 0;\n    }\n}\n"
  },
  {
    "path": "core/src/main/resources/META-INF/services/com.megaease.easeagent.plugin.bean.BeanProvider",
    "content": "com.megaease.easeagent.core.health.HealthProvider\ncom.megaease.easeagent.core.info.AgentInfoProvider\n"
  },
  {
    "path": "core/src/main/resources/version.txt",
    "content": "${project.version}\n"
  },
  {
    "path": "core/src/test/java/com/megaease/easeagent/core/AppendBootstrapClassLoaderSearchTest.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.core;\n\nimport com.google.common.collect.Sets;\nimport net.bytebuddy.dynamic.loading.ClassInjector;\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport java.lang.instrument.Instrumentation;\nimport java.util.Set;\n\nimport static org.mockito.Mockito.mock;\n\npublic class AppendBootstrapClassLoaderSearchTest {\n    @Test\n    public void should_inject_classes() throws Exception {\n        final Set<String> strings = Sets.newHashSet(\n                \"com.megaease.easeagent.core.utils.AgentArray\",\n                \"com.megaease.easeagent.core.utils.ContextUtils\",\n                \"com.megaease.easeagent.core.plugin.Dispatcher\"\n            );\n        Assert.assertEquals(strings, AppendBootstrapClassLoaderSearch.by(mock(Instrumentation.class), ClassInjector.UsingInstrumentation.Target.SYSTEM));\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/megaease/easeagent/core/BootstrapTest.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.core;\n\n\nimport com.j256.simplejmx.client.JmxClient;\nimport com.j256.simplejmx.common.IoUtils;\nimport com.j256.simplejmx.server.JmxServer;\nimport com.megaease.easeagent.config.GlobalConfigs;\nimport lombok.SneakyThrows;\nimport org.hamcrest.CoreMatchers;\nimport org.hamcrest.MatcherAssert;\nimport org.junit.AfterClass;\nimport org.junit.BeforeClass;\nimport org.junit.Test;\n\nimport javax.management.MBeanOperationInfo;\nimport javax.management.ObjectName;\nimport javax.management.openmbean.*;\nimport java.net.InetAddress;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.UUID;\n\npublic class BootstrapTest {\n    private final static Integer DEFAULT_PORT = 8778;\n    private static JmxServer server;\n    private static InetAddress serverAddress;\n\n    private static ObjectName objectName;\n\n    @BeforeClass\n    public static void beforeClass() throws Exception {\n        serverAddress = InetAddress.getByName(\"127.0.0.1\");\n        objectName = new ObjectName(Bootstrap.MX_BEAN_OBJECT_NAME);\n    }\n\n    @AfterClass\n    public static void afterClass() {\n        IoUtils.closeQuietly(server);\n        System.gc();\n    }\n\n    @Test\n    public void should_work() throws Exception {\n        HashMap<String, String> source = new HashMap<>();\n\n        String text = UUID.randomUUID().toString();\n        String text2 = UUID.randomUUID().toString();\n        source.put(\"key\", text);\n        source.put(\"key2\", text2);\n        GlobalConfigs configs = new GlobalConfigs(source);\n\n        Bootstrap.registerMBeans(configs);\n        server = new JmxServer(serverAddress, DEFAULT_PORT);\n        server.start();\n        JmxClient jmxClient = new JmxClient(serverAddress, DEFAULT_PORT);\n\n        Map<String, String> cfg = mxBeanGetConfigs(jmxClient);\n        MatcherAssert.assertThat(cfg.get(\"key\"), CoreMatchers.equalTo(text));\n\n        source = new HashMap<>();\n        String helloValue = \"helloValue\";\n        source.put(\"hello\", helloValue);\n        mxBeanSetConfigs(jmxClient, source);\n        cfg = mxBeanGetConfigs(jmxClient);\n\n        MatcherAssert.assertThat(cfg.get(\"key\"), CoreMatchers.equalTo(text));\n        MatcherAssert.assertThat(cfg.get(\"hello\"), CoreMatchers.equalTo(helloValue));\n\n        jmxClient.close();\n    }\n\n    @SneakyThrows\n    private TabularData getUpdateConfigsOperationInfo(MBeanOperationInfo operationInfo,\n                                                             Map<String, String> source) {\n        OpenMBeanParameterInfoSupport p = (OpenMBeanParameterInfoSupport)operationInfo.getSignature()[0];\n\n        TabularType pType = (TabularType)p.getOpenType();\n        CompositeType rowType = pType.getRowType();\n\n        TabularDataSupport tabularData = new TabularDataSupport(pType);\n        for(Map.Entry<String, String> entry : source.entrySet()) {\n            Map<String, Object> imap = new HashMap<>();\n            imap.put(\"key\",  entry.getKey());\n            imap.put(\"value\", entry.getValue());\n            try {\n                CompositeData compositeData = new CompositeDataSupport(rowType, imap);\n                tabularData.put(compositeData);\n            } catch (OpenDataException e) {\n                throw new IllegalArgumentException(e.getMessage(),e);\n            }\n        }\n        return tabularData;\n    }\n\n    @SneakyThrows\n    private void mxBeanSetConfigs(JmxClient client, Map<String, String> source) {\n        MBeanOperationInfo operationInfo = client.getOperationInfo(objectName, \"updateConfigs\");\n        TabularData data = getUpdateConfigsOperationInfo(operationInfo, source);\n        client.invokeOperation(objectName, \"updateConfigs\", data);\n    }\n\n    @SneakyThrows\n    private Map<String, String> mxBeanGetConfigs(JmxClient client) {\n        Object a = client.getAttribute(objectName, \"Configs\");\n\n        TabularDataSupport d = (TabularDataSupport)  a;\n        HashMap<String, String> map = new HashMap<>();\n        for (Map.Entry<Object, Object> entry : d.entrySet()) {\n            CompositeDataSupport dd = (CompositeDataSupport)entry.getValue();\n            map.put(dd.get(\"key\").toString(), dd.get(\"value\").toString());\n        }\n\n        return map;\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/megaease/easeagent/core/HttpServerTest.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.core;\n\nimport com.megaease.easeagent.config.GlobalConfigs;\nimport com.megaease.easeagent.config.PluginConfigManager;\nimport com.megaease.easeagent.config.WrappedConfigManager;\nimport com.megaease.easeagent.core.config.PluginPropertiesHttpHandler;\nimport com.megaease.easeagent.core.config.PluginPropertyHttpHandler;\nimport com.megaease.easeagent.core.config.ServiceUpdateAgentHttpHandler;\nimport com.megaease.easeagent.httpserver.nano.AgentHttpHandler;\nimport com.megaease.easeagent.httpserver.nano.AgentHttpServer;\nimport com.megaease.easeagent.plugin.api.config.AutoRefreshPluginConfigRegistry;\nimport com.megaease.easeagent.plugin.api.config.IConfigFactory;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.config.PluginConfigChangeListener;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport org.junit.AfterClass;\nimport org.junit.Assert;\nimport org.junit.BeforeClass;\nimport org.junit.Test;\n\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.io.OutputStream;\nimport java.net.*;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Random;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicInteger;\n\npublic class HttpServerTest {\n    static WrappedConfigManager oldWrappedConfigManager;\n    static IConfigFactory originConfigFactory;\n\n    @BeforeClass\n    public static void before() throws NoSuchFieldException, IllegalAccessException {\n        oldWrappedConfigManager = GlobalAgentHolder.getWrappedConfigManager();\n        originConfigFactory = EaseAgent.configFactory;\n    }\n\n    @AfterClass\n    public static void after() throws NoSuchFieldException, IllegalAccessException {\n        setWrappedConfigManager(oldWrappedConfigManager);\n        EaseAgent.configFactory = originConfigFactory;\n    }\n\n\n    private static void setWrappedConfigManager(WrappedConfigManager wrappedConfigManager) throws NoSuchFieldException, IllegalAccessException {\n        GlobalAgentHolder.setWrappedConfigManager(wrappedConfigManager);\n    }\n\n    private static String runUpHttpServer() throws Exception {\n        int port = getPort();\n        return runUpHttpServer(port);\n    }\n\n    private static int getPort() throws IOException {\n        Random random = new Random();\n        for (int i = 0; i < 4; i++) {\n            int port = 8000 + random.nextInt(1000);\n            if (!isPortUsing(port)) {\n                return port;\n            }\n        }\n        throw new RuntimeException(\"can not found port for test.\");\n    }\n\n    private static boolean isPortUsing(int port) throws UnknownHostException {\n        ServerSocket s = null;\n        try {\n            s = new ServerSocket(port);\n        } catch (IOException e) {\n            if (e instanceof BindException) {\n                return true;\n            }\n        } finally {\n            if (s != null) {\n                try {\n                    s.close();\n                } catch (Exception e) {\n                    // ignored\n                }\n            }\n        }\n        return false;\n    }\n\n    private static String runUpHttpServer(int port) {\n        String httpServer = \"http://127.0.0.1:\" + port;\n        AgentHttpServer agentHttpServer = new AgentHttpServer(port);\n        List<AgentHttpHandler> list = new ArrayList<>();\n        list.add(new ServiceUpdateAgentHttpHandler());\n        list.add(new PluginPropertyHttpHandler());\n        list.add(new PluginPropertiesHttpHandler());\n        agentHttpServer.addHttpRoutes(list);\n        agentHttpServer.startServer();\n        return httpServer;\n    }\n\n    @Test\n    public void httpServer() throws Exception {\n        HashMap<String, String> source = new HashMap<>();\n        source.put(\"plugin.observability.global.metric.enabled\", \"true\");\n        source.put(\"plugin.observability.global.tracings.enabled\", \"true\");\n\n        source.put(\"plugin.observability.global.kafka-tracings.enabled\", \"true\");\n        source.put(\"plugin.observability.global.kafka-tracings.size\", \"12\");\n\n        source.put(\"plugin.observability.kafka.kafka-tracings.size\", \"13\");\n        source.put(\"plugin.observability.kafka.tracings.enabled\", \"true\");\n        source.put(\"plugin.observability.kafka.tracings.servicePrefix\", \"true\");\n        source.put(\"plugin.observability.kafka.metric.enabled\", \"true\");\n        source.put(\"plugin.observability.kafka.metric.interval\", \"30\");\n\n        source.put(\"plugin.observability.kafka.metric.topic\", \"platform-meter\");\n        source.put(\"plugin.observability.kafka.metric.appendType\", \"kafka\");\n\n        GlobalConfigs configs = new GlobalConfigs(source);\n        IConfigFactory iConfigFactory = PluginConfigManager.builder(configs).build();\n        AtomicInteger count = new AtomicInteger(0);\n\n        iConfigFactory.getConfig(\"observability\", \"kafka\", \"kafka-tracings\")\n            .addChangeListener(new PluginConfigChange(count, \"kafka.kafka-tracings\"));\n        iConfigFactory.getConfig(\"observability\", \"kafka\", \"tracings\")\n            .addChangeListener(new PluginConfigChange(count, \"kafka.tracings\"));\n        iConfigFactory.getConfig(\"observability\", \"kafka\", \"metric\")\n            .addChangeListener(new PluginConfigChange(count, \"kafka.metric\"));\n\n        WrappedConfigManager cfgMng = null;\n        try {\n            ClassLoader customClassLoader = Thread.currentThread().getContextClassLoader();\n            cfgMng = new WrappedConfigManager(customClassLoader, configs);\n            setWrappedConfigManager(cfgMng);\n        } catch (Exception e) {\n            System.out.println(\"\" + e.getMessage());\n        }\n        Assert.assertNotNull(cfgMng);\n\n        EaseAgent.configFactory = iConfigFactory;\n\n        String httpServer = runUpHttpServer();\n        Thread.sleep(100);\n        String resp;\n        resp = get(httpServer + \"/plugins/domains/observability/namespaces/kafka/metric/properties/interval/15/1\");\n        get(httpServer + \"/plugins/domains/observability/namespaces/kafka/metric/properties/interval/14/1\");\n        get(httpServer + \"/plugins/domains/observability/namespaces/kafka/metric/properties/interval/13/1\");\n        get(httpServer + \"/plugins/domains/observability/namespaces/kafka/metric/properties/interval/12/1\");\n        Assert.assertEquals(4, count.get());\n        get(httpServer + \"/plugins/domains/observability/namespaces/kafka/kafka-tracings/properties/enabled/false/1\");\n        get(httpServer + \"/plugins/domains/observability/namespaces/kafka/kafka-tracings/properties/enabled/true/1\");\n        Assert.assertEquals(6, count.get());\n        get(httpServer + \"/plugins/domains/observability/namespaces/global/tracings/properties/enabled/false/1\");\n        get(httpServer + \"/plugins/domains/observability/namespaces/global/tracings/properties/enabled/true/1\");\n        Assert.assertEquals(8, count.get());\n        post(httpServer + \"/plugins/domains/observability/namespaces/kafka/metric/properties\", \"{\\\"enabled\\\":\\\"false\\\",\\\"interval\\\": 15, \\\"version\\\": \\\"1\\\"}\");\n        post(httpServer + \"/plugins/domains/observability/namespaces/kafka/metric/properties\", \"{\\\"enabled\\\":\\\"true\\\",\\\"interval\\\": 15, \\\"version\\\": \\\"1\\\"}\");\n        post(httpServer + \"/plugins/domains/observability/namespaces/kafka/metric/properties\", \"{\\\"enabled\\\":\\\"true\\\",\\\"interval\\\": 13, \\\"version\\\": \\\"1\\\"}\");\n        Assert.assertEquals(11, count.get());\n\n        IPluginConfig config = AutoRefreshPluginConfigRegistry.getOrCreate(\"observability\", \"kafka\", \"metric\");\n        Assert.assertTrue(config.enabled());\n\n        get(httpServer + \"/plugins/domains/observability/namespaces/kafka/metric/properties/enabled/false/1\");\n\n        Assert.assertFalse(config.enabled());\n\n        get(httpServer + \"/plugins/domains/observability/namespaces/kafka/metric/properties/enabled/true/1\");\n        get(httpServer + \"/plugins/domains/observability/namespaces/global/metric/properties/enabled/false/1\");\n        Assert.assertFalse(config.enabled());\n\n        get(httpServer + \"/plugins/domains/observability/namespaces/global/metric/properties/enabled/true/1\");\n        resp = post(httpServer + \"/config\", \"{\\\"observability.metrics.jdbcConnection.enabled\\\":\\\"false\\\",\\\"observability.metrics.kafka.enabled\\\": \\\"false\\\", \\\"version\\\": \\\"1\\\"}\");\n        System.out.println(resp);\n\n        Thread.sleep(TimeUnit.SECONDS.toMillis(1));\n    }\n\n    static String get(String urlStr) throws IOException {\n        StringBuilder result = new StringBuilder();\n        URL url = new URL(urlStr);\n        HttpURLConnection conn = (HttpURLConnection) url.openConnection();\n        conn.setRequestMethod(\"GET\");\n        try (BufferedReader reader = new BufferedReader(\n            new InputStreamReader(conn.getInputStream()))) {\n            for (String line; (line = reader.readLine()) != null; ) {\n                result.append(line);\n            }\n        }\n        return result.toString();\n    }\n\n    static String post(String urlStr, String body) throws IOException {\n        String charset = \"UTF-8\";\n        StringBuilder result = new StringBuilder();\n        URL url = new URL(urlStr);\n        HttpURLConnection conn = (HttpURLConnection) url.openConnection();\n        conn.setDoOutput(true);\n        conn.setRequestProperty(\"Accept-Charset\", charset);\n        conn.setRequestMethod(\"POST\");\n        conn.setRequestProperty(\"Content-Type\", \"application/json\");\n        try (OutputStream output = conn.getOutputStream()) {\n            output.write(body.getBytes(charset));\n            output.flush();\n        }\n\n        try (BufferedReader reader = new BufferedReader(\n            new InputStreamReader(conn.getInputStream()))) {\n            for (String line; (line = reader.readLine()) != null; ) {\n                result.append(line);\n            }\n        }\n        return result.toString();\n    }\n\n    static class PluginConfigChange implements PluginConfigChangeListener {\n        final AtomicInteger count;\n        final String name;\n\n        public PluginConfigChange(AtomicInteger count, String name) {\n            this.count = count;\n            this.name = name;\n        }\n\n        @Override\n        public void onChange(IPluginConfig oldConfig, IPluginConfig newConfig) {\n            count.incrementAndGet();\n            System.out.printf(\"----------------------- on change %s begin ----------------------%n\", name);\n            System.out.println(\"old config:\");\n            printConfig(oldConfig);\n            System.out.println(\"\\n-------------------------------\\n\");\n            System.out.println(\"new config:\");\n            printConfig(newConfig);\n            System.out.printf(\"----------------------- on change %s end ---------------------- \\n\\n%n\", name);\n        }\n\n        public void printConfig(IPluginConfig config) {\n            for (String s : config.keySet()) {\n                String value = config.getString(s);\n                if (\"TRUE\".equalsIgnoreCase(value)) {\n                    System.out.printf(\"%s=%s%n\", s, config.getBoolean(s));\n                } else {\n                    System.out.printf(\"%s=%s%n\", s, config.getString(s));\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/megaease/easeagent/core/info/AgentInfoFactoryTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.core.info;\n\nimport com.megaease.easeagent.config.ConfigFactory;\nimport com.megaease.easeagent.config.Configs;\nimport com.megaease.easeagent.plugin.bridge.AgentInfo;\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\npublic class AgentInfoFactoryTest {\n\n    @Test\n    public void loadAgentInfo() {\n        AgentInfo config = AgentInfoFactory.loadAgentInfo(this.getClass().getClassLoader());\n        assertEquals(AgentInfoFactory.AGENT_TYPE, config.getType());\n        assertTrue(config.getVersion().matches(\".*\\\\d+\\\\.\\\\d+\\\\.\\\\d+.*\"));\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/megaease/easeagent/core/instrument/ClinitMethodTransformTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.core.instrument;\n\nimport com.megaease.easeagent.core.Bootstrap;\nimport com.megaease.easeagent.core.plugin.CommonInlineAdvice;\nimport com.megaease.easeagent.core.plugin.PluginLoader;\nimport com.megaease.easeagent.core.plugin.matcher.MethodTransformation;\nimport com.megaease.easeagent.core.utils.AgentAttachmentRule;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.interceptor.InterceptorProvider;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\nimport net.bytebuddy.agent.ByteBuddyAgent;\nimport net.bytebuddy.agent.builder.AgentBuilder;\nimport net.bytebuddy.dynamic.ClassFileLocator;\nimport net.bytebuddy.dynamic.loading.ByteArrayClassLoader;\nimport net.bytebuddy.dynamic.scaffold.TypeWriter;\nimport net.bytebuddy.matcher.ElementMatchers;\nimport org.junit.BeforeClass;\nimport org.junit.Rule;\nimport org.junit.rules.MethodRule;\n\nimport java.io.File;\nimport java.lang.instrument.ClassFileTransformer;\nimport java.lang.instrument.Instrumentation;\nimport java.util.Set;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.function.Supplier;\n\nimport static org.hamcrest.CoreMatchers.instanceOf;\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertTrue;\n\n@SuppressWarnings(\"all\")\npublic class ClinitMethodTransformTest extends TransformTestBase {\n    private static ClassLoader classLoader;\n    private static String dumpFolder;\n    private static final AtomicInteger globalIndex = new AtomicInteger(0);\n\n    private static String testString = \"kkk\";\n\n    @Rule\n    public MethodRule agentAttachmentRule = new AgentAttachmentRule();\n\n    @BeforeClass\n    public static void setUp() {\n        EaseAgent.initializeContextSupplier = TestContext::new;\n        classLoader = new ByteArrayClassLoader.ChildFirst(\n            NonStaticMethodTransformTest.class.getClassLoader(),\n            ClassFileLocator.ForClassLoader.readToNames(Foo.class,\n                FooClassInitInterceptor.class,\n                FooClsInitProvider.class,\n                CommonInlineAdvice.class),\n            ByteArrayClassLoader.PersistenceHandler.MANIFEST);\n\n        String path = \"target/test-classes\";\n        File file = new File(path);\n        dumpFolder = file.getAbsolutePath();\n        System.out.println(dumpFolder);\n        assertTrue(dumpFolder.endsWith(\"target\" + File.separator + \"test-classes\"));\n    }\n\n    // @Test\n    // @AgentAttachmentRule.Enforce\n    public void testTypeInitialAdviceTransformer() throws Exception {\n        System.setProperty(TypeWriter.DUMP_PROPERTY, dumpFolder);\n        assertEquals(System.getProperty(TypeWriter.DUMP_PROPERTY), dumpFolder);\n\n        assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));\n        AgentBuilder builder = Bootstrap.getAgentBuilder(null, true);\n\n        Set<MethodTransformation> transformations = getMethodTransformations(globalIndex.incrementAndGet(),\n            CLASS_INIT, new FooClsInitProvider());\n\n        ClassFileTransformer classFileTransformer = builder\n            .type(ElementMatchers.is(Foo.class), ElementMatchers.is(classLoader))\n            .transform(PluginLoader.compound(false, transformations, null))\n            .installOnByteBuddyAgent();\n\n        try {\n            Class<?> type = classLoader.loadClass(Foo.class.getName());\n            AgentFieldReflectAccessor.setStaticFieldValue(type, \"clazzInitString\", BAR + QUX);\n            testString = AgentFieldReflectAccessor.getStaticFieldValue(type, \"clazzInitString\");\n            assertEquals(BAR + QUX, testString);\n            // wait to finish\n        } finally {\n            assertThat(ByteBuddyAgent.getInstrumentation().removeTransformer(classFileTransformer), is(true));\n        }\n    }\n\n    @SuppressWarnings(\"unused\")\n    public static class Foo {\n        static String clazzInitString = FOO;\n\n        public static String fooStatic(String a) {\n            return a;\n        }\n\n        public String foo(String a) {\n            return a;\n        }\n    }\n\n    public static class FooClassInitInterceptor implements Interceptor {\n        @Override\n        public void before(MethodInfo methodInfo, Context context) {\n            System.out.println(\"aaa\");\n        }\n\n        @Override\n        public void after(MethodInfo methodInfo, Context context) {\n            testString = BAR + QUX;\n        }\n\n        @Override\n        public int order() {\n            return Order.HIGH.getOrder();\n        }\n    }\n\n    static class FooClsInitProvider implements InterceptorProvider {\n        @Override\n        public Supplier<Interceptor> getInterceptorProvider() {\n            return FooClassInitInterceptor::new;\n        }\n\n        @Override\n        public String getAdviceTo() {\n            return \"\";\n        }\n\n        @Override\n        public String getPluginClassName() {\n            return TestPlugin.class.getCanonicalName();\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/megaease/easeagent/core/instrument/NewInstanceMethodTransformTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.core.instrument;\n\nimport com.megaease.easeagent.core.Bootstrap;\nimport com.megaease.easeagent.core.plugin.CommonInlineAdvice;\nimport com.megaease.easeagent.core.plugin.PluginLoader;\nimport com.megaease.easeagent.core.plugin.matcher.MethodTransformation;\nimport com.megaease.easeagent.core.utils.AgentAttachmentRule;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\nimport net.bytebuddy.agent.ByteBuddyAgent;\nimport net.bytebuddy.agent.builder.AgentBuilder;\nimport net.bytebuddy.dynamic.ClassFileLocator;\nimport net.bytebuddy.dynamic.loading.ByteArrayClassLoader;\nimport net.bytebuddy.dynamic.scaffold.TypeWriter;\nimport net.bytebuddy.matcher.ElementMatchers;\nimport org.junit.BeforeClass;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.MethodRule;\n\nimport java.io.File;\nimport java.lang.instrument.ClassFileTransformer;\nimport java.lang.instrument.Instrumentation;\nimport java.lang.reflect.Constructor;\nimport java.util.Set;\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport static net.bytebuddy.matcher.ElementMatchers.hasSuperType;\nimport static net.bytebuddy.matcher.ElementMatchers.named;\nimport static org.hamcrest.CoreMatchers.instanceOf;\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertTrue;\n\npublic class NewInstanceMethodTransformTest extends TransformTestBase {\n    private static ClassLoader classLoader;\n    private static String dumpFolder;\n    private static final AtomicInteger globalIndex = new AtomicInteger(1000);\n\n    @Rule\n    public MethodRule agentAttachmentRule = new AgentAttachmentRule();\n\n    @BeforeClass\n    public static void setUp() {\n        EaseAgent.initializeContextSupplier = TestContext::new;\n        classLoader = new ByteArrayClassLoader.ChildFirst(\n            NewInstanceMethodTransformTest.class.getClassLoader(),\n            ClassFileLocator.ForClassLoader\n                .readToNames(NewInstanceMethodTransformTest.Foo.class, CommonInlineAdvice.class),\n            ByteArrayClassLoader.PersistenceHandler.MANIFEST);\n\n        String path = \"target\" + File.separator + \"test-classes\";\n        File file = new File(path);\n        dumpFolder = file.getAbsolutePath();\n        System.out.println(dumpFolder);\n        assertTrue(dumpFolder.endsWith(\"target\" + File.separator + \"test-classes\"));\n    }\n\n    @Test\n    @AgentAttachmentRule.Enforce\n    public void testClassInstanceTransformer() throws Exception {\n        System.setProperty(TypeWriter.DUMP_PROPERTY, dumpFolder);\n        assertEquals(System.getProperty(TypeWriter.DUMP_PROPERTY), dumpFolder);\n\n        assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));\n        AgentBuilder builder = Bootstrap.getAgentBuilder(null, true);\n\n        IMethodMatcher m = MethodMatcher.builder()\n            .named(\"<init>\")\n            .argsLength(1)\n            .arg(0, \"java.lang.String\")\n            .build();\n\n        Set<MethodTransformation> transformations = getMethodTransformations(globalIndex.incrementAndGet(),\n            m, new FooProvider());\n\n        ClassFileTransformer classFileTransformer = builder\n            .type(hasSuperType(named(FooBase.class.getName())), ElementMatchers.is(classLoader))\n            .transform(PluginLoader.compound(true, transformations, null))\n            .installOnByteBuddyAgent();\n        try {\n            Class<?> type = classLoader.loadClass(Foo.class.getName());\n            // check\n            Constructor<?> c = type.getDeclaredConstructor(String.class);\n            Object instance = c.newInstance(\"kkk\");\n            assertThat(type.getDeclaredMethod(\"getInstanceT\")\n                    .invoke(instance),\n                is(QUX));\n\n            // test arg(idx, t)\n            Constructor<?> d = type.getDeclaredConstructor(CharSequence.class);\n            instance = d.newInstance(BAR);\n            assertThat(type.getDeclaredMethod(\"getInstanceT\")\n                    .invoke(instance),\n                is(BAR));\n\n            // test argLength\n            instance = type.getDeclaredConstructor(String.class, String.class).newInstance(QUX, BAR);\n            assertThat(type.getDeclaredMethod(\"getInstanceT\")\n                    .invoke(instance),\n                is(QUX + BAR));\n        } finally {\n            assertThat(ByteBuddyAgent.getInstrumentation().removeTransformer(classFileTransformer), is(true));\n        }\n    }\n\n    @SuppressWarnings(\"unused\")\n    public static class Foo extends FooBase {\n        public String instanceT;\n\n        static String clazzInitString = FOO;\n\n        public static String fooStatic(String a) {\n            return a;\n        }\n\n        public Foo(String a) {\n            this.instanceT = a;\n            System.out.println(\"init:\" + this.instanceT);\n        }\n\n        public Foo(CharSequence a) {\n            this.instanceT = a.toString();\n            System.out.println(\"init:\" + this.instanceT);\n        }\n\n        public Foo(String a, String b) {\n            this.instanceT = a + b;\n            System.out.println(\"init:\" + this.instanceT);\n        }\n\n        public String getInstanceT() {\n            return this.instanceT;\n        }\n\n        public String foo(String a) {\n            return a;\n        }\n\n        public int baz() {\n            return (int) System.currentTimeMillis();\n        }\n    }\n\n    public interface FooInterface {\n        String foo(String a);\n    }\n\n    public static class FooBase implements FooInterface {\n        public String foo(String a) {\n            return a + \"-base\";\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/megaease/easeagent/core/instrument/NonStaticMethodTransformTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.core.instrument;\n\nimport com.megaease.easeagent.core.Bootstrap;\nimport com.megaease.easeagent.core.plugin.CommonInlineAdvice;\nimport com.megaease.easeagent.core.plugin.PluginLoader;\nimport com.megaease.easeagent.core.plugin.matcher.MethodTransformation;\nimport com.megaease.easeagent.core.utils.AgentAttachmentRule;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.field.AgentDynamicFieldAccessor;\nimport net.bytebuddy.agent.ByteBuddyAgent;\nimport net.bytebuddy.agent.builder.AgentBuilder;\nimport net.bytebuddy.dynamic.ClassFileLocator;\nimport net.bytebuddy.dynamic.loading.ByteArrayClassLoader;\nimport net.bytebuddy.dynamic.scaffold.TypeWriter;\nimport net.bytebuddy.matcher.ElementMatchers;\nimport org.junit.BeforeClass;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.MethodRule;\n\nimport java.io.File;\nimport java.lang.instrument.ClassFileTransformer;\nimport java.lang.instrument.Instrumentation;\nimport java.util.Set;\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport static net.bytebuddy.matcher.ElementMatchers.hasSuperType;\nimport static net.bytebuddy.matcher.ElementMatchers.named;\nimport static org.hamcrest.CoreMatchers.instanceOf;\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertTrue;\n\n@SuppressWarnings(\"unused\")\npublic class NonStaticMethodTransformTest extends TransformTestBase {\n    private static ClassLoader classLoader;\n    private static String dumpFolder;\n    private static final AtomicInteger globalIndex = new AtomicInteger(1000);\n\n    @Rule\n    public MethodRule agentAttachmentRule = new AgentAttachmentRule();\n\n    @BeforeClass\n    public static void setUp() {\n        EaseAgent.initializeContextSupplier = TestContext::new;\n        classLoader = new ByteArrayClassLoader.ChildFirst(\n            NonStaticMethodTransformTest.class.getClassLoader(),\n            ClassFileLocator.ForClassLoader.readToNames(Foo.class, CommonInlineAdvice.class),\n            ByteArrayClassLoader.PersistenceHandler.MANIFEST);\n\n        String path = \"target\" + File.separator + \"test-classes\";\n        File file = new File(path);\n        dumpFolder = file.getAbsolutePath();\n        System.out.println(dumpFolder);\n        assertTrue(dumpFolder.endsWith(\"target\" + File.separator + \"test-classes\"));\n    }\n\n    @Test\n    @AgentAttachmentRule.Enforce\n    public void testAdviceTransformer() throws Exception {\n        System.setProperty(TypeWriter.DUMP_PROPERTY, dumpFolder);\n        assertEquals(System.getProperty(TypeWriter.DUMP_PROPERTY), dumpFolder);\n\n        assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));\n        AgentBuilder builder = Bootstrap.getAgentBuilder(null, true);\n\n        Set<MethodTransformation> transformations = getMethodTransformations(globalIndex.incrementAndGet(),\n            FOO, new FooProvider());\n\n        ClassFileTransformer classFileTransformer = builder\n            .type(hasSuperType(named(FooBase.class.getName())), ElementMatchers.is(classLoader))\n            .transform(PluginLoader.compound(true, transformations, null))\n            .installOnByteBuddyAgent();\n        try {\n            Class<?> type = classLoader.loadClass(Foo.class.getName());\n            // check\n            Object instance = type.getDeclaredConstructor(String.class).newInstance(\"kkk\");\n            AgentDynamicFieldAccessor.setDynamicFieldValue(instance, BAR);\n            assertEquals(BAR, AgentDynamicFieldAccessor.getDynamicFieldValue(instance));\n            assertThat(type.getDeclaredMethod(FOO, String.class)\n                    .invoke(instance, \"kkk\"),\n                is(QUX + BAR));\n        } finally {\n            assertThat(ByteBuddyAgent.getInstrumentation().removeTransformer(classFileTransformer), is(true));\n        }\n    }\n\n    @SuppressWarnings(\"unused\")\n    public static class Foo extends FooBase {\n        public String instanceT;\n\n        static String clazzInitString = FOO;\n\n        public static String fooStatic(String a) {\n            return a;\n        }\n\n        public Foo(String a) {\n            this.instanceT = a;\n            System.out.println(\"init:\" + this.instanceT);\n        }\n\n        public String getInstanceT() {\n            return this.instanceT;\n        }\n\n        public String foo(String a) {\n            return a;\n        }\n\n        public int baz() {\n            return (int) System.currentTimeMillis();\n        }\n    }\n\n    public interface FooInterface {\n        String foo(String a);\n    }\n\n    public static class FooBase implements FooInterface {\n        public String foo(String a) {\n            return a + \"-base\";\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/megaease/easeagent/core/instrument/OrchestrationTransformTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.core.instrument;\n\nimport com.megaease.easeagent.core.Bootstrap;\nimport com.megaease.easeagent.core.plugin.CommonInlineAdvice;\nimport com.megaease.easeagent.core.plugin.PluginLoader;\nimport com.megaease.easeagent.core.plugin.matcher.MethodTransformation;\nimport com.megaease.easeagent.core.utils.AgentAttachmentRule;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.field.AgentDynamicFieldAccessor;\nimport net.bytebuddy.agent.ByteBuddyAgent;\nimport net.bytebuddy.agent.builder.AgentBuilder;\nimport net.bytebuddy.dynamic.ClassFileLocator;\nimport net.bytebuddy.dynamic.loading.ByteArrayClassLoader;\nimport net.bytebuddy.dynamic.scaffold.TypeWriter;\nimport net.bytebuddy.matcher.ElementMatchers;\nimport org.junit.BeforeClass;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.MethodRule;\n\nimport java.io.File;\nimport java.lang.instrument.ClassFileTransformer;\nimport java.lang.instrument.Instrumentation;\nimport java.util.Set;\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport static net.bytebuddy.matcher.ElementMatchers.hasSuperType;\nimport static net.bytebuddy.matcher.ElementMatchers.named;\nimport static org.hamcrest.CoreMatchers.instanceOf;\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertTrue;\n\n@SuppressWarnings(\"unused\")\npublic class OrchestrationTransformTest extends TransformTestBase {\n    private static ClassLoader classLoader;\n    private static String dumpFolder;\n    private static final AtomicInteger globalIndex = new AtomicInteger(1000);\n\n    @Rule\n    public MethodRule agentAttachmentRule = new AgentAttachmentRule();\n\n    @BeforeClass\n    public static void setUp() {\n        EaseAgent.initializeContextSupplier = TestContext::new;\n        classLoader = new ByteArrayClassLoader.ChildFirst(\n            OrchestrationTransformTest.class.getClassLoader(),\n            ClassFileLocator.ForClassLoader.readToNames(Foo.class, CommonInlineAdvice.class),\n            ByteArrayClassLoader.PersistenceHandler.MANIFEST);\n\n        String path = \"target\" + File.separator + \"test-classes\";\n        File file = new File(path);\n        dumpFolder = file.getAbsolutePath();\n        System.out.println(dumpFolder);\n        assertTrue(dumpFolder.endsWith(\"target\" + File.separator + \"test-classes\"));\n    }\n\n    @Test\n    @AgentAttachmentRule.Enforce\n    public void testOrchestration() {\n        System.setProperty(TypeWriter.DUMP_PROPERTY, dumpFolder);\n        assertEquals(System.getProperty(TypeWriter.DUMP_PROPERTY), dumpFolder);\n\n        assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));\n        AgentBuilder builder = Bootstrap.getAgentBuilder(null, true);\n\n        Set<MethodTransformation> transformations\n            = getMethodTransformations(globalIndex.incrementAndGet(), FOO, new FooProvider());\n        Set<MethodTransformation> secTransformations\n            = getMethodTransformations(globalIndex.incrementAndGet(), FOO, new FooSecProvider());\n\n        ClassFileTransformer classFileTransformer = builder\n            .type(hasSuperType(named(FooBase.class.getName())), ElementMatchers.is(classLoader))\n            .transform(PluginLoader.compound(true, transformations, null))\n            .transform(PluginLoader.compound(true, secTransformations, null))\n            .installOnByteBuddyAgent();\n        try {\n            Class<?> type = classLoader.loadClass(Foo.class.getName());\n            // check\n            Object instance = type.getDeclaredConstructor().newInstance();\n            AgentDynamicFieldAccessor.setDynamicFieldValue(instance, BAR);\n            assertEquals(BAR, AgentDynamicFieldAccessor.getDynamicFieldValue(instance));\n            assertThat(type.getDeclaredMethod(FOO, String.class)\n                    .invoke(instance, \"kkk\"),\n                is(BAR + QUX + BAR));\n        } catch (Exception e) {\n            System.out.println(e.getMessage());\n        } finally {\n            assertThat(ByteBuddyAgent.getInstrumentation().removeTransformer(classFileTransformer), is(true));\n        }\n    }\n\n    @SuppressWarnings(\"unused\")\n    public static class Foo extends FooBase {\n        static String clazzInitString = FOO;\n\n        public static String fooStatic(String a) {\n            return a;\n        }\n\n        public String foo(String a) {\n            return a;\n        }\n\n        public int baz() {\n            return (int) System.currentTimeMillis();\n        }\n    }\n\n    public interface FooInterface {\n        String foo(String a);\n    }\n\n    public static class FooBase implements FooInterface {\n        public String foo(String a) {\n            return a + \"-base\";\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/megaease/easeagent/core/instrument/StaticMethodTransformTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.core.instrument;\n\nimport com.megaease.easeagent.core.Bootstrap;\nimport com.megaease.easeagent.core.plugin.CommonInlineAdvice;\nimport com.megaease.easeagent.core.plugin.PluginLoader;\nimport com.megaease.easeagent.core.plugin.matcher.MethodTransformation;\nimport com.megaease.easeagent.core.utils.AgentAttachmentRule;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.InterceptorProvider;\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport net.bytebuddy.agent.ByteBuddyAgent;\nimport net.bytebuddy.agent.builder.AgentBuilder;\nimport net.bytebuddy.dynamic.ClassFileLocator;\nimport net.bytebuddy.dynamic.loading.ByteArrayClassLoader;\nimport net.bytebuddy.dynamic.scaffold.TypeWriter;\nimport net.bytebuddy.matcher.ElementMatchers;\nimport org.junit.BeforeClass;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.MethodRule;\n\nimport java.io.File;\nimport java.lang.instrument.ClassFileTransformer;\nimport java.lang.instrument.Instrumentation;\nimport java.util.Set;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.function.Supplier;\n\nimport static org.hamcrest.CoreMatchers.instanceOf;\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertTrue;\n\npublic class StaticMethodTransformTest extends TransformTestBase {\n    private static ClassLoader classLoader;\n    private static String dumpFolder;\n    private static final AtomicInteger globalIndex = new AtomicInteger(0);\n\n    @Rule\n    public MethodRule agentAttachmentRule = new AgentAttachmentRule();\n\n    @BeforeClass\n    public static void setUp() {\n        EaseAgent.initializeContextSupplier = TestContext::new;\n        classLoader = new ByteArrayClassLoader.ChildFirst(\n            NonStaticMethodTransformTest.class.getClassLoader(),\n            ClassFileLocator.ForClassLoader.readToNames(Foo.class, CommonInlineAdvice.class),\n            ByteArrayClassLoader.PersistenceHandler.MANIFEST);\n\n        String path = \"target\" + File.separator + \"test-classes\";\n        File file = new File(path);\n        dumpFolder = file.getAbsolutePath();\n        System.out.println(dumpFolder);\n        assertTrue(dumpFolder.endsWith(\"target\" + File.separator + \"test-classes\"));\n    }\n\n    @Test\n    @AgentAttachmentRule.Enforce\n    public void testStaticAdviceTransformer() throws Exception {\n        System.setProperty(TypeWriter.DUMP_PROPERTY, dumpFolder);\n        assertEquals(System.getProperty(TypeWriter.DUMP_PROPERTY), dumpFolder);\n\n        assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));\n        AgentBuilder builder = Bootstrap.getAgentBuilder(null, true);\n\n        Set<MethodTransformation> transformations = getMethodTransformations(globalIndex.incrementAndGet(),\n            FOO_STATIC, new FooProvider());\n\n        ClassFileTransformer classFileTransformer = builder\n            .type(ElementMatchers.is(Foo.class), ElementMatchers.is(classLoader))\n            .transform(PluginLoader.compound(false, transformations, null))\n            .installOnByteBuddyAgent();\n\n        try {\n            Class<?> type = classLoader.loadClass(Foo.class.getName());\n            // check\n            assertThat(type.getDeclaredMethod(FOO_STATIC, String.class)\n                    .invoke(null, \"kkk\"),\n                is(QUX + BAR));\n        } finally {\n            assertThat(ByteBuddyAgent.getInstrumentation().removeTransformer(classFileTransformer), is(true));\n        }\n    }\n\n    @Test\n    @AgentAttachmentRule.Enforce\n    public void testTypeInitialAdviceTransformer() throws Exception {\n        System.setProperty(TypeWriter.DUMP_PROPERTY, dumpFolder);\n        assertEquals(System.getProperty(TypeWriter.DUMP_PROPERTY), dumpFolder);\n\n        assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));\n        AgentBuilder builder = Bootstrap.getAgentBuilder(null, true);\n\n        Set<MethodTransformation> transformations = getMethodTransformations(globalIndex.incrementAndGet(),\n            CLASS_INIT, new FooClsInitProvider());\n\n        ClassFileTransformer classFileTransformer = builder\n            .type(ElementMatchers.is(Foo.class), ElementMatchers.is(classLoader))\n            .transform(PluginLoader.compound(false, transformations, null))\n            .installOnByteBuddyAgent();\n\n        try {\n            Class<?> type = classLoader.loadClass(Foo.class.getName());\n            // check, wait to finish\n            // assertEquals(Foo.clazzInitString, BAR);\n        } finally {\n            assertThat(ByteBuddyAgent.getInstrumentation().removeTransformer(classFileTransformer), is(true));\n        }\n    }\n\n    @SuppressWarnings(\"unused\")\n    public static class Foo {\n        static String clazzInitString = FOO;\n\n        public static String fooStatic(String a) {\n            return a;\n        }\n\n        public String foo(String a) {\n            return a;\n        }\n    }\n\n    public static class FooClassInitInterceptor implements Interceptor {\n        @Override\n        public void before(MethodInfo methodInfo, Context context) {\n        }\n\n        @Override\n        public void after(MethodInfo methodInfo, Context context) {\n            Foo.clazzInitString = BAR;\n        }\n\n        @Override\n        public int order() {\n            return Order.HIGH.getOrder();\n        }\n    }\n\n    static class FooClsInitProvider implements InterceptorProvider {\n        @Override\n        public Supplier<Interceptor> getInterceptorProvider() {\n            return FooClassInitInterceptor::new;\n        }\n\n        @Override\n        public String getAdviceTo() {\n            return \"\";\n        }\n\n        @Override\n        public String getPluginClassName() {\n            return TestPlugin.class.getCanonicalName();\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/megaease/easeagent/core/instrument/TestContext.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.core.instrument;\n\nimport com.megaease.easeagent.plugin.bridge.NoOpContext;\n\npublic class TestContext extends NoOpContext.NoopContext {\n    @Override\n    public boolean isNoop() {\n        return false;\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/megaease/easeagent/core/instrument/TestPlugin.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.core.instrument;\n\nimport com.megaease.easeagent.plugin.AgentPlugin;\n\npublic class TestPlugin implements AgentPlugin {\n    @Override\n    public String getNamespace() {\n        return \"test\";\n    }\n\n    @Override\n    public String getDomain() {\n        return \"observability\";\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/megaease/easeagent/core/instrument/TransformTestBase.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.core.instrument;\n\nimport com.megaease.easeagent.core.plugin.interceptor.ProviderChain;\nimport com.megaease.easeagent.core.plugin.interceptor.ProviderPluginDecorator;\nimport com.megaease.easeagent.core.plugin.matcher.MethodMatcherConvert;\nimport com.megaease.easeagent.core.plugin.matcher.MethodTransformation;\nimport com.megaease.easeagent.core.plugin.registry.PluginRegistry;\nimport com.megaease.easeagent.plugin.interceptor.InterceptorProvider;\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.HashSet;\nimport java.util.Set;\nimport java.util.function.Supplier;\n\npublic class TransformTestBase {\n    protected static final String FOO = \"foo\",\n        BAR = \"bar\",\n        QUX = \"qux\",\n        CLASS_INIT = \"<clinit>\",\n        FOO_STATIC = \"fooStatic\";\n\n    @SuppressWarnings(\"all\")\n    protected Set<MethodTransformation> getMethodTransformations(int index,\n                                                               String methodName,\n                                                               InterceptorProvider provider) {\n        IMethodMatcher m = MethodMatcher.builder().named(methodName).build();\n        return getMethodTransformations(index, m, provider);\n    }\n\n    @SuppressWarnings(\"all\")\n    protected Set<MethodTransformation> getMethodTransformations(int index,\n                                                                 IMethodMatcher m,\n                                                                 InterceptorProvider provider) {\n        ProviderChain.Builder providerBuilder = ProviderChain.builder();\n        providerBuilder.addProvider(new ProviderPluginDecorator(new TestPlugin(), provider));\n\n        MethodTransformation methodTransformation = new MethodTransformation(index,\n            MethodMatcherConvert.INSTANCE.convert(m),\n            providerBuilder);\n        PluginRegistry.addMethodTransformation(index, methodTransformation);\n\n        Set<MethodTransformation> transformations = new HashSet<>();\n        transformations.add(methodTransformation);\n\n        return transformations;\n    }\n\n    public static class FooInstInterceptor implements Interceptor {\n        @Override\n        public void before(MethodInfo methodInfo, Context context) {\n            Object [] args = methodInfo.getArgs();\n            args[0] = QUX;\n            methodInfo.markChanged();\n        }\n\n        @Override\n        public void after(MethodInfo methodInfo, Context context) {\n        }\n\n        @Override\n        public int order() {\n            return Order.HIGHEST.getOrder();\n        }\n    }\n\n    public static class FooInterceptor implements Interceptor {\n        @Override\n        public void before(MethodInfo methodInfo, Context context) {\n            Object [] args = methodInfo.getArgs();\n            args[0] = QUX;\n            methodInfo.markChanged();\n        }\n\n        @Override\n        public void after(MethodInfo methodInfo, Context context) {\n            methodInfo.setRetValue(methodInfo.getRetValue() + BAR);\n        }\n\n        @Override\n        public int order() {\n            return Order.HIGHEST.getOrder();\n        }\n    }\n\n    public static class FooSecondInterceptor implements Interceptor {\n        @Override\n        public void before(MethodInfo methodInfo, Context context) {\n            Object [] args = methodInfo.getArgs();\n            args[0] = BAR;\n            methodInfo.markChanged();\n        }\n\n        @Override\n        public void after(MethodInfo methodInfo, Context context) {\n            methodInfo.setRetValue(methodInfo.getRetValue() + QUX);\n        }\n\n        @Override\n        public int order() {\n            return Order.LOW.getOrder();\n        }\n    }\n\n    static class FooProvider implements InterceptorProvider {\n        @Override\n        public Supplier<Interceptor> getInterceptorProvider() {\n            return FooInterceptor::new;\n        }\n\n        @Override\n        public String getAdviceTo() {\n            return \"\";\n        }\n\n        @Override\n        public String getPluginClassName() {\n            return TestPlugin.class.getCanonicalName();\n        }\n    }\n\n    static class FooInstProvider implements InterceptorProvider {\n        @Override\n        public Supplier<Interceptor> getInterceptorProvider() {\n            return FooInstInterceptor::new;\n        }\n\n        @Override\n        public String getAdviceTo() {\n            return \"\";\n        }\n\n        @Override\n        public String getPluginClassName() {\n            return TestPlugin.class.getCanonicalName();\n        }\n    }\n\n    static class FooSecProvider implements InterceptorProvider {\n        @Override\n        public Supplier<Interceptor> getInterceptorProvider() {\n            return FooSecondInterceptor::new;\n        }\n\n        @Override\n        public String getAdviceTo() {\n            return \"\";\n        }\n\n        @Override\n        public String getPluginClassName() {\n            return TestPlugin.class.getCanonicalName();\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/megaease/easeagent/core/matcher/ClassLoaderMatcherTest.java",
    "content": "/*\n * Copyright (c) 2022, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.core.matcher;\n\nimport com.megaease.easeagent.core.Bootstrap;\nimport com.megaease.easeagent.core.plugin.matcher.ClassLoaderMatcherConvert;\nimport com.megaease.easeagent.plugin.matcher.loader.ClassLoaderMatcher;\nimport net.bytebuddy.matcher.ElementMatcher;\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport java.net.URL;\nimport java.net.URLClassLoader;\n\npublic class ClassLoaderMatcherTest {\n    @Test\n    public void test_convert() {\n        ElementMatcher<ClassLoader> matcher;\n        // bootstrap\n        matcher = ClassLoaderMatcherConvert.INSTANCE.convert(ClassLoaderMatcher.BOOTSTRAP);\n        Assert.assertTrue(matcher.matches(null));\n        Assert.assertFalse(matcher.matches(ClassLoader.getSystemClassLoader()));\n\n        // external\n        matcher = ClassLoaderMatcherConvert.INSTANCE.convert(ClassLoaderMatcher.EXTERNAL);\n        Assert.assertFalse(matcher.matches(null));\n        Assert.assertFalse(matcher.matches(ClassLoader.getSystemClassLoader()));\n        Assert.assertTrue(matcher.matches(ClassLoader.getSystemClassLoader().getParent()));\n\n        // system\n        matcher = ClassLoaderMatcherConvert.INSTANCE.convert(ClassLoaderMatcher.SYSTEM);\n        Assert.assertFalse(matcher.matches(ClassLoader.getSystemClassLoader().getParent()));\n        Assert.assertTrue(matcher.matches(ClassLoader.getSystemClassLoader()));\n\n        // agent\n        matcher = ClassLoaderMatcherConvert.INSTANCE.convert(ClassLoaderMatcher.AGENT);\n        Assert.assertTrue(matcher.matches(Bootstrap.class.getClassLoader()));\n        Assert.assertFalse(matcher.matches(ClassLoader.getSystemClassLoader().getParent()));\n\n        // name\n        matcher = ClassLoaderMatcherConvert.INSTANCE\n            .convert(new ClassLoaderMatcher(\"com.megaease.easeagent.core.matcher.ClassLoaderMatcherTest.TestClassLoader\"));\n        URL[] urls = new URL[1];\n        urls[0] = this.getClass().getProtectionDomain().getCodeSource().getLocation();\n        Assert.assertFalse(matcher.matches(Bootstrap.class.getClassLoader()));\n        Assert.assertTrue(matcher.matches(new TestClassLoader(urls)));\n    }\n\n    static class TestClassLoader extends URLClassLoader {\n        public TestClassLoader(URL[] urls) {\n            super(urls);\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/megaease/easeagent/core/matcher/ClassMatcherTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.core.matcher;\n\nimport com.megaease.easeagent.core.plugin.annotation.Index;\nimport com.megaease.easeagent.core.plugin.matcher.ClassMatcherConvert;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport net.bytebuddy.description.type.TypeDescription;\nimport net.bytebuddy.matcher.ElementMatcher;\nimport org.junit.Assert;\nimport org.junit.Test;\n\npublic class ClassMatcherTest {\n    public static class TestBaseClass {\n    }\n\n    public interface TestInterface {\n    }\n\n    public interface TestInterface2 extends TestInterface {\n    }\n\n    @Index\n    public static class TestClass extends TestBaseClass implements TestInterface {\n    }\n\n    public static class TestClass2 extends TestBaseClass implements TestInterface2 {\n    }\n\n    @Test\n    public void testMatch() {\n        // super class matcher\n        IClassMatcher matcher = ClassMatcher.builder()\n            .hasSuperClass(TestBaseClass.class.getName()).build();\n\n        ElementMatcher<TypeDescription> eMatcher = ClassMatcherConvert.INSTANCE.convert(matcher);\n        TypeDescription type = TypeDescription.ForLoadedType.of(TestClass.class);\n        Assert.assertTrue(eMatcher.matches(type));\n\n        // test annotation match\n        matcher = ClassMatcher.builder()\n            .hasInterface(TestInterface.class.getName())\n            .hasAnnotation(Index.class.getName())\n            .build();\n        eMatcher = ClassMatcherConvert.INSTANCE.convert(matcher);\n\n        Assert.assertTrue(eMatcher.matches(type));\n\n        // no interface\n        matcher = ClassMatcher.builder()\n            .hasInterface(TestInterface.class.getName())\n            .notInterface()\n            .build();\n        type = TypeDescription.ForLoadedType.of(TestInterface2.class);\n        eMatcher = ClassMatcherConvert.INSTANCE.convert(matcher);\n        Assert.assertFalse(eMatcher.matches(type));\n        type = TypeDescription.ForLoadedType.of(TestClass.class);\n        Assert.assertTrue(eMatcher.matches(type));\n\n        // negate test\n        matcher = ClassMatcher.builder()\n            .hasSuperClass(TestBaseClass.class.getName())\n            .and()\n            .hasInterface(TestInterface2.class.getName())\n            .negate()\n            .build();\n        eMatcher = ClassMatcherConvert.INSTANCE.convert(matcher);\n        type = TypeDescription.ForLoadedType.of(TestClass.class);\n        Assert.assertTrue(eMatcher.matches(type));\n        type = TypeDescription.ForLoadedType.of(TestClass2.class);\n        Assert.assertFalse(eMatcher.matches(type));\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/megaease/easeagent/core/matcher/MethodMatcherTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.core.matcher;\n\nimport com.megaease.easeagent.core.plugin.matcher.MethodMatcherConvert;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\nimport net.bytebuddy.description.method.MethodDescription;\nimport net.bytebuddy.matcher.ElementMatcher;\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport java.lang.reflect.Method;\nimport java.sql.Statement;\n\npublic class MethodMatcherTest {\n    @SuppressWarnings(\"unused\")\n    interface FooInterface {\n        void basicPublish(int a1, int a2, int a3, int a4);\n    }\n\n    @SuppressWarnings(\"unused\")\n    static class Foo implements FooInterface {\n        @Override\n        public void basicPublish(int a1, int a2, int a3, int a4) {\n            System.out.println(\"sum:\" + a1 + a2 + a3 + a4);\n        }\n\n        public String basicPublish(int a2, int a3, int a4) {\n            return \"\";\n        }\n\n        void basicConsume(int a1, int a2, int a3, int a4) {\n            System.out.println(\"sum:\" + a1 + a2 + a3 + a4);\n        }\n\n        int basicConsume(char a1, int a2, int a3, int a4) {\n            System.out.println(\"sum:\" + a1 + a2 + a3 + a4);\n            return 0;\n        }\n    }\n\n    private IClassMatcher named(String name) {\n        return ClassMatcher.builder().hasClassName(name).isInterface()\n            .build();\n    }\n\n    @Test\n    public void testIsOverriddenFrom() {\n        IClassMatcher o = named(FooInterface.class.getName())\n            .or(named(Statement.class.getName()));\n\n        // overridden from test\n        IMethodMatcher m = MethodMatcher.builder()\n            .isOverriddenFrom(o)\n            .build();\n\n        ElementMatcher<MethodDescription> eMatcher = MethodMatcherConvert.INSTANCE.convert(m);\n        Method reflectMethod;\n        try {\n            reflectMethod = Foo.class.getDeclaredMethod(\"basicPublish\",\n                int.class, int.class, int.class, int.class);\n        } catch (Exception e) {\n            reflectMethod = null;\n        }\n        Assert.assertNotNull(reflectMethod);\n        MethodDescription method = new MethodDescription.ForLoadedMethod(reflectMethod);\n\n        Assert.assertTrue(eMatcher.matches(method));\n    }\n\n    @Test\n    public void testMatcher() {\n        // modifier | argsLength | returnType test\n        IMethodMatcher matcher = MethodMatcher.builder().named(\"basicPublish\")\n            .isPublic()\n            .argsLength(4)\n            .returnType(\"void\")\n            .qualifier(\"basicPublish\")\n            .build();\n\n        ElementMatcher<MethodDescription> eMatcher = MethodMatcherConvert.INSTANCE.convert(matcher);\n        Method reflectMethod;\n        try {\n            reflectMethod = Foo.class.getDeclaredMethod(\"basicPublish\",\n                int.class, int.class, int.class, int.class);\n        } catch (Exception e) {\n            reflectMethod = null;\n        }\n        Assert.assertNotNull(reflectMethod);\n        MethodDescription method = new MethodDescription.ForLoadedMethod(reflectMethod);\n        Assert.assertTrue(eMatcher.matches(method));\n\n        matcher = MethodMatcher.builder().named(\"basicConsume\")\n            .isPublic()\n            .argsLength(4)\n            .qualifier(\"basicConsume\")\n            .build();\n        eMatcher = MethodMatcherConvert.INSTANCE.convert(matcher);\n        Assert.assertFalse(eMatcher.matches(method));\n\n        // negate test\n        matcher = MethodMatcher.builder().named(\"basicPublish\")\n            .isPublic()\n            .and()\n            .returnType(\"void\")\n            .negate()\n            .build();\n        eMatcher = MethodMatcherConvert.INSTANCE.convert(matcher);\n        Assert.assertFalse(eMatcher.matches(method));\n        try {\n            reflectMethod = Foo.class.getDeclaredMethod(\"basicPublish\",\n                int.class, int.class, int.class);\n        } catch (Exception e) {\n            reflectMethod = null;\n        }\n        Assert.assertNotNull(reflectMethod);\n        method = new MethodDescription.ForLoadedMethod(reflectMethod);\n        Assert.assertTrue(eMatcher.matches(method));\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/megaease/easeagent/core/plugin/PluginLoaderTest.java",
    "content": "package com.megaease.easeagent.core.plugin;\n\nimport com.megaease.easeagent.config.Configs;\nimport com.megaease.easeagent.core.plugin.registry.PluginRegistry;\nimport com.megaease.easeagent.plugin.AgentPlugin;\nimport com.megaease.easeagent.plugin.CodeVersion;\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.InterceptorProvider;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport org.junit.Test;\n\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.Set;\nimport java.util.function.Supplier;\n\nimport static org.junit.Assert.assertFalse;\nimport static org.junit.Assert.assertTrue;\n\npublic class PluginLoaderTest {\n\n    @Test\n    public void isVersion() {\n        AgentPlugin plugin = new TestAgentPlugin();\n        PluginRegistry.register(plugin);\n        TestPoints points = new TestPoints();\n        assertTrue(PluginLoader.isCodeVersion(points, new Configs(Collections.emptyMap())));\n        Configs configs = new Configs(Collections.singletonMap(\"runtime.code.version.points.sprint-boot\", \"spring_boot_2_x\"));\n        //empty codeVersion always true\n        assertTrue(PluginLoader.isCodeVersion(points, configs));\n\n        points.versions = CodeVersion.builder().key(\"sprint-boot\").add(Points.DEFAULT_VERSION).add(\"spring_boot_2_x\").build();\n        assertTrue(PluginLoader.isCodeVersion(points, configs));\n\n        points.versions = CodeVersion.builder().key(\"sprint-boot\").add(\"spring_boot_2_x\").build();\n        assertTrue(PluginLoader.isCodeVersion(points, configs));\n\n        points.versions = CodeVersion.builder().key(\"sprint-boot\").add(\"spring_boot_3_x\").build();\n        assertFalse(PluginLoader.isCodeVersion(points, configs));\n\n        configs = new Configs(Collections.singletonMap(\"runtime.code.version.points.sprint-boot\", \"spring_boot_3_x\"));\n        points.versions = CodeVersion.builder().key(\"sprint-boot\").add(Points.DEFAULT_VERSION).add(\"spring_boot_2_x\").build();\n        assertFalse(PluginLoader.isCodeVersion(points, configs));\n\n        configs = new Configs(Collections.emptyMap());\n        assertTrue(PluginLoader.isCodeVersion(points, configs));\n\n    }\n\n    class TestAgentPlugin implements AgentPlugin {\n        @Override\n        public String getNamespace() {\n            return \"test_namespace\";\n        }\n\n        @Override\n        public String getDomain() {\n            return \"test_domain\";\n        }\n    }\n\n    class TestPoints implements Points {\n        CodeVersion versions;\n\n        @Override\n        public CodeVersion codeVersions() {\n            if (versions == null) {\n                return Points.super.codeVersions();\n            } else {\n                return versions;\n            }\n        }\n\n        @Override\n        public IClassMatcher getClassMatcher() {\n            return null;\n        }\n\n        @Override\n        public Set<IMethodMatcher> getMethodMatcher() {\n            return null;\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/megaease/easeagent/core/utils/AgentAttachmentRule.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.core.utils;\n\nimport net.bytebuddy.agent.ByteBuddyAgent;\nimport org.junit.rules.MethodRule;\nimport org.junit.runners.model.FrameworkMethod;\nimport org.junit.runners.model.Statement;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\nimport java.lang.instrument.Instrumentation;\nimport java.util.logging.Logger;\n\n/**\n * This rules assures that the running JVM is a JDK JVM with an available\n * <a href=\"https://blogs.oracle.com/CoreJavaTechTips/entry/the_attach_api\">attach API</a>.\n */\npublic class AgentAttachmentRule implements MethodRule {\n    private final boolean available;\n\n    public AgentAttachmentRule() {\n        available = ByteBuddyAgent.AttachmentProvider.DEFAULT.attempt().isAvailable();\n    }\n\n    public Statement apply(Statement base, FrameworkMethod method, Object target) {\n        Enforce enforce = method.getAnnotation(Enforce.class);\n        if (enforce != null) {\n            if (!available) {\n                return new NoOpStatement(\"The executing JVM does not support runtime attachment\");\n            }\n            Instrumentation instrumentation = ByteBuddyAgent.install(ByteBuddyAgent.AttachmentProvider.DEFAULT);\n            if (enforce.redefinesClasses() && !instrumentation.isRedefineClassesSupported()) {\n                return new NoOpStatement(\"The executing JVM does not support class redefinition\");\n            } else if (enforce.retransformsClasses() && !instrumentation.isRetransformClassesSupported()) {\n                return new NoOpStatement(\"The executing JVM does not support class retransformation\");\n            } else if (enforce.nativeMethodPrefix() && !instrumentation.isNativeMethodPrefixSupported()) {\n                return new NoOpStatement(\"The executing JVM does not support class native method prefixes\");\n            }\n        }\n        return base;\n    }\n\n    @Retention(RetentionPolicy.RUNTIME)\n    @Target(ElementType.METHOD)\n    public @interface Enforce {\n\n        boolean redefinesClasses() default false;\n\n        boolean retransformsClasses() default false;\n\n        boolean nativeMethodPrefix() default false;\n    }\n\n    private static class NoOpStatement extends Statement {\n\n        private final String reason;\n\n        private NoOpStatement(String reason) {\n            this.reason = reason;\n        }\n\n        public void evaluate() {\n            Logger.getLogger(\"net.bytebuddy\").info(\"Omitting test case: \" + reason);\n        }\n    }\n}\n"
  },
  {
    "path": "doc/add-plugin-demo.md",
    "content": "# Add Plugin Demo\n\nYou need Java 1.8+ and easeagent:\n\nIf you don't already have easeagent, get it and set $EASE_AGENT_PATH: [EaseAgent](../README.md#get-and-set-environment-variable)\n\n## 1. Building the application.\nThere is a [demo](https://github.com/megaease/easeagent-test-demo) which is spring web and client\n```\n$ git clone https://github.com/megaease/easeagent-test-demo.git\n$ cd spring-web\n$ mvn clean package\n```\n\n## 2. Building and copy the plugin.\nThere is a [simple plugin](https://github.com/megaease/easeagent-test-demo/tree/master/simple-plugin) which only for demonstrating the use of the plugin.\n \nThe compiled simple plugin JAR package should be copy to the `plugins` directory located in `EASE_AGENT_PATH`, and if the directory is not exsist, it need to be created.\n```\n$ cd simple-plugin\n$ mvn clean package\n$ cp target/simple-plugin-1.0.jar $EASE_AGENT_PATH/plugins/\n```\n\n## 3. Run the demo application with EaseAgent.\n```\n# Open another console\n$ export EASE_AGENT_PATH=[Replace with agent path]\n$ java \"-javaagent:${EASE_AGENT_PATH}/easeagent-dep.jar\" -Deaseagent.config.path=${EASE_AGENT_PATH}/agent.properties -Deaseagent.server.port=9900 -jar target/spring-web-1.0.jar\n\n```\n\nOpen another console, run curl to access the test url for several times.\n\n```\n$ for i in {1..1000}; do curl -v http://127.0.0.1:18888/web_client;sleep 0.1; done\n```\n\n## 4. How to verify it？\n\n### * Tracing\n  \nIf the tracing data is send to console, there would be some tracing log in console like this:\n```\n[{\"traceId\":\"5a8800b902703307\",\"parentId\":\"84c4cba42fb92788\",\"id\":\"fd00a1705c88cbb2\",\"kind\":\"SERVER\",\"name\":\"get\",\"timestamp\":1639493283759877,\"duration\":217545,\"shared\":true,\"localEndpoint\":{\"serviceName\":\"demo-service\",\"ipv4\":\"192.168.0.102\"},\"remoteEndpoint\":{\"ipv4\":\"127.0.0.1\",\"port\":55809},\"tags\":{\"http.method\":\"GET\",\"http.path\":\"/hello\",\"http.route\":\"/hello\",\"i\":\"ServerName.local\"},\"type\":\"log-tracing\",\"service\":\"demo-service\",\"system\":\"demo-system\"},{\"traceId\":\"5a8800b902703307\",\"id\":\"5a8800b902703307\",\"kind\":\"SERVER\",\"name\":\"get\",\"timestamp\":1639493283753466,\"duration\":228827,\"localEndpoint\":{\"serviceName\":\"demo-service\",\"ipv4\":\"192.168.0.102\"},\"remoteEndpoint\":{\"ipv4\":\"127.0.0.1\",\"port\":55851},\"tags\":{\"http.method\":\"GET\",\"http.path\":\"/web_client\",\"i\":\"ServerName.local\"},\"type\":\"log-tracing\",\"service\":\"demo-service\",\"system\":\"demo-system\"}]\n...\n```\n\n### * Metric\n\n#### Integrate with Prometheus\n\nAdding the following configuration in `prometheus.yml`\n```yaml\n  - job_name: 'spring-web-service'\n    static_configs:\n      - targets: ['localhost:9900']\n    metrics_path: \"/prometheus/metrics\"\n```\n\nStart Prometheus\n```bash\n$ ./prometheus --config.file=prometheus.yml\n```\n\nOpen Browser to visit [http://localhost:9090](http://localhost:9090).\n\nPrometheus Metric Schedule: [Prometheus Metric](./prometheus-metric-schedule.md)\n\nsearch `application_http_request_m1{url=\"GET /web_client\"}`. You will see as following.\n\n![image](./images/prometheus-demo.jpg)\n\n## Configuration\n\n* Modify service name, default configuration is `demo-service`.\n```\nname=[app-name]\n```\n* Modify kafka server config, default configuration is `127.0.0.1:9092`.\nBoth `tracing` data and `metric` data will be send to kafka server by default configuration.\n```\n#reporter.outputServer.bootstrapServer=[ip:port]\n```\n\n* Modify output configuration, if you want to watch log information in console.\n```\n# metric output\nplugin.observability.global.metric.appendType=console\n\n# tracings output to console\nreporter.tracing.sender.appendType=console\n```\n\n* Sending tracing data to zipkin server\n```\n# [zipkin]: send data to zipkin server\n# [system]: send data to kafka\nreporter.tracing.sender.appendType=http\nreporter.tracing.sender.url=http://localhost:9411/api/v2/spans\n```\n"
  },
  {
    "path": "doc/benchmark.md",
    "content": "# Benchmark\nWe use [spring-petclinic applications](https://github.com/spring-petclinic/spring-petclinic-microservices) as the benchmark application because it is relatively close to the real user scenario.\n\nWe use three AWS EC2 machines in a subnet for the benchmark test:\n\n- Gateway EC2: deploy `api-gateway`, `config-service`, and `discovery-server` on this node.\n- Service EC2: deploy `customers-service`, `vets-service`, and `visits-service` on this node.\n- Tester EC2: we initiate testing requests and collect testing metric data on this node.\n\nAll three AWS EC2 machines have the exact same configuration, and in a subnet:\n```\nEC2 Instance Type: t3-large, cn-northwest-1c\n\nCPU: 2 cores, Intel(R) Xeon(R) Platinum 8175M CPU @ 2.50GHz\nMEM: 7850092 kB\nSSD: XFS 100G\n\nOS: CentOS Linux release 8.5.2111\nDocker version 20.10.14, build a224086\ndocker-compose version 1.27.4, build 40524192\n```\n\nOn Gateway EC2 node:\n```\n# lunch up without EaseAgent\nenv COMPOSER=gateway-compose EASEAGENT=\"\" ./benchmark.sh start\n\n# lunch up with EaseAgent\nenv COMPOSER=gateway-compose EASEAGENT=\"true\" ./benchmark.sh start\n\n```\n\nOn Service EC2 node:\n```\n# lunch up without EaseAgent\nenv COMPOSER=service-compose EASEAGENT=\"\" ./benchmark.sh start\n\n# lunch up with EaseAgent\nenv COMPOSER=service-compose EASEAGENT=\"true\" ./benchmark.sh start\n\n```\n\nOn Tester EC2 node, we start Grafana/Prometheus:\n```\nenv COMPOSER=benchmark-tester-compose EASEAGENT=\"\" PROMETHEUS_CONFIG_FILE=prometheus_benchmark_2.yaml ./benchmark.sh start\n```\n\nWe run `k6` on `Tester` node to start stress test, and collect the metric data.\n```\nk6 run vets.js\n```\n\nEnvironment details of the test, reference to the document in [Easeagent-spring-petclinic](https://github.com/megaease/easeagent-spring-petclinic/blob/main/doc/benchmark.md).\n\n## Test Result\n\nWe access the URI: `/api/vet/vets` of the application, it will generate 10-Spans in total, and distributed in two serivces:\n- jmx-api-gateway: 1 span;\n- jmx-vets: 9spans.\n\nBy comparing the results of the two services, the impact of the number of SPANs can be observed.\n\nFor each value pair in the table, the former is the baseline value and the latter is the agent value (baseline / agent / difference value).\n\n### 320 virtual users\n\n| URI-Label               | CPU (baseline/agent/diff) | Heap Memory    | P90 Latency                 | Throughput (15s) |\n|-------------------------|:-----------------------|:------------------|:----------------------------|-----------------------|\n| jmx-api-gateway: 1spans | 33.2% / 44.4% / 21.2%  | 208M / 253M / 45M |                             |                       |\n| jmx-vets: 9spans        | 40.2% / 65.2% / 25.0%  | 236M / 284M / 48M | 28.94 ms / 189.39 ms / 553% | 1164 / 1057 / -9.2%  |\n\n#### CPU\n\n| Label     |  jmx-api-gateway  | jmx-vets              |\n|-----------|:------------------|:----------------------|\n| Baseline  | 33.2%             |  40.2%                |\n| Agent     | 44.4%             |  65.2%                |\n| Diff      | 21.2%             |  25.0%                |\n\n- Baseline:\n![Process CPU Load](./images/benchmark/baseline-320vus-process-cpu-load.png)\n\n- Agent:\n![Process CPU Load](./images/benchmark/agent-320vus-process-cpu-load.png)\n\n\n#### Heap\n- Baseline:\n![Heap](./images/benchmark/baseline-320vus-mem.png)\n- Agent:\n![Heap](./images/benchmark/agent-320vus-mem.png)\n\n\n#### Latency & Throuthput\n\n| Label     |  P90 Latency (ms) | Throughput (reqs/s)   |\n|-----------|:------------------|:----------------------|\n| Baseline  | 28.94             |  1164                 |\n| Agent     | 189.39            |  1057                 |\n\n- Baseline\n\n```\nrunning (11m40.4s), 000/320 VUs, 203885 complete and 0 interrupted iterations\ndefault ✓ [======================================] 000/320 VUs  11m40s\n\n     data_received..................: 637 MB 910 kB/s\n     data_sent......................: 78 MB  111 kB/s\n     http_req_blocked...............: avg=3.98µs   min=687ns  med=1.84µs  max=13.63ms  p(90)=3.44µs   p(95)=4.86µs\n     http_req_connecting............: avg=741ns    min=0s     med=0s      max=13.58ms  p(90)=0s       p(95)=0s\n\n     http_req_duration..............: avg=14.2ms   min=1.1ms  med=7.83ms  max=475.17ms p(90)=28.94ms  p(95)=45.38ms\n       { expected_response:true }...: avg=14.2ms   min=1.1ms  med=7.83ms  max=475.17ms p(90)=28.94ms  p(95)=45.38ms\n\n     http_req_failed................: 0.00%  ✓ 0           ✗ 815540\n     http_req_receiving.............: avg=285.63µs min=8.6µs  med=73.14µs max=89.64ms  p(90)=462.23µs p(95)=889.81µs\n     http_req_sending...............: avg=18.85µs  min=3.83µs med=8.29µs  max=54.16ms  p(90)=16.14µs  p(95)=22.15µs\n     http_req_tls_handshaking.......: avg=0s       min=0s     med=0s      max=0s       p(90)=0s       p(95)=0s\n     http_req_waiting...............: avg=13.9ms   min=1.04ms med=7.57ms  max=475.02ms p(90)=28.36ms  p(95)=44.63ms\n\n     http_reqs......................: 815540 1164.420944/s\n\n     iteration_duration.............: avg=1.02s    min=1s     med=1.01s   max=1.47s    p(90)=1.04s    p(95)=1.06s\n     iterations.....................: 203885 291.105236/s\n     vus............................: 5      min=5         max=320\n     vus_max........................: 320    min=320       max=320\n\n```\n- Agent\n```\nrunning (11m40.8s), 000/320 VUs, 185219 complete and 0 interrupted iterations\ndefault ↓ [======================================] 005/320 VUs  11m40s\n\n     data_received..................: 579 MB 827 kB/s\n     data_sent......................: 70 MB  100 kB/s\n     http_req_blocked...............: avg=4.45µs  min=700ns  med=1.9µs   max=71.05ms  p(90)=3.47µs   p(95)=4.51µs\n     http_req_connecting............: avg=1.39µs  min=0s     med=0s      max=70.99ms  p(90)=0s       p(95)=0s\n\n     http_req_duration..............: avg=75.71ms min=1.52ms med=38.65ms max=1.18s    p(90)=189.39ms p(95)=275.46ms\n       { expected_response:true }...: avg=75.71ms min=1.52ms med=38.65ms max=1.18s    p(90)=189.39ms p(95)=275.46ms\n\n     http_req_failed................: 0.00%  ✓ 0           ✗ 740876\n     http_req_receiving.............: avg=1.37ms  min=9.07µs med=54.95µs max=333.25ms p(90)=1.44ms   p(95)=6.48ms\n     http_req_sending...............: avg=49.77µs min=3.85µs med=8.36µs  max=228.08ms p(90)=16.97µs  p(95)=23.34µs\n     http_req_tls_handshaking.......: avg=0s      min=0s     med=0s      max=0s       p(90)=0s       p(95)=0s\n     http_req_waiting...............: avg=74.29ms min=1.47ms med=37.3ms  max=1.18s    p(90)=186.62ms p(95)=273.55ms\n\n     http_reqs......................: 740876 1057.248498/s\n\n     iteration_duration.............: avg=1.12s   min=1s     med=1.07s   max=2.18s    p(90)=1.3s     p(95)=1.39s\n     iterations.....................: 185219 264.312124/s\n     vus............................: 5      min=5         max=320\n     vus_max........................: 320    min=320       max=320\n\n```\n\n### 700 virtual users\n\n| URI-Label                | CPU (baseline/agent/diff) | Heap Memory    | P90 Latency               | Throughput (15s) |\n|----------------------|:--------------------------|:-------------------|:----------------------------------|-----------------------|\n| jmx-api-gateway: 1spans | 57.5% / 61.1% / 3.6%   | 385M / 583M / 200M |                                   |                       |\n| jmx-vets: 9spans        | 69.0% / 81.2% / 12.2%  | 303M / 464M / 151M | 564.69 ms / 737.55 ms / 30%       | 1800 / 1526 / -15.2%  |\n\n\n- Process CPU Load: 3.6% - more than 10%, requiring optimisation, the number of span has a significant impact on CPU performance.\n- Heap Memory: requiring optimisation.\n- Latency & Throughput: Easeagent has an excessive impact on latency and throughput and is positively correlated with the number of Spans.\n\nCPU, Process CPU Load, collected through JMX:java_lang_OperatingSystem_ProcessCpuLoad.\n\n#### CPU\n| Label     |  jmx-api-gateway  | jmx-vets              |\n|-----------|:------------------|:----------------------|\n| Baseline  | 57.5%             |  69.0%                |\n| Agent     | 61.1%             |  81.2%                |\n| Diff      | 3.60%             |  12.2%                |\n\n- Baseline:\n![Process CPU Load](./images/benchmark/baseline-process-cpu-load.png)\n\n- Agent:\n![Process CPU Load](./images/benchmark/agent-process-cpu-load.png)\n\n#### Heap\n\n- Baseline:\n![Heap](./images/benchmark/baseline-mem.png)\n- Agent:\n![Heap](./images/benchmark/agent-mem.png)\n\n#### Latency & Throuthput\n| Label     |  P90 Latency (ms) | Throughput (reqs/s)   |\n|-----------|:------------------|:----------------------|\n| Baseline  | 564.69            |  1800                 |\n| Agent     | 737.55            |  1526                 |\n\n- Baseline:\n```\nrunning (13m20.6s), 000/700 VUs, 360354 complete and 0 interrupted iterations\ndefault ↓ [======================================] 009/700 VUs  13m20s\n\n     data_received..................: 1.1 GB  1.4 MB/s\n     data_sent......................: 137 MB  171 kB/s\n     http_req_blocked...............: avg=6.6µs    min=704ns  med=1.88µs   max=269.76ms p(90)=3.35µs   p(95)=5.16µs\n     http_req_connecting............: avg=3.22µs   min=0s     med=0s       max=254.77ms p(90)=0s       p(95)=0s\n\n     http_req_duration..............: avg=262.62ms min=1.19ms med=219.42ms max=1.58s    p(90)=564.69ms p(95)=669.43ms\n\n       { expected_response:true }...: avg=262.62ms min=1.19ms med=219.42ms max=1.58s    p(90)=564.69ms p(95)=669.43ms\n     http_req_failed................: 0.00%   ✓ 0           ✗ 1441416\n     http_req_receiving.............: avg=7.48ms   min=8.94µs med=517.01µs max=480.89ms p(90)=17.64ms  p(95)=38.22ms\n     http_req_sending...............: avg=105.42µs min=3.72µs med=8.55µs   max=236.91ms p(90)=20.64µs  p(95)=37.64µs\n     http_req_tls_handshaking.......: avg=0s       min=0s     med=0s       max=0s       p(90)=0s       p(95)=0s\n     http_req_waiting...............: avg=255.03ms min=1.12ms med=208.8ms  max=1.58s    p(90)=554.48ms p(95)=661.33ms\n\n     http_reqs......................: 1441416 1800.434166/s\n\n     iteration_duration.............: avg=1.37s    min=1s     med=1.36s    max=2.69s    p(90)=1.74s    p(95)=1.86s\n     iterations.....................: 360354  450.108541/s\n     vus............................: 9       min=5         max=700\n     vus_max........................: 700     min=700       max=700\n\n```\n\n- Agent\n```\nrunning (13m20.9s), 000/700 VUs, 305670 complete and 0 interrupted iterations\ndefault ✓ [======================================] 000/700 VUs  13m20s\n\n     data_received..................: 956 MB  1.2 MB/s\n     data_sent......................: 116 MB  145 kB/s\n     http_req_blocked...............: avg=11.59µs  min=688ns  med=1.94µs   max=399.48ms p(90)=3.48µs   p(95)=4.62µs\n     http_req_connecting............: avg=6.21µs   min=0s     med=0s       max=217.13ms p(90)=0s       p(95)=0s\n\n     http_req_duration..............: avg=360.83ms min=1.45ms med=252.27ms max=7.99s    p(90)=737.55ms p(95)=1.04s\n\n       { expected_response:true }...: avg=360.83ms min=1.45ms med=252.27ms max=7.99s    p(90)=737.55ms p(95)=1.04s\n     http_req_failed................: 0.00%   ✓ 0           ✗ 1222680\n     http_req_receiving.............: avg=4.88ms   min=9.14µs med=51.6µs   max=887.76ms p(90)=10.7ms   p(95)=28.04ms\n     http_req_sending...............: avg=193.63µs min=3.72µs med=8.54µs   max=756.82ms p(90)=19.57µs  p(95)=44.94µs\n     http_req_tls_handshaking.......: avg=0s       min=0s     med=0s       max=0s       p(90)=0s       p(95)=0s\n     http_req_waiting...............: avg=355.75ms min=1.36ms med=248.41ms max=7.99s    p(90)=727.94ms p(95)=1.03s\n\n     http_reqs......................: 1222680 1526.574647/s\n\n     iteration_duration.............: avg=1.62s    min=1s     med=1.41s    max=8.99s    p(90)=2.36s    p(95)=2.76s\n     iterations.....................: 305670  381.643662/s\n     vus............................: 6       min=5         max=700\n     vus_max........................: 700     min=700       max=700\n```\n\n\n\n## Conclusion\n\n**Optimization is required in terms of CPU efficiency/memory usage and latency throughput.** \n\n"
  },
  {
    "path": "doc/context.md",
    "content": "#  Context\n\nBetween plugin and plugin, there is data transfer, such as jdbc uri, span and key, etc.\n\nIn order to solve the problem of data transmission, EaseAgent provides a session-level Context.\n\n### What is a session-level context?\nContext shared in the same session in the same process\n\nDefinition of \"same session\":\nIn the same thread, from a request to the end of the response, all belong to the same session.\nIn the case of cross-threading, from a request to the request thread resource operation, to the end of the entire response, all belong to the same session\n\n![image](./images/SessionContext.png)\n\n### Session-level Context features:\n\n1. The same session, data sharing between different plugins\n2. When the session has a cross-thread request, a \"plugin\" is required to ensure effective data transfer\n\n![image](./images/session-RunnableImplUML.png)\n\nThis Context is passed in the before and after method parameters of Interceptor.\n\n```\ninterface Interceptor{\n    void before(..., Context context);\n    void after(..., Context context)\n}\n```\n\nIf you are not in the Interceptor, but you want to get the SessionContext, the way to get it:\n```java\nclass DoIt{\n    public static void toGet(){\n        Context context = com.megaease.easeagent.plugin.bridge.EaseAgent.getContext();\n    }    \n}\n```\n\n## Context API\n\n[com.megaease.easeagent.plugin.api.Context](../plugin-api/src/main/java/com/megaease/easeagent/plugin/api/Context.java)\n\n```java\npackage com.megaease.easeagent.plugin.api;\n\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.context.AsyncContext;\nimport com.megaease.easeagent.plugin.api.trace.*;\n\n/**\n * A Context remains in the session it was bound to until business finish.\n */\npublic interface Context {\n    /**\n     * When true, do nothing and nothing is reported . However, this Context should\n     * still be injected into outgoing requests. Use this flag to avoid performing expensive\n     * computation.\n     */\n    boolean isNoop();\n\n    /**\n     * Returns the most recently created tracing component iff it hasn't been closed. null otherwise.\n     *\n     * <p>This object should not be cached.\n     */\n    Tracing currentTracing();\n\n    /**\n     * Returns the value to which the specified key is mapped,\n     * or {@code null} if this context contains no mapping for the key.\n     *\n     * <p>More formally, if this context contains a mapping from a key\n     * {@code k} to a value {@code v} such that {@code (key==null ? k==null :\n     * key.equals(k))}, then this method returns {@code v}; otherwise\n     * it returns {@code null}.  (There can be at most one such mapping.)\n     *\n     * <p>If this context permits null values, then a return value of\n     * {@code null} does not <i>necessarily</i> indicate that the context\n     * contains no mapping for the key; it's also possible that the context\n     * explicitly maps the key to {@code null}.\n     *\n     * @param key the key whose associated value is to be returned\n     * @return the value to which the specified key is mapped, or\n     * {@code null} if this context contains no mapping for the key\n     * @throws ClassCastException if the key is of an inappropriate type for\n     *                            this context\n     *                            (<a href=\"{@docRoot}/java/util/Collection.html#optional-restrictions\">optional</a>)\n     */\n    <V> V get(Object key);\n\n    /**\n     * Removes the mapping for a key from this Context if it is present\n     * (optional operation).   More formally, if this context contains a mapping\n     * from key <tt>k</tt> to value <tt>v</tt> such that\n     * <code>(key==null ?  k==null : key.equals(k))</code>, that mapping\n     * is removed.  (The context can contain at most one such mapping.)\n     *\n     * <p>Returns the value to which this context previously associated the key,\n     * or <tt>null</tt> if the context contained no mapping for the key.\n     *\n     * <p>If this context permits null values, then a return value of\n     * <tt>null</tt> does not <i>necessarily</i> indicate that the context\n     * contained no mapping for the key; it's also possible that the context\n     * explicitly mapped the key to <tt>null</tt>.\n     *\n     * <p>The context will not contain a mapping for the specified key once the\n     * call returns.\n     *\n     * @param key key whose mapping is to be removed from the Context\n     * @return the previous value associated with <tt>key</tt>, or\n     * <tt>null</tt> if there was no mapping for <tt>key</tt>.\n     * @throws ClassCastException if the key is of an inappropriate type for\n     *                            this context\n     *                            (<a href=\"{@docRoot}/java/util/Collection.html#optional-restrictions\">optional</a>)\n     */\n    <V> V remove(Object key);\n\n    /**\n     * Associates the specified value with the specified key in this context\n     * (optional operation).  If the context previously contained a mapping for\n     * the key, the old value is replaced by the specified value.  (A context\n     * <tt>m</tt> is said to contain a mapping for a key <tt>k</tt>\n     *\n     * @param key   key with which the specified value is to be associated\n     * @param value value to be associated with the specified key\n     * @return the previous value associated with <tt>key</tt>, or\n     * <tt>null</tt> if there was no mapping for <tt>key</tt>.\n     * (A <tt>null</tt> return can also indicate that the context\n     * previously associated <tt>null</tt> with <tt>key</tt>,\n     * if the implementation supports <tt>null</tt> values.)\n     * @throws ClassCastException if the class of the specified key or value\n     *                            prevents it from being stored in this context\n     */\n    <V> V put(Object key, V value);\n\n    /**\n     * Looks at the config at the current without removing it\n     * from the stack.\n     *\n     * @return The config at the top of this stack (the last config of the <tt>Config</tt> object).\n     * return {@link com.megaease.easeagent.plugin.bridge.NoOpIPluginConfig#INSTANCE} if this stack is empty.\n     */\n    Config getConfig();\n\n    /**\n     * Record and return the stacking sequence of Object{@code key}'s Session\n     * It needs to be used together with the {@link #exit(Object)} to be effective\n     * for example 1:\n     * <pre>{@code\n     *      fun1(){\n     *          try{\n     *              if (context.enter(obj)!=1){\n     *                 return;\n     *              }\n     *              //do something1\n     *          }finally{\n     *              if (context.exit(obj)!=1){\n     *                 return;\n     *              }\n     *              //do something2\n     *          }\n     *      }\n     *      fun2(){\n     *          try{\n     *              if (context.enter(obj)!=1){\n     *                 return;\n     *              }\n     *              // call fun1();\n     *              //do something3\n     *          }finally{\n     *              if (context.exit(obj)!=1){\n     *                 return;\n     *              }\n     *              //do something4\n     *          }\n     *      }\n     * }</pre>\n     * if call fun2(), something1 and something2 will no longer execute\n     * <p>\n     * for example 2:\n     *\n     * <pre>{@code\n     *      fun1(){\n     *          try{\n     *              if (context.enter(obj)>2){\n     *                 return;\n     *              }\n     *              //do something1\n     *          }finally{\n     *              if (context.exit(obj)>2){\n     *                 return;\n     *              }\n     *              //do something2\n     *          }\n     *      }\n     *      fun2(){\n     *          try{\n     *              if (context.enter(obj)>2){\n     *                 return;\n     *              }\n     *              // call fun1();\n     *              //do something3\n     *          }finally{\n     *              if (context.exit(obj)>2){\n     *                 return;\n     *              }\n     *              //do something4\n     *          }\n     *      }\n     *      fun3(){\n     *          try{\n     *              if (context.enter(obj)>2){\n     *                 return;\n     *              }\n     *              // call fun2();\n     *              //do something5\n     *          }finally{\n     *              if (context.exit(obj)>2){\n     *                 return;\n     *              }\n     *              //do something6\n     *          }\n     *      }\n     * }</pre>\n     * if call fun3(), something1 and something2 will no longer execute\n     *\n     * @param key the Object of stacking sequence\n     * @return stacking sequence\n     * @see #exit(Object)\n     */\n    int enter(Object key);\n\n    /**\n     * Record and verify the stacking sequence of Object{@code key}'s Session\n     * It needs to be used together with the {@link #exit(Object, int)} to be effective\n     *\n     * @param key   the Object of stacking sequence\n     * @param times the verify of stacking sequence\n     * @return true if stacking sequence is {@code times} else false\n     * @see #enter(Object)\n     */\n    default boolean enter(Object key, int times) {\n        return enter(key) == times;\n    }\n\n    /**\n     * Release and return the stacking sequence of Object{@code key}'s Session\n     * It needs to be used together with the {@link #enter(Object)} to be effective\n     *\n     * @param key the Object of stacking sequence\n     * @return stacking sequence\n     * @see #enter(Object)\n     */\n    int exit(Object key);\n\n    /**\n     * Release and verify the stacking sequence of Object's Session\n     * It needs to be used together with the {@link #enter(Object, int)} to be effective\n     *\n     * @param key   the Object of stacking sequence\n     * @param times the verify of stacking sequence\n     * @return true if stacking sequence is {@code times} else false\n     * @see #exit(Object)\n     */\n    default boolean exit(Object key, int times) {\n        return exit(key) == times;\n    }\n\n\n    //---------------------------------- 1. async context begin ------------------------------------------\n    // When you import and export the AsyncContext, you will also import and export the Tracing context for Thread.\n    /**\n     * Export a {@link AsyncContext} for asynchronous program processing\n     * It will copy all the key:value in the current Context\n     *\n     * @return {@link AsyncContext}\n     */\n    AsyncContext exportAsync();\n\n    /**\n     * Import a {@link AsyncContext} for async\n     * It will copy all the key: value to the current Context\n     * <p>\n     * If you don’t want to get the Context, you can use the {@link AsyncContext#importToCurrent()} proxy call\n     * <p>\n     * The Cleaner must be close after business:\n     * <p>\n     * example:\n     * <pre>{@code\n     *    void callback(Context context, AsyncContext ac){\n     *       try (Cleaner cleaner = context.importAsync(ac)) {\n     *          //do business\n     *       }\n     *    }\n     * }</pre>\n     *\n     * @param snapshot the AsyncContext from {@link #exportAsync()} called\n     * @return {@link Cleaner} for tracing\n     */\n    Cleaner importAsync(AsyncContext snapshot);\n\n\n    /**\n     * Wraps the input so that it executes with the same context as now.\n     */\n    Runnable wrap(Runnable task);\n\n    /**\n     * Check task is wrapped.\n     *\n     * @param task Runnable\n     * @return true if task is wrapped.\n     */\n    boolean isWrapped(Runnable task);\n    \n    //---------------------------------- 1. async end ------------------------------------------\n\n    /**\n     * @return true if the key is necessary for EaseAgent\n     */\n    boolean isNecessaryKeys(String key);\n\n    /**\n     * Inject Forwarded Headers key:value to Setter {@link Setter#setHeader(String, String)}.\n     *\n     * @param setter key:value to\n     * @see Request#setHeader(String, String)\n     */\n    void injectForwardedHeaders(Setter setter);\n\n    /**\n     * Import Forwarded Headers key:value to Context {@link Getter#header(String)}.\n     * <p>\n     * The Cleaner must be close after plugin:\n     *\n     * <pre>{@code\n     *    void after(...){\n     *       Cleaner c = context.remove(...)\n     *       try{\n     *\n     *       }finally{\n     *           c.close();\n     *       }\n     *    }\n     * }</pre>\n     *\n     * @param getter name from\n     * @return {@link Scope} for current session\n     * @see Request#header(String)\n     */\n    Cleaner importForwardedHeaders(Getter getter);\n}\n\n```\n\n## AsyncContext API\n[com.megaease.easeagent.plugin.api.context.AsyncContext](../plugin-api/src/main/java/com/megaease/easeagent/plugin/api/context/AsyncContext.java)\n\n```java\n/**\n * An asynchronous thread snapshot context\n * code example:\n * <pre>{@code\n *  AsyncContext asyncContext = context.exportAsync();\n *  class Run implements Runnable{\n *      void run(){\n *          try (Cleaner cleaner = asyncContext.importToCurrent()) {\n *               //do something\n *               //or EaseAgent.getContext().nextSpan();\n *          }\n *      }\n *  }\n *  }</pre>\n */\npublic interface AsyncContext {\n    /**\n     * When true, do nothing and nothing is reported . However, this AsyncContext should\n     * still be injected into outgoing requests. Use this flag to avoid performing expensive\n     * computation.\n     */\n    boolean isNoop();\n\n\n    /**\n     * get Span Context for Tracing\n     *\n     * @return SpanContext\n     */\n    SpanContext getSpanContext();\n\n    /**\n     * Import this AsyncContext to current {@link Context} and return a {@link com.megaease.easeagent.plugin.api.Cleaner}\n     * <p>\n     * The Cleaner must be close after business:\n     * <p>\n     * example:\n     * <pre>{@code\n     *    void callback(AsyncContext ac){\n     *       try (Cleaner cleaner = ac.importAsync()) {\n     *          //do business\n     *       }\n     *    }\n     * }</pre>\n     *\n     * @return {@link com.megaease.easeagent.plugin.api.Cleaner}\n     */\n    Cleaner importToCurrent();\n\n    /**\n     * @return all async snapshot context key:value\n     */\n    Map<Object, Object> getAll();\n\n    /**\n     * Returns the value to which the specified key is mapped,\n     * or {@code null} if this context contains no mapping for the key.\n     *\n     * <p>More formally, if this context contains a mapping from a key\n     * {@code k} to a value {@code v} such that {@code (key==null ? k==null :\n     * key.equals(k))}, then this method returns {@code v}; otherwise\n     * it returns {@code null}.  (There can be at most one such mapping.)\n     *\n     * <p>If this context permits null values, then a return value of\n     * {@code null} does not <i>necessarily</i> indicate that the context\n     * contains no mapping for the key; it's also possible that the context\n     * explicitly maps the key to {@code null}.\n     *\n     * @param key the key whose associated value is to be returned\n     * @return the value to which the specified key is mapped, or\n     * {@code null} if this context contains no mapping for the key\n     * @throws ClassCastException if the key is of an inappropriate type for\n     *                            this context\n     *                            (<a href=\"{@docRoot}/java/util/Collection.html#optional-restrictions\">optional</a>)\n     */\n    <T> T get(Object key);\n\n    /**\n     * Associates the specified value with the specified key in this context\n     * (optional operation).  If the context previously contained a mapping for\n     * the key, the old value is replaced by the specified value.  (A context\n     * <tt>m</tt> is said to contain a mapping for a key <tt>k</tt>\n     *\n     * @param key   key with which the specified value is to be associated\n     * @param value value to be associated with the specified key\n     * @return the previous value associated with <tt>key</tt>, or\n     * <tt>null</tt> if there was no mapping for <tt>key</tt>.\n     * (A <tt>null</tt> return can also indicate that the context\n     * previously associated <tt>null</tt> with <tt>key</tt>,\n     * if the implementation supports <tt>null</tt> values.)\n     * @throws ClassCastException if the class of the specified key or value\n     *                            prevents it from being stored in this context\n     */\n    <V> V put(Object key, V value);\n\n}\n```\n\n## Cleaner API\n\nAfter you import some content into the context, after running your business, you must clean up your content, otherwise it may affect the context content of the next session.\n\n[com.megaease.easeagent.plugin.api.Cleaner](../plugin-api/src/main/java/com/megaease/easeagent/plugin/api/Cleaner.java)\n\n```java\nimport java.io.Closeable;\n\n/**\n * A Cleaner for Context\n * It must be call after your business.\n * <p>\n * example 1:\n * <pre>{@code\n *    Cleaner cleaner = context.importAsync(snapshot);\n *    try{\n *       //do business\n *    }finally{\n *        cleaner.close();\n *    }\n * }</pre>\n * <p>\n * example 2:\n * <pre>{@code\n *    void before(...){\n *       Cleaner cleaner = context.importForwardedHeaders(getter);\n *    }\n *    void after(...){\n *      try{\n *         //do business\n *      }finally{\n *          cleaner.close();\n *      }\n *    }\n * }</pre>\n * <p>\n * example 3:\n * <pre>{@code\n *    void callback(AsyncContext ac){\n *       try (Cleaner cleaner = ac.importToCurrent()) {\n *          //do business\n *       }\n *    }\n * }</pre>\n */\npublic interface Cleaner extends Closeable {\n    /**\n     * No exceptions are thrown when unbinding a Context.\n     * It must be call after your business.\n     * <pre>{@code\n     *  try{\n     *      ......\n     *  }finally{\n     *      cleaner.close();\n     *  }\n     * }</pre>\n     */\n    @Override\n    void close();\n}\n```\n\n\n"
  },
  {
    "path": "doc/criteria-for-configuring-priorities.md",
    "content": "# Criteria For Configuring Priorities\n\nIn EaseAgent, there are the configuration of global variables and the configuration of specified items.\n\nWhen a configuration item has more than two different configurations, we need a simple priority standard to manage it.\n\n`This standard should be implemented in EaseAgent, whether it is user-configured or code-configured.`\n\nExample:\n\n```yaml\nplugin.observability.global.metric.topic=application-meter\nplugin.observability.access.metric.topic=application-log\n```\n\nWhen getting the topic sent by the access metric, what value is obtained?\n\n## Configuration Standard\n\n### Value is a boolean standard\n\nGet the logical AND operation of two values. If there is only one configuration, get its value directly.\n\nExample:\n\n```yaml\nplugin.observability.global.metric.enabled=true\nplugin.observability.access.metric.enabled=false\nplugin.observability.global.tracing.enabled=true\n```\n\n* get access metric enabled -> (true && false) -> false;\n* get httpServlet tracing enabled -> global.tracing.enabled = true\n\n### Value isn't a boolean standard\n\nPrefer configurations with small coverage.\n\nExample:\n\n```yaml\nplugin.observability.global.metric.topic=application-meter\nplugin.observability.httpServlet.metric.topic=application-log\n```\n\n* get httpServlet metric topic ->  application-log\n\n\n## FAQ\n\nWhy boolean config uses logical AND ?\n\n>  When value is boolean, its meaning is a switch. We want it to be like a light switch: there's a master switch, and then there are switches that control individual bulbs\n\nWhy we should prioritize configurations with less coverage?\n\n> When we configure a wide configuration, we mean configure a default value.\n\n> When we configure a small scope configuration, it means that we need a special value that is different from other configurations.\n\n> For this reason, we have chosen this criterion.\n\nWhich configuration is global and which configuration is specific?\n\n> The definition of global is distinguished by the location of the configuration in the architecture, which needs to be discussed and confirmed by the MegaEase team.\n"
  },
  {
    "path": "doc/development-guide.md",
    "content": "# Plugin Development Guide\n- [Plugin Development Guide](#plugin-development-guide)\n  - [Overview](#overview)\n    - [Architecture](#architecture)\n  - [Plugin Structure](#plugin-structure)\n    - [Points](#points)\n      - [Code Version](#code-version)\n    - [Interceptor](#interceptor)\n      - [Plugin Orchestration](#plugin-orchestration)\n      - [AdviceTo Annotation](#adviceto-annotation)\n    - [AgentPlugin: Plugin Configuration](#agentplugin-plugin-configuration)\n    - [A Simple Plugin Example](#a-simple-plugin-example)\n      - [Points of Simple Plugin](#points-of-simple-plugin)\n      - [Interceptor of Simple Plugin](#interceptor-of-simple-plugin)\n      - [AgentPlugin](#agentplugin)\n      - [Test](#test)\n  - [EaseAgent Tracing Plugin of Reality Sample](#easeagent-tracing-plugin-of-reality-sample)\n    - [Span and Trace](#span-and-trace)\n    - [Context Overview](#context-overview)\n    - [HttpServletPlugin](#httpservletplugin)\n  - [Context](#context)\n  - [Tracing API](#tracing-api)\n  - [Metric API](#metric-api)\n  - [Logging API](#logging-api)\n  - [Configuration API](#configuration-api)\n    - [Customize](#customize)\n  - [Plugin Unit Test](#plugin-unit-test)\n  - [EaseAgent Plugin Debug FAQ](#easeagent-plugin-debug-faq)\n    - [Development Environment Configuration](#development-environment-configuration)\n    - [Enhancement Debug](#enhancement-debug)\n    - [Interceptor Debug](#interceptor-debug)\n    - [Performance Verification](#performance-verification)\n- [References](#references)\n\n## Overview\n### Architecture\n\n![image](./images/EaseAgent-Architecture-v2.0.jpg)\n\nThe core pivot of a Javaagent is the ability to enhance specific methods to implement enhanced business, such as Tracing and Metric business. Therefore, EaseAgent needs an easy-to-understand and use, efficient, and reliable plugin framework that allows users to easily enhance specific methods to achieve business requirements.\n\nTo make the plugin framework easy to understand and use, we have abstracted the plugin into the `three elements`, **Points**, **Interceptor**, and **AgentPlugin**.\n- **Points** is used to define where to enhance.\n- **Interceptor** is used to define what to do at enhanced *Points*.\n- **AgentPlugin** makes the plugin configurable and configuration can be updated dynamically at runtime.\n\nEfficient and reliable, the architecture design needs to address two situations.\n- **Multiple plugins perform multiple enhancements to a method**. We let multiple plugin interceptors on the same method form an interceptors Chain. Then, let each method point only be enhanced by a simple piece of bytecode, and allocate a unique Id (unique Index) for the enhanced method, which is used as an array index to get the corresponding Interceptors Chains.\n- **Plugins can be independent or collaborative**.  In an Interceptors Chain, Interceptors can be scheduled by priorities and a mechanism for exchanging data between interceptors is provided.\n\nMoreover, Interceptors are enhanced to achieve business requirements, we provide a set of APIs for the most common Tracing and Metric services so that the enhancement plugin can complete the Tracing and Metric collection requirements quickly with the help of API. The Report component is responsible for formatting the data and uploading it to the backend server. It can also be customized and extended to meet the needs of different data formats and network architectures.\n\nThis document describes how to develop plugins for Easeagent, and it will be divided into the following sections to introduce plugin development.\n1. Plugin structure and examples, the plugin contains three core components, which are the **Points**, **Interceptor**, and **AgentPlugin definition**.\n2. Tracing API, which helps users complete the transaction tracing task.\n3. Metric API, helps users to complete metrics data collection.\n4. Logging API\n5. Configuration API\n6. Debug FAQ\n\n##  Plugin Structure\nAll plugin modules are located in the `plugins` folder under the top-level directory of the Easeagent project and a plugin module can contain several plugins, eg. a \"Tracking Plugin\" and a \"Metric Plugin\".\n![image](./images/httpServletPlugin-module.jpg)\n\nAs mentioned before, we abstract the plugin into the \"three elements\" corresponding to three interfaces, **Points**, **Interceptor** and **AgentPlugin**. The development of a plugin is an implementation of these three interfaces, which complete the definition of where to enhance, what to do at the enhancement point and the configuration of the plugin respectively.\n\n### Points\n`Points` implementation specifies methods that will be enhanced and if a dynamic private field with access methods for that field is added to instances of matched classes.\nWhen there is only one `MethodMatcher` in the return set of `getMethodMather()`, the qualifier value defaults to 'default', and there is no need to explicitly assign a value.\nWhen there are multiple methods in a matched class that require enhancement with different interceptors, a qualifier needs to be assigned to each `MethodMatcher` as the keyword used by different interceptors to bind.\n\nDecoupled from **ByteBuddy**, the EaseAgent \"ClassMatcher\" and \"MethodMatcher\" were designed by learning the ByteBuddy's DSL, instead of using the ByteBuddy interface directly\n\nThe DSL of `ClassMatcher` and `MethodMatcher` is described in [Matcher DSL](./matcher-DSL.md)\n```java\npublic interface Points {\n    CodeVersion EMPTY_VERSION = CodeVersion.builder().build();\n\n\n    /**\n     * eg.\n     * versions=CodeVersion.builder().key(\"jdk\").add(\"default\").add(\"jdk8\").build()\n     * do not set or set the following value to load: runtime.code.version.points.jdk=jdk8\n     * <p>\n     * when set for not load: runtime.code.version.points.jdk=jdk17\n     * but load from Points: versions=CodeVersion.builder().key(\"jdk\").add(\"jdk17\").build()\n     * @see com.megaease.easeagent.plugin.CodeVersion\n     * @return CodeVersion code of versions for control whether to load, If EMPTY_VERSIONS is returned, it means it will load forever\n     */\n    default CodeVersion codeVersions() {\n        return EMPTY_VERSION;\n    }\n    \n    /**\n     * return the defined class matcher matching a class or a group of classes\n     * eg.\n     * ClassMatcher.builder()\n     *      .hadInterface(A)\n     *      .isPublic()\n     *      .isAbstract()\n     *      .build()\n     *      .or()\n     *        .hasSuperClass(B)\n     *        .isPublic()\n     *        .build())\n     */\n    IClassMatcher getClassMatcher();\n\n    /**\n     * return the defined method matcher\n     * eg.\n     * MethodMatcher.builder().named(\"execute\")\n     *      .isPublic()\n     *      .argNum(2)\n     *      .arg(1, \"java.lang.String\")\n     *      .build().toSet()\n     * or\n     * MethodMatcher.multiBuilder()\n     *      .match(MethodMatcher.builder().named(\"<init>\")\n     *          .argsLength(3)\n     *          .arg(0, \"org.apache.kafka.clients.consumer.ConsumerConfig\")\n     *          .qualifier(\"constructor\")\n     *          .build())\n     *      .match(MethodMatcher.builder().named(\"poll\")\n     *          .argsLength(1)\n     *          .arg(0, \"java.time.Duration\")\n     *          .qualifier(\"poll\")\n     *          .build())\n     *      .build();\n     */\n    Set<IMethodMatcher> getMethodMatcher();\n\n    /**\n     * When returning true, the transformer will add an Object field and an accessor\n     * The dynamically added member can be accessed by AgentDynamicFieldAccessor:\n     *\n     * AgentDynamicFieldAccessor.setDynamicFieldValue(instance, value)\n     * value = AgentDynamicFieldAccessor.getDynamicFieldValue(instance)\n     */\n    default boolean isAddDynamicField() {\n        return false;\n    }\n\n    /**\n     * When a non-null string is returned, the converter will add an accessor to get the member variables inside the class.\n     * Get method: value = TypeFieldGetter.get(instance)\n     * @see com.megaease.easeagent.plugin.field.TypeFieldGetter#get(Object)\n     * @return String field name\n     */\n    default String getTypeFieldAccessor() {\n        return null;\n    }\n}\n```\n\n#### code-version\nThe same function may be implemented differently in different versions of the runtime environment, which may cause errors in the Interceptor runtime, such as reporting ClassNotFoundException.\n\nIn this case, different versions of Interceptor should use different `Points`.\n\nAt this time, just implement the codeVersions() method of `Points`, return different version numbers, and specify a specific version number in the configuration at runtime.\n\nThe configuration rules are as follows:\n\n* If `CodeVersion.builder().build()` is returned, it means it will load forever.\n* The configuration format is as follows: `runtime.code.version.points.{key}={version}`\n* There is a special string `default` in version, please do not occupy it.\n  * When the returned version has this `default`, it means that if there is no configuration for this key, Points will be used by default.\n* eg.\n  * CodeVersion.builder().key(\"jdk\").add(\"default\").build(), it means it will load by default. but not load by specified like `runtime.code.version.points.jdk=jdk10`\n  * CodeVersion.builder().key(\"jdk\").add(\"jdk10\").add(\"jdk11\").build()\n    * When multiple versions are specified, it means that it can be loaded by multiple versions:\n    * `runtime.code.version.points.jdk=jdk10` or `runtime.code.version.points.jdk=jdk11`\n\n### Interceptor\n`Interceptor` is the core of implementing specific enhancements.\n\n`Interceptor` interface has a name method `getType()` and a initialization method `init`.\n- The name will be used as `type` in combination with the `domain` and `namespace` of the binding plugin to get the plugin configuration which will be automatically injected into the `Context`. The description of plugin configuration will be given in [AgentPlugin](#agentplugin) session.\n- The `init` method is invoked during transform, allowing developers to initialize static resources of an interceptor, and also allowing them to load third party classes which can't load by runtime classloader.\n\nThe `before` and `after` methods of the interceptor are invoked when the method being enhanced enters and returns, respectively.\nBoth `before` and `after` methods have parameters `MethodInfo` and `Context`.\n- `MethodInfo` contains all method information, including class name, method name, parameters, return value and exception information.\n- `Context` contains the Interceptor configuration that is automatically injected and updated and other interfaces that support `tracing`, for details, please refer to the [Tracing API](#tracing-api) section.\n\n```java\npublic interface Interceptor extends Ordered {\n    /**\n     * @param methodInfo instrumented method info\n     * @param context    Interceptor can pass data, method `after` of interceptor can receive context data\n     */\n    void before(MethodInfo methodInfo, Context context);\n\n    /**\n     * @param methodInfo instrumented method info\n     * @param context    Interceptor can pass data, method `after` of interceptor can receive context data\n     */\n    default void after(MethodInfo methodInfo, Context context) {\n    };\n\n    /**\n     * Interceptor can get interceptor config thought Config API :\n     * EaseAgent.configFactory.getConfig\n     * Config API requires 3 params: domain, nameSpace, name\n     * domain and namespace are defined by the plugin, the third param, the name is defined here\n     *\n     * @return name, eg. tracing, metric, etc.\n     */\n    default String getType() {\n        return Order.TRACING.getName();\n    }\n\n    /**\n     * Initialization method for the interceptor,\n     * This method will be called and only be called once for every method which is injected by this interceptor,\n     * which means this method may be called several times, when there are several methods matched\n     *\n     * @param config interceptor configuration\n     * @param className injected method's class name\n     * @param methodName injected method name\n     * @param methodDescriptor injected method descriptor\n     */\n    default void init(Config config, String className, String methodName, String methodDescriptor) {\n    }\n}\n```\nThe `Interceptor` interface also includes the `Order` interface that defines the order of the interceptors.\n\n#### Plugin Orchestration\nWhen several Interceptors are injected into a single enhancement point, the order of execution of the Interceptors is determined by the Order of the Interceptor and the Order of the Plugin the interceptor belongs to.\n```\nEffective Order = Interceptor Order << 8 + Plugin Order\n```\n\nThe `before` method of the interceptor will be invoked in descending order of the `Effective Order`, that is, the smaller order will have higher priority.\nThe `after` method will be invoked in the opposite order as the before method was invoked.\n\n\n#### AdviceTo Annotation\nWithin a plugin, there may be multiple interceptors, and multiple enhancement points, so which enhancement point is a particular interceptor used for?\n\nThis can be specified through the `@AdviceTo` annotation, which is applied to the Interceptor's implementation to specify the enhancement point binding with the Interceptor.\n\n\n```java\n/**\n * use to annotate Interceptor implementation,\n * to link Interceptor to Points and AgentPlugin\n */\n@Retention(RetentionPolicy.RUNTIME)\n@Target(ElementType.TYPE)\n@Repeatable(AdvicesTo.class)\npublic @interface AdviceTo {\n    Class<? extends Points> value();\n    Class<? extends AgentPlugin> plugin() default AgentPlugin.class;\n    String qualifier() default \"default\";\n}\n```\nThe `@AdviceTo` annotation associates an `Interceptor` to the enhanced `Points` implementation specified by `value()`, and more specifically to the method matcher in that Points with the name specified by `qualifier()` which defaults to \"default\".\n```java\n@AdviceTo(value = DoFilterPoints.class, plugin = SimplePlugin.class)\npublic class ResponseHeaderInterceptor implements Interceptor {\n}\n```\nThe `@AdviceTo` annotation also binds the Interceptor to a specific plugin via `plugin()`, which gives the `Interceptor` the ability to fetch dynamically updated configurations of the plugin. The Interceptor's plugin configuration is accessible via the Context's `getConfig()` method, the details will be described in the following `AgentPlugin` section.\n\n### AgentPlugin: Plugin Configuration\nPlugin definition defines what `domain` and `namespace` of this plugin by implementing the `AgentPlugin` interface.\n\n```java\npublic interface AgentPlugin extends Ordered {\n    /**\n     * define the plugin name, avoiding conflicts with others\n     * it will be used as a namespace when getting the configuration.\n     */\n    String getNamespace();\n\n    /**\n     * define the plugin domain,\n     * it will be used to get configuration when loaded:\n     */\n    String getDomain();\n}\n\n```\nThe `domain` and `namespace` of a plugin determine the configuration prefix for each interceptor bound to the plugin by `@AdviceTo` annotation.\n\nThe format of the plugin configuration is defined as follows.\n```\nplugin.[domain].[namespace].[type].[key] = [value]\n```\nTake the tracing switch of `httpclient` as an example.\n```\nplugin.observability.httpclient.tracing.enabled=true\n\ndomain          : observability\nnamespace       : httpclient\ntype            : tracing\nkey             : enabled\nvalue           : true\n```\n\n`[domain]` and `[namespace]` are defined by `AgentPlugin` interface implementations.\n\nThe `type` is provided by the `Interceptor` interface implementation's `getType()` method, and this method needs to return a String value like 'tracing', 'metric', and 'redirect' which are already defined by Easeagent, or any other user-defined keyword.\n\nThis prefix `plugin.[domain].[namespace].[type]` is used to maintained configuration for this `Interceptor`, and in this `Interceptor` developer can get its configuration by the `getConfig()` method of the `Context` param.\n\nThe `AgentPlugin` interface also includes the `Order` interface that defines the order of the plugins, which is related to plugin orchestration. Plugin orchestration will be described in the [Plugin Orchestration](#plugin-orchestration) section.\n\n\n### A Simple Plugin Example\nOnly three interfaces need to be implemented to complete the development of a simple plugin. Although plugin development varies in complexity depending on the business, for illustrating the plugin mechanism, the simplest Simple plugin with only three interface implementations is the most appropriate.\n\nSuppose this plugin is to help complete the Tracing function, the name of the current microservice needs to be added to the Response header. So we can define the domain of the plugin as \"observability\", and then we will give the simple plugin a namespace \"simple\". The full source code is available [here](https://github.com/megaease/easeagent-test-demo/tree/master/simple-plugin).\n\n![image](./images/simple-plugin.jpg)\n\nThis plugin contain only three interface implementations: `AgentPlugin`, `Points` and `Interceptor`, corresponding to the classes `SimplePlugin`, `DoFilterPoints` and `ResponseHeaderInterceptor` respectively.\n\n```\n# Full code structure\n▾ src/main/java/com/megaease/easeagent/plugin/simple/\n  ▾ points/\n      DoFilterPoints.java\n  ▾ interceptor/\n      ResponseHeaderInterceptor.java\n    SimplePlugin.java\n  pom.xml\n```\n\n#### Points of Simple Plugin\n\n\n```java\npublic class DoFilterPoints implements Points {\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return ClassMatcher.builder()\n            .hasInterface(\"javax.servlet.Filter\")\n            .or()\n            .hasSuperClass(\"javax.servlet.http.HttpServlet\")\n            .build();\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.builder().named(\"doFilter\")\n                .isPublic()\n                .argsLength(3)\n                .arg(0, \"javax.servlet.ServletRequest\")\n                .arg(1, \"javax.servlet.ServletResponse\")\n                .returnType(\"void\")\n                .or()\n                .named(\"service\")\n                .arg(0, \"javax.servlet.ServletRequest\")\n                .arg(1, \"javax.servlet.ServletResponse\")\n                .build().toSet();\n    }\n}\n```\n\n#### Interceptor of Simple Plugin\nThis `ResponseHeaderInterceptor` is bound to the enhancement point defined above via the `@AdviceTo` annotation, and does not need to be explicitly assigned a qualifier value when qualifier is the default value.\n```java\n@AdviceTo(value = DoFilterPoints.class, plugin = SimplePlugin.class)\npublic class ResponseHeaderInterceptor implements Interceptor {\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n        HttpServletResponse httpServletResponse = (HttpServletResponse) methodInfo.getArgs()[1];\n        String serviceName = EaseAgent.getConfig(ConfigConst.SERVICE_NAME);\n        httpServletResponse.setHeader(\"easeagent-srv-name\", serviceName);\n    }\n    ......\n}\n```\n\n#### AgentPlugin\n```java\npublic class SimplePlugin implements AgentPlugin {\n    @Override\n    public String getNamespace() {\n        return \"simple\";\n    }\n\n    // ConfigConst.OBSERVABILITY;\n    @Override\n    public String getDomain() {\n        return \"observability\";\n    }\n}\n```\n\n#### Test\n1. Compile\nAs mentioned above, the source code is available [here](https://github.com/megaease/easeagent-test-demo/tree/master/simple-plugin).\n\n```\n$ git clone git@github.com:megaease/easeagent-test-demo.git\n$ cd easeagent-test-demo/simple-plugin\n$ mvn clean package\n$\n```\n\n2. Install Plugin\nThis simple plugin is compiled independently of easeagent, so the compiled output plugin jar package `simple-plugin-1.0.0.jar` need to be copied to the **plugins** directory which are at the same level directory as easeagent.jar (create if not existing), to allow easeagent to detect it.\n```\n$ export EASE_AGENT_PATH=[Replace with agent path]\n$ mkdir $EASE_AGENT_PATH/plugins\n$ cp target/simple-plugin-1.0.0.jar $EASE_AGENT_PATH/plugins\n\n```\n\n3. Run\nTaking the `spring-web` module under [ease-test-demo](https://github.com/megaease/easeagent-test-demo) as a test demo project, run the demo application with the EaseAgent.\n```\n$ export EASE_AGENT_PATH=[Replace with agent path]\n$ cd ../\n$ mvn clean package -Dmaven.test.skip\n$ java -javaagent:${EASE_AGENT_PATH}/easeagent-dep.jar -Deaseagent.config.path=agent.properties -jar spring-gateway/employee/target/employee-0.0.1.jar\n```\n4. Test\nExecute the following test and the header information added can be seen in the HTTP Response.\n```\neaseagent-srv-name: demo-springweb\n```\n\n```\n# \n# curl -v http://127.0.0.1:18081/employee/message\n*   Trying 127.0.0.1:18081...\n* Connected to 127.0.0.1 (127.0.0.1) port 18081 (#0)\n> GET /employee/message HTTP/1.1\n> Host: 127.0.0.1:18081\n> User-Agent: curl/7.77.0\n> Accept: */*\n>\n* Mark bundle as not supporting multiuse\n< HTTP/1.1 200\n< easeagent-srv-name: demo-springweb\n< easeagent-duration: 55\n< Content-Type: text/plain;charset=UTF-8\n< Content-Length: 34\n< Date: Thu, 17 Mar 2022 03:46:40 GMT\n<\n* Connection #0 to host 127.0.0.1 left intact\nGateway Called in employee Service%\n\n```\n\nIn addition to the `easeagent-srv-name` response header, we also see another response header `easeagent-duration` indicating the response time of the request, which is achieved by adding another enhancement point `ResponseProcessPoints` and `ResponseDurationInterceptor`. if interested, please visit the source code for details.\n```\nhttps://github.com/megaease/easeagent-test-demo/tree/master/simple-plugin\n```\n\nWhen the plugin is integrated into the `plugins` subdirectory in the easeagent project source tree, it will be compiled into the easeagent-dep.jar package.\n\nIn this simple plugin project, the `com.megaease.easeagent:plugin-api` dependency is wrapped in the local maven repository which locates in the `simple-plugin/lib` directory, user can also download the Easeagent source tree then install the `plugin-api` module.\n\n```\n$ git clone https://github.com/megaease/easeagent.git\n$ cd easeagent/plugin-api\n$ mvn clean install\n\n```\n## EaseAgent Tracing Plugin of Reality Sample\nWe have described the EaseAgent architecture, plugin concepts and design details above, and now we are familiar with three core elements of a plugin through the minimalist Simple plugin example. \n\nHowever, to develop a practical business plugin, it is necessary to have an understanding of the business API. Next, we outline how to use the Tracing API to complete a Tracing plugin development in combination with the actual Tracing plugin HttpServletPlugin.\n\n### Span and Trace\n![image](./images/trace-and-span.png)\nNote: Image From Jaeger., Retrieved March 08, 2022, from Architecture., https://www.jaegertracing.io/docs/1.31/architecture\n\nBefore we look at the concrete implementation, let's briefly introduce the core concepts in Tracing, Trace and Span.\n\nAs shown above, a Trace represents a complete transaction, containing multiple Spans, A-E; a Span represents an independent service sub-unit of a complete transaction, such as a database request, a method call or an external request; Trace is a logical concept only and is represented by the `traceId` in the Span, and there are multiple Spans with the same `traceId` forming a directed acyclic graph.\n\nA more concrete illustration of the Span concept and interface comes from a concrete sample of data in OpenZipkin format.\n![image](./images/zipkin-span.jpg)\nIn the above data, `id` is the unique id of the current `Span`; `traceId` represents the unique id of the `trace` of the current transaction request, often directly using the id of the first Span, like Span-A's id in the above figure; `parentId` represents the id of the parent Span, like B's `parentId` in the above figure is the `id` of Span-A; for more specific details can be found in Openzipkin's documentation.\n\nThe key point in the development of the Tracing plugin is the generation and reporting of Span. So what interface does EaseAgent use to provide the generation and reporting of Spans?\n\n\n### Context Overview\nAmong the `three elements` of a plugin, the `Interceptor` interface implementation is the core of a plugin development, where the `before/after` methods carry the `Context` parameter, and it is through the `Context` interface that developers can make Tracing API calls to complete the collection and reporting of Span.\n\n![image](./images/Context-span-api.png)\n\nAs shown above, the `nextSpan()` interface is used to create a Span, while the `servReceive()` and `clientRequest()` interfaces are wrappers of the `nextSpan()` interface, corresponding to the creation of a Span for a request received by the server and the creation of a Span for an external request, and initialize the fields within the Span.\n\nThe next step is to look at the specific HttpServlet Tracing plugin implementation.\n### HttpServletPlugin\nWe skip the corresponding implementations of the plugin `Points` and `AgentPlugin` and look directly at Tracing's `Interceptor` implementation `DoFilterTracingInterceptor`.\n```java\n@Override\npublic void doBefore(MethodInfo methodInfo, Context context) {\n  HttpServletRequest httpServletRequest = methodInfo.getArgs()[0];\n  ...\n  HttpRequest httpRequest = new HttpServerRequest(httpServletRequest);\n  requestContext = context.serverReceive(httpRequest);\n\n  httpServletRequest.setAttribute(PROGRESS_CONTEXT, requestContext);\n  HttpUtils.handleReceive(requestContext.span(), httpRequest);\n}\n```\n\nAs you can see from the above code snippet, the `context.serverReceive()` method is called in `before` to create and initialize the Span, and the `HttpUtils::handleReceive()` method is used to make HTTP request-related injections to the fields in the Span.\n\n```java\n@Override\npublic void doAfter(MethodInfo methodInfo, Context context) {\n  ...\n  try {\n    Span span = requestContext.span();\n    if (!httpServletRequest.isAsyncStarted()) {\n      ...\n      HttpUtils.finish(span, response);\n    } else if (methodInfo.getThrowable() != null) {\n      span.error(methodInfo.getThrowable());\n      span.finish();\n    } else {\n      // async\n      ...\n    }\n  } finally {\n    requestContext.scope().close();\n  }\n}\n```\n\nIn the `after` method, the `HttpUtils::finish()` or `span.finish()` method is called to complete the Span collection and reporting. This is an overview of the Trace API interface calls in the actual Tracing plugin, for more specific details you can check the source code of this file in EaseAgent:\n```\nhttps://github.com/megaease/easeagent/blob/master/plugins/httpservlet/src/main/java/com/megaease/easeagent/plugin/httpservlet/interceptor/DoFilterTraceInterceptor.java\n```\n\nThe [Context](#context) section provides a comprehensive description of the `Context`.\n\n##  Context\n* [Context](context.md)\n\n##  Tracing API\n* [Tracing API](tracing-api.md)\n\n##  Metric API\n* [Metric API](metric-api.md)\n\n##  Logging API\nIf you need to print logs in the plugin to the EaseAgent log output, you can use the Slf4j interface directly.\n\n## Configuration API\n\nRegarding configuration, we have a set of rules to follow. For detailed rules, please see: [Plugin Configuration](#plugin-configuration)\n\nWhen you want to get your own configuration file in the plugin, you only need to get it from the Context.\nThe framework itself will automatically maintain configuration changes and modifications.\n\n```java\nclass InterceptorImpl  implements Interceptor {\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n        Config config = context.getConfig();\n        // You don’t need to verify enabled here, because enabled is a reserved attribute. If it is false, the Interceptor will not be run.\n        // boolean enabled = config.enabled();\n        Integer outputSize = config.getInt(\"output.size\"); //it will be get config key: plugin.[domain].[namespace].[function].output.size = [value]\n    }\n\n    @Override\n    public void after(MethodInfo methodInfo, Context context) {\n        Config config = context.getConfig();\n    }\n}\n```\n\nWhen you want to get the configuration outside of the plugin, we provide tools to get it.\n\nThis tool will automatically maintain configuration updates and modifications, and the configuration obtained each time will be the latest configuration.\n\nIt is a singleton registration factory, which also means that the singleton acquisition is locked, so it is hoped that the user can acquire it as little as possible.\n\nfor example: acquire it once during initialization, and then put it in a static variable.\n\nThe registered key is `domain`, `namespace`, `id`.\n\n```java\nimport com.megaease.easeagent.plugin.api.config.AutoRefreshPluginConfigRegistry;\nimport com.megaease.easeagent.plugin.api.config.AutoRefreshPluginConfigImpl;\nclass Demo{\n  AutoRefreshConfigImpl config = AutoRefreshRegistry.getOrCreate(\"observability\", \"httpclient\", \"metric\");\n}\n```\n\n### Customize\n\nWhen you need to customize Config, implement the [com.megaease.easeagent.plugin.api.config.AutoRefreshPluginConfig](../plugin-api/src/main/java/com/megaease/easeagent/plugin/api/config/AutoRefreshConfig.java) interface, and then register\n\nThe registered key is `domain`, `namespace`, `id` and the `type` of Class.\n\n```java\npublic class ServiceNameConfig implements AutoRefreshConfig {\n    private volatile String propagateHead = \"X-Mesh-RPC-Service\";\n\n    public String getPropagateHead() {\n        return propagateHead;\n    }\n\n    @Override\n    public void onChange(Config oldConfig, Config newConfig) {\n        String propagateHead = newConfig.getString(\"propagate.head\");\n        if (StringUtils.isEmpty(propagateHead) || StringUtils.isEmpty(propagateHead.trim())) {\n            return;\n        }\n        this.propagateHead = propagateHead.trim();\n    }\n}\n\npublic class ServiceNameInterceptor implements Interceptor {\n    protected static ServiceNameConfig config = null;\n\n    @Override\n    public void init(Config pConfig, String className, String methodName, String methodDescriptor) {\n        config = AutoRefreshRegistry.getOrCreate(pConfig.domain(), pConfig.namespace(), pConfig.id(),\n            new AutoRefreshConfigSupplier<ServiceNameConfig>() {\n                @Override\n                public ServiceNameConfig newInstance() {\n                    return new ServiceNameConfig();\n                }\n            });\n    }\n}\n```\n\n## Plugin Unit Test\n* [Plugin Unit Test](plugin-unit-test.md)\n\n## EaseAgent Plugin Debug FAQ\nThe above covers all aspects of plugin development, the following are common debugging issues during plugin development.\n\n### Development Environment Configuration\nDebugging environment configuration, briefly described below:\n- Download the EaseAgent source code to the localhost and add it to the workspace of the IDE, build and the output located in the source code directory build/target/easeagent-dep.jar, and then create the directory `build/target/plugins`；\n- Add the plugin project (e.g. simple-plugin) to the same workspace and copy the compiled and packaged JAR file to the `build/target/plugins` directory created in the previous step so that the plugin can be loaded.\n- Add the source code of the application (e.g. spring-gateway/employee) to the workspace and configure the JVM options in the Debug menu to start the application with easeagent-dep.jar for debugging later.\n  eg.:\n  ```\n  -javaagent:/path-to-easeagent/build/target/easeagent-dep.jar -Deaseagent.config.path=/my-own-if-changed-or-add/agent.properties -Deaseagent.log.conf=/my-own-if-changed/easeagent-log4j2.xml -Dnet.bytebuddy.dump=/path-to-dump/\n  ```\n  The `path` above need to be replaced with the user's actual environment path.\n- Set breakpoints, launch debug session.\n### Enhancement Debug\n- How can I determine whether target classes and methods are enhanced?\n  The following debug options were set in step 3 of the previous section of environment configuration:\n  ```\n  -Dnet.bytebuddy.dump=/path-to-dump/\n  ```\n  The class files of all the enhanced classes will be printed in this directory. Decompile the class files (IDEA can pull them in and open them directly) to see if the corresponding method has the enhanced bytecode to call the EaseAgent method.\n\n- If the check confirms that the target method is not enhanced, how do I debug it?\n![image](./images/enhancement-debug.png)\n  There are three key checkpoints: ClassMatchers, MethodMatchers and all other issues.\n  1. All classes that are matched will run into the ForAdviceTransformer::transform(...) method, where conditional breakpoints can be added, then checking ClassMatchers if the breakpoint is not interrupted.\n  2. All methods matched will run into the AdviceRegistry::check(...) method, where conditional breakpoints can be added, then checking MethodMatchers if the breakpoint is not interrupted.\n  3. Set a breakpoint by going back through the breakpoint stack in step 1 and navigating to the **ByteBuddy** source code where throw exception when enhance fail, check the cause of the exception.\n\n  All enhancement failures can be resolved by analyzing the three breakpoints above.\n### Interceptor Debug\n- Why the Interceptor is not called when the class method has been enhanced?\n  All enhanced methods run into the following two methods.\n  ```java\n  com.megaease.easeagent.core.plugin.Dispatcher::enter\n  com.megaease.easeagent.core.plugin.Dispatcher::exit\n  ```\n  Breakpoints can be set at the entrances and exits to further trace the execution logic.\n  Commonly, and most likely, the plugin's corresponding configuration has enabled=false or is not configured.\n  ```\n  plugin.[domain].[namespace].[type].enabled=true\n  ```\n### Performance Verification\nTo verify the impact of EageAgent on performance/latency, the Profiler tool can be used to determine the performance/latency impact by sampling the CPU and focusing on the percentage of `Megaease` related function executions within the stack.\n\n\nThere are profiler tools such as Async-profiler, Arthas and VisualVm.\n\nFinally, have fun using and extending EaseAgent, and feel free to raise Issues or join the Slack community to discuss.\n\n* [Github Issues](https://github.com/megaease/easeagent/issues)\n* [Join Slack Workspace](https://join.slack.com/t/openmegaease/shared_invite/zt-upo7v306-lYPHvVwKnvwlqR0Zl2vveA) for function requirement, issues, and discussion.\n* [MegaEase on Twitter](https://twitter.com/megaease)\n\n# References\n1. Chen, H., Zhao, K., & Zhou, J. (2022). EaseAgent 2.0 Architecture Design and Code Implementation. megaease.com. Retrieved March 9, 2022, from https://www.youtube.com/watch?v=u6Aoa2roGuA \n2. Data model. Data Model · OpenZipkin. (n.d.). Retrieved March 08, 2022, from https://zipkin.io/pages/data_model.html \n3. Architecture. Jaeger. (n.d.). Retrieved March 08, 2022, from https://www.jaegertracing.io/docs/1.31/architecture \n4. Oaks, S. (2020). Java performance 2nd Edition. O'Reilly.\n\n\n"
  },
  {
    "path": "doc/how-to-use/megacloud-config.md",
    "content": "### First: Download and install the agent\n[see our doc](https://github.com/megaease/easeagent#get-and-set-environment-variable)\n\n### Second: Configuration\nModify the agent.properties file to configure your information.\n\n##### 1. name\n\nYou'll name to find your data later. It's important to use a unique and meaningful name.\n\nThe service name of megacloud consists of three parts: zone, domain, name. They are joined by `.` into `ServiceName`\n\n```properties\nname=zone.domain.service\n```\n\n##### 2. MTLS\n\nMTLS is a secure authentication protocol for EaseAgent to connect to MegaCloud.\n\nConfig: Get TLS\n```properties\nreporter.outputServer.tls.enable=true\nreporter.outputServer.tls.key=YOUR_TLS_KEY\nreporter.outputServer.tls.cert=YOUR_TLS_CERT\n```\n\n##### 3. reporter\n\nMegaCloud uses http to receive data, so you need to change the configuration to http and MegaCloud's address.\n```properties\nreporter.outputServer.bootstrapServer={MEGA_CLOUD_URL}\nreporter.outputServer.appendType=http\nreporter.tracing.sender.appendType=http\nreporter.metric.sender.appendType=http\nplugin.observability.global.metric.url=/platform-metrics\nplugin.observability.global.log.url=/application-log\nplugin.observability.access.log.url=/application-access-log\nreporter.tracing.sender.url=/application-tracing-log\nreporter.tracing.encoder=SpanJsonEncoder\n```\n\n##### 4. other\nOther configurations are EaseAgent related configurations such as Tracing, Metric, etc. For details, please refer to [github doc](https://github.com/megaease/easeagent/blob/master/doc/user-manual.md#configuration)\n\n### Third: About `MEGA_CLOUD_URL` And `TLS`\n\nWhen you download the `easeagent.jar` file through our megacloud, `MEGA_CLOUD_URL` and `TLS` will be filled in for you automatically.\n\nIf you need it separately, please download the `easeagent.jar` and get it by yourself.\n\nYou can use the command to get config:\n```bash\njar xf easeagent.jar agent.properties \n```\n"
  },
  {
    "path": "doc/how-to-use/use-in-docker.md",
    "content": "# Get the agent\n\nDownload the agent, [see our doc](https://github.com/megaease/easeagent#get-and-set-environment-variable)\n\n# Modify startup scripts\n\nThe startup parameters of the Java application server must include the built-in argument -javaagent.\n\nIt is recommended that you set this argument with the JAVA_OPTS environment variable.\n\nThe value of that argument must contain the location where you ADD the easeagent.jar file to the image.\n\nFor example, with Tomcat, use commands like these in the Dockerfile:\n\n```dockerfile\nRUN mkdir -p /usr/local/tomcat/easeagent\nADD ./easeagent/easeagent.jar /usr/local/tomcat/easeagent/easeagent.jar\nENV JAVA_OPTS=\"$JAVA_OPTS -javaagent:/usr/local/tomcat/easeagent/easeagent.jar\"\n```\n\n# Restart your application\n\nDeploy your application to start using the EaseAgent to send data to MegaCloud.\n"
  },
  {
    "path": "doc/how-to-use/use-on-host.md",
    "content": "# Install the agent\n\nDownload the agent, [see our doc](https://github.com/megaease/easeagent#get-and-set-environment-variable)\n\nOnce you’ve get the EaseAgent, these steps to start the Java agent installation.\n\n1. Create a directory for your EaseAgent, such as /opt/easeagent. On Windows, the EaseAgent must be in a subdirectory of your application server’s directory, such as C:\\Tomcat 1.0\\easeagent.\n\n2. Copy the easeagent.jar file into your new directory. \n\n\n# Specific instructions for your Java setup\n\nTo use the EaseAgent with your JVM, you’ll need to pass the -javaagent argument. In the commands below, replace EASE_AGENT_PATH with the path of the EaseAgent directory you created, eg. /opt/easeagent.\n\n## Spring Boot\nPass the -javaagent argument to the command line where you start your app. Make sure to add it before the -jar argument:\n```\njava -javaagent:$EASE_AGENT_PATH/easeagent.jar -jar app.jar\n```\n\n## Tomcat\n\nPass the -javaagent argument on catalina.sh, catalina.bat or the GUI.\n\n1. Linux with catalina.sh\n\n    In tomcat catalina.sh file, use the JAVA_OPTS environment variable:\n    ```\n    export CATALINA_OPTS=\"$CATALINA_OPTS -javaagent:EASE_AGENT_PATH/easeagent.jar\"\n    ```\n\n2. Windows with catalina.bat\n\n    In tomcat catalina.bat file, set the JAVA_OPTS variable near the top of the file:\n    ```\n    set \"CATALINA_OPTS=%CATALINA_OPTS% -javaagent:EASE_AGENT_PATH\\easeagent.jar\"\n    ```\n\n3. Windows with GUI\n\n    On Windows with GUI, add the full EaseAgent path to your Java options: In Apache Tomcat, click Configure Tomcat, and then click Java.\n    \n    In the Java Options text box, enter the following argument:\n    \n    ```\n    -javaagent:EASE_AGENT_PATH\\easeagent.jar\n    ```\n\n## Jetty\n\nPass the -javaagent in jetty.sh or the start.ini file:\n\n1. jetty.sh\n    ```\n    export JAVA_OPTIONS=\"${JAVA_OPTIONS} -javaagent:EASE_AGENT_PATH/easeagent.jar\"\n    ```\n2. start.ini\n    ```\n    -javaagent:EASE_AGENT_PATH/easeagent.jar\n    ```\n\n\n## Wildfly\n\nIf you’re using Wildfly 11+, to pass the -javaagent argument on Linux or Windows.\n\nAdd the Java agent and make it visible to the JBoss modules by adding the following lines to your file.\n\n1. Linux with standalone.conf:\n\n    ```\n    JAVA_OPTS=\"$JAVA_OPTS -javaagent:EASE_AGENT_PATH/easeagent.jar\"\n    JAVA_OPTS=\"$JAVA_OPTS -Djboss.modules.system.pkgs=$JBOSS_MODULES_SYSTEM_PKGS,com.easeagent\"\n    ```\n\n2. Windows with standalone.bat.conf:\n\n    ```\n    set \"JAVA_OPTS=%JAVA_OPTS% -javaagent:EASE_AGENT_PATH\\easeagent.jar\"\n    set \"JAVA_OPTS=%JAVA_OPTS% -Djboss.modules.system.pkgs=%JBOSS_MODULES_SYSTEM_PKGS%,com.easeagent\"\n    ```\n\n# Restart your application\n\nDeploy your application to start using the EaseAgent to send data to MegaCloud.\n"
  },
  {
    "path": "doc/matcher-DSL.md",
    "content": "# Matcher DSL\n- [Overview](#Overview)\n- [ClassMatcher](#ClassMatcher)\n- [MethodMatcher](#MethodMatcher)\n\n## Overview\nEaseAgent provides a Matcher DSL (Domain Specific Language) to define a `Points` implementation which is used to define classes and their methods to be enhanced. \n\nThe design of this DSL is borrowed from ByteBuddy's class and method matching DSL design. Why not use ByteBuddy's DSL interface directly?\n- Decouple from the ByteBuddy. Although ByteBuddy is an excellent byte code framework, we provide dedicated DSL to developers via abstracting it, can easily migrate to a more excellent framework in the future.\n- Provide a cleaner and easier to use API for Agent scenarios. We have defined two matchers, there are `ClassMatcher` and `MethodMatcher`.\n  - The `ClassMatcher` is dedicated to identify classes in which methods need to be enhanced are located.\n  - The `MethodMatcher` is dedicated to select methods located in the matched classes that need to be enhanced.\n\n\n## ClassMatcher\n\n### Definition\n\nAs defined by the JVM Specification for the class structure and the status of the annotations commonly used in Java, the optional elements used for class matching contain:\n\n- Class name.\n- Implementing Interface name.\n- Super class name.\n- Modifier, access flags, public/private/abstract/final/synthetic, and so on.\n- Annotation.\n\n### Implementation\nClassMatcher definition is provided by `com.megaease.easeagent.plugin.matcher.ClassMatcher`.\nEach condition in a matcher is a combination of `logical AND`.\n```java\n    private String name;\n    // setup the match type of name: className, super className, Interface Name or Annotation name.\n    private ClassMatch matchType;\n\n    private int modifier = Modifier.ACC_NONE;\n    private int notModifier = Modifier.ACC_NONE;\n```\n\nThere is a `MethodMatcherBuilder` class in `com.megaease.easeagent.plugin.matcher.MethodMatcher`, providing interface to build a `ClassMatcher`.\nThe following methods are common condition settings in class matching, \n```java\n    ClassMatcherBuilder hasSuperClass(String className)\n    ClassMatcherBuilder hasClassName(String className)\n    ClassMatcherBuilder hasAnnotation(String className)\n    ClassMatcherBuilder hasInterface(String className)\n\n    ClassMatcherBuilder isPublic();\n    ClassMatcherBuilder isPrivate();\n    ClassMatcherBuilder isAbstract();\n    ClassMatcherBuilder isInterface();\n\n    ClassMatcherBuilder notPrivate();\n    ClassMatcherBuilder notAbstract();\n    ClassMatcherBuilder notInterface();\n```\n\n### Logical Operation\n- Logical AND  \n  As mention above, each condition in a matcher is a combination of `logical AND`.\n\n  For example, the following ClassMatcher will match non-interface classes which inherit the `java.lang.String` **and** implements the `java.lang.Comparable` interface.\n```java\n    ClassMatcher.builder()\n        .hasSuperClass(\"java.lang.String\")\n        .hasInterface(\"java.lang.Comparable\")\n        .notInterface()\n        .build()\n\n```\nIn addition, `ClassMatcherBuilder` provides `and()` method which will finish current builder and start a new `ClassMatcherBuilder`.\nThe previous builder will generate a `Left ClassMatcher` and the new builder will generate a 'Right ClassMatcher'.\nThe `Left matcher` and `Right matcher` will be combined into one `AndClassMatcher`, and a method will match as long as the `Left` or `Right` matcher is a match.  \n\nFor example, the following matcher will match classes that inherit the `easeagent.test.TestBaseClass` class **and** exclude classes implementing \"easeagent.test.InterfaceB\" interface.\n\n```java\n    matcher = ClassMatcher.builder()\n        .hasSuperClass(\"easeagent.test.TestBaseClass\")\n        .and()\n        .hasInterface(\"easeagent.test.InterfaceB\")\n        .negate()\n        .build();\n```\n\n- Logical OR  \n  `ClassMatcherBuilder` provides `or()` method which will finish current builder and start a new `ClassMatcherBuilder`.\n  The previous builder will generate a `Left ClassMatcher` and the new builder will generate a 'Right ClassMatcher'.\n  The `Left matcher` and `Right matcher` will be combined into one `OrClassMatcher`, and a class will match as long as the `Left` or `Right` matcher is a match.\n\n  For example, the following ClassMatcher will match classes that implement the `javax.servlet.Filter` interface **or** inherit the \"javax.servlet.http.HttpServlet\" class.\n\n```java\n    public IClassMatcher getClassMatcher() {\n        return ClassMatcher.builder()\n            .hasInterface(\"javax.servlet.Filter\")\n            .or()\n            .hasSuperClass(\"javax.servlet.http.HttpServlet\")\n            .build();\n    }\n```\n\n- Logical NOT  \nThe `negate()` method of `ClassMatcherBuilder` uses `NegateClassMatcher` to wrap the original matcher generated by the builder when no such method is applied.\nWhen a class matches the original matcher, it is unmatched by the wrap matcher; conversely, it is matched.  \nFor example, the following ClassMatcher will match classes that inherit the `easeagent.test.TestBaseClass` class, but **exclude** classes implementing \"easeagent.test.InterfaceB\" class.\n```java\n    matcher = ClassMatcher.builder()\n        .hasSuperClass(\"easeagent.test.TestBaseClass\")\n        .and()\n        .hasInterface(\"easeagent.test.InterfaceB\")\n        .negate()\n        .build();\n```\n\n\n## MethodMatcher\n\n### Definition\nAs defined by the JVM Specification for the method structure , the optional elements used for method matching contain:\n\n- Method name.\n- Modifier, public/private, and so on.\n- Argument type, the full qualified class name of argument.\n- ReturnType, the return type of method.\n\n### Implementation\n\nMethodMatcher definition is provided by `com.megaease.easeagent.plugin.matcher.MethodMatcher`.\nEach condition in a matcher is a combination of `logical AND`.\n```java\n    // method name\n    private String name;\n\n    // the match type of method name: equals, startWith...\n    private StringMatch nameMatchType;\n\n    // ignored when with default value\n    private String returnType = null;\n    // types of method arguments\n    private String[] args;\n    private int argsLength = -1;\n    private int modifier = Modifier.ACC_NONE;\n    private int notModifier = Modifier.ACC_NONE;\n\n    // only match methods overridden form the ClassMatcher\n    private IClassMatcher overriddenFrom = null;\n\n    // assign a name for this method matcher\n    private String qualifier;\n```\n\nThere is a `MethodMatcherBuilder` class in `com.megaease.easeagent.plugin.matcher.MethodMatcher`, providing interface to build a `MethodMatcher`.\nThe following methods are common condition settings in method matching, \n```java\n    // method name condition\n    MethodMatcherBuilder named(String methodName);\n    MethodMatcherBuilder isConstruct();\n    MethodMatcherBuilder nameStartWith(String methodName);\n    MethodMatcherBuilder nameEndWith(String methodName);\n    MethodMatcherBuilder nameContains(String methodName);\n\n    // modifier condition\n    MethodMatcherBuilder isPublic();\n    MethodMatcherBuilder isPrivate();\n    MethodMatcherBuilder isAbstract();\n    MethodMatcherBuilder isStatic();\n\n    MethodMatcherBuilder notPublic();\n    MethodMatcherBuilder notPrivate();\n    MethodMatcherBuilder notAbstract();\n    MethodMatcherBuilder notStatic();\n\n    MethodMatcherBuilder returnType(String returnType);\n\n    // setup method arguments condition\n    MethodMatcherBuilder arg(int idx, String argType);\n    MethodMatcherBuilder argsLength(int length);\n\n    // setup method matcher name\n    MethodMatcherBuilder qualifier(String qualifier);\n\n    // only match methods overridden from matched classed through cMatcher\n    MethodMatcherBuilder isOverriddenFrom(IClassMatcher cMatcher);\n```\n\n\n### Logical Operation\n- Logical AND  \nAs mention above, each condition in a matcher is a combination of `logical AND`.  \nFor example, the following MethodMatcher will match the method which named `getConnection` and with a return type of `java.sql.Connection`.\n```java\n    MethodMatcher.builder()\n        .named(\"getConnection\")\n        .returnType(\"java.sql.Connection\")\n        .build())\n\n```\nIn addition, `MethodMatcherBuilder` provides `and()` method which will finish current builder and start a new `MethodMatcherBuilder`.\nThe previous builder will generate a `Left MethodMatcher` and the new builder will generate a 'Right MethodMatcher'.\nThe `Left matcher` and `Right matcher` will be combined into one `AndMethodMatcher`, and a method will match as long as the `Left` or `Right` matcher is a match.  \n\nFor example, the following matcher will match public methods that named `write` and exclude that with `void` return type.\n\n```java\n    MethodMatcher.builder()\n        .named(\"write\")\n        .argsLength(1)\n        .isPublic()\n        .and()\n        .returnType(\"void\")\n        .negate()\n        .build()\n```\n\n\n- Logical OR  \n  `MethodMatcherBuilder` provides `or()` method which will finish current builder and start a new `MethodMatcherBuilder`.  \n  The previous builder will generate a `Left MethodMatcher` and the new builder will generate a 'Right MethodMatcher'.\n  The `Left matcher` and `Right matcher` will be combined into one `OrMethodMatcher`, and a method will match as long as the `Left` or `Right` matcher is a match.  \n\n  For example, the following MethodMatcher will match method that named `addBatch`  **or** named \"clearBatch\" and then give this `OrClassMatcher` matcher a name `batch`.\n\n```java\n    MethodMatcher.builder()\n        .named(\"addBatch\")\n        .or()\n        .named(\"clearBatch\")\n        .qualifier(\"batch\")\n        .build()\n```\n\n- Logical NOT  \n  The `negate()` method of `MethodMatcherBuilder` uses `NegateMethodMatcher` to wrap the original matcher generated by builder when no such method is applied.  \n  When a method matches the original matcher, it is unmatched by the wrap matcher; conversely, it is matched.\n\n  For example, the following matcher will match public methods that named `write` and exclude that with `void` return type.\n\n```java\n    MethodMatcher.builder()\n        .named(\"write\")\n        .argsLength(1)\n        .isPublic()\n        .and()\n        .returnType(\"void\")\n        .negate()\n        .build()\n```\n\nEnds.\n"
  },
  {
    "path": "doc/metric-api.md",
    "content": "# Metric API\n\nIn Easeagent, Metric is inspired by Dropwizard, but Dropwizard is just the basis of Metric.\n\nThe use of time series indicators needs to be very rigorous.\n \nIn order to prevent the random use of metrics and the wrong use of metrics as relational databases, we have a strict standard for business metrics.\n\nIn order to be applicable to the business, we additionally encapsulate and define a dedicated interface.\n\nThese interfaces are related to business.\n\n\n![image](./images/Metric-SequenceDiagram-UML.png)\n\n![image](./images/Metric-Class-UML.png)\n\n\n### 1. MetricSubType\n\n[com.megaease.easeagent.plugin.api.metric.name.MetricSubType](../plugin-api/src/main/java/com/megaease/easeagent/plugin/api/metric/name/MetricSubType.java)\n\nWe have briefly categorized common business types.\n\n| name           | Description                                                                                           |\n|:---------------|:------------------------------------------------------------------------------------------------------|\n| DEFAULT        | Default,no metric with error                                                                          |\n| ERROR          | Metric with error                                                                                     |\n| CHANNEL        | The metric of the connection channel, for rabbitmq.                                                   |\n| CONSUMER       | The metric of the consumer of the data queue, for messaging kafka/rabbitmq consumer, etc.             |\n| PRODUCER       | The metric of the producer of the data queue, for messaging kafka/rabbitmq producer, etc.             |\n| CONSUMER_ERROR | The error metric of the consumer of the data queue, for messaging kafka/rabbitmq consumer error, etc. |\n| PRODUCER_ERROR | The error metric of the producer of the data queue, for messaging kafka/rabbitmq producer error, etc. |\n| NONE           | Unknown type                                                                                          |\n\n### 2. MetricField\n\n[com.megaease.easeagent.plugin.api.metric.name.MetricField](../plugin-api/src/main/java/com/megaease/easeagent/plugin/api/metric/name/MetricField.java)\n\nDifferent types will use different names depending on the business.\n\nFor example, the Count value of Counter and Timer can be named TotalCount or ErrorCount according to success or failure.\n\nWe have made certain specifications for the name:\n\n```java\npublic enum MetricField {\n    MIN_EXECUTION_TIME(\"min\", ConverterType.DURATION, 2),\n    MAX_EXECUTION_TIME(\"max\", ConverterType.DURATION, 2),\n    MEAN_EXECUTION_TIME(\"mean\", ConverterType.DURATION, 2),\n    P25_EXECUTION_TIME(\"p25\", ConverterType.DURATION, 2),\n    P50_EXECUTION_TIME(\"p50\", ConverterType.DURATION, 2),\n    P75_EXECUTION_TIME(\"p75\", ConverterType.DURATION, 2),\n    P95_EXECUTION_TIME(\"p95\", ConverterType.DURATION, 2),\n    P98_EXECUTION_TIME(\"p98\", ConverterType.DURATION, 2),\n    P99_EXECUTION_TIME(\"p99\", ConverterType.DURATION, 2),\n    P999_EXECUTION_TIME(\"p999\", ConverterType.DURATION, 2),\n    STD(\"std\"),\n    EXECUTION_COUNT(\"cnt\"),\n    EXECUTION_ERROR_COUNT(\"errcnt\"),\n    M1_RATE(\"m1\", ConverterType.RATE, 5),\n    M5_RATE(\"m5\", ConverterType.RATE, 5),\n    M15_RATE(\"m15\", ConverterType.RATE, 5),\n    RETRY_M1_RATE(\"retrym1\", ConverterType.RATE, 5),\n    RETRY_M5_RATE(\"retrym5\", ConverterType.RATE, 5),\n    RETRY_M15_RATE(\"retrym15\", ConverterType.RATE, 5),\n    RATELIMITER_M1_RATE(\"rlm1\", ConverterType.RATE, 5),\n    RATELIMITER_M5_RATE(\"rlm5\", ConverterType.RATE, 5),\n    RATELIMITER_M15_RATE(\"rlm15\", ConverterType.RATE, 5),\n    CIRCUITBREAKER_M1_RATE(\"cbm1\", ConverterType.RATE, 5),\n    CIRCUITBREAKER_M5_RATE(\"cbm5\", ConverterType.RATE, 5),\n    CIRCUITBREAKER_M15_RATE(\"cbm15\", ConverterType.RATE, 5),\n    MEAN_RATE(\"mean_rate\", ConverterType.RATE, 5),\n    M1_ERROR_RATE(\"m1err\", ConverterType.RATE, 5),\n    M5_ERROR_RATE(\"m5err\", ConverterType.RATE, 5),\n    M15_ERROR_RATE(\"m15err\", ConverterType.RATE, 5),\n    M1_COUNT(\"m1cnt\", ConverterType.RATE, 0),\n    M5_COUNT(\"m5cnt\", ConverterType.RATE, 0),\n    M15_COUNT(\"m15cnt\", ConverterType.RATE, 0),\n    TIMES_RATE(\"time_rate\", ConverterType.RATE, 5),\n    TOTAL_COLLECTION_TIME(\"total_collection_time\", ConverterType.RATE, 0),\n    TIMES(\"times\", ConverterType.RATE, 0),\n    /* channel is for rabbitmq */\n    CHANNEL_M1_RATE(\"channel_m1_rate\", ConverterType.RATE, 5),\n    CHANNEL_M5_RATE(\"channel_m5_rate\", ConverterType.RATE, 5),\n    CHANNEL_M15_RATE(\"channel_m15_rate\", ConverterType.RATE, 5),\n    QUEUE_M1_RATE(\"queue_m1_rate\", ConverterType.RATE, 5),\n    QUEUE_M5_RATE(\"queue_m5_rate\", ConverterType.RATE, 5),\n    QUEUE_M15_RATE(\"queue_m15_rate\", ConverterType.RATE, 5),\n    QUEUE_M1_ERROR_RATE(\"queue_m1_error_rate\", ConverterType.RATE, 5),\n    QUEUE_M5_ERROR_RATE(\"queue_m5_error_rate\", ConverterType.RATE, 5),\n    QUEUE_M15_ERROR_RATE(\"queue_m15_error_rate\", ConverterType.RATE, 5),\n    /*producer and consumer is for message kafka rabbitmq service*/\n    PRODUCER_M1_RATE(\"prodrm1\", ConverterType.RATE, 5),\n    PRODUCER_M5_RATE(\"prodrm5\", ConverterType.RATE, 5),\n    PRODUCER_M15_RATE(\"prodrm15\", ConverterType.RATE, 5),\n    PRODUCER_M1_ERROR_RATE(\"prodrm1err\", ConverterType.RATE, 5),\n    PRODUCER_M5_ERROR_RATE(\"prodrm5err\", ConverterType.RATE, 5),\n    PRODUCER_M15_ERROR_RATE(\"prodrm15err\", ConverterType.RATE, 5),\n    CONSUMER_M1_RATE(\"consrm1\", ConverterType.RATE, 5),\n    CONSUMER_M5_RATE(\"consrm5\", ConverterType.RATE, 5),\n    CONSUMER_M15_RATE(\"consrm15\", ConverterType.RATE, 5),\n    CONSUMER_M1_ERROR_RATE(\"consrm1err\", ConverterType.RATE, 5),\n    CONSUMER_M5_ERROR_RATE(\"consrm5err\", ConverterType.RATE, 5),\n    CONSUMER_M15_ERROR_RATE(\"consrm15err\", ConverterType.RATE, 5),\n    EXECUTION_PRODUCER_ERROR_COUNT(\"prodrerrcnt\"),\n    EXECUTION_CONSUMER_ERROR_COUNT(\"consrerrcnt\"),\n    EXECUTION_PRODUCER_COUNT(\"prodrcnt\"),\n    EXECUTION_CONSUMER_COUNT(\"consrcnt\"),\n    PRODUCER_MIN_EXECUTION_TIME(\"prodrmin\", ConverterType.DURATION, 2),\n    PRODUCER_MAX_EXECUTION_TIME(\"prodrmax\", ConverterType.DURATION, 2),\n    PRODUCER_MEAN_EXECUTION_TIME(\"prodrmean\", ConverterType.DURATION, 2),\n    PRODUCER_P25_EXECUTION_TIME(\"prodrp25\", ConverterType.DURATION, 2),\n    PRODUCER_P50_EXECUTION_TIME(\"prodrp50\", ConverterType.DURATION, 2),\n    PRODUCER_P75_EXECUTION_TIME(\"prodrp75\", ConverterType.DURATION, 2),\n    PRODUCER_P95_EXECUTION_TIME(\"prodrp95\", ConverterType.DURATION, 2),\n    PRODUCER_P98_EXECUTION_TIME(\"prodrp98\", ConverterType.DURATION, 2),\n    PRODUCER_P99_EXECUTION_TIME(\"prodrp99\", ConverterType.DURATION, 2),\n    PRODUCER_P999_EXECUTION_TIME(\"prodrp999\", ConverterType.DURATION, 2),\n    CONSUMER_MIN_EXECUTION_TIME(\"consrmin\", ConverterType.DURATION, 2),\n    CONSUMER_MAX_EXECUTION_TIME(\"consrmax\", ConverterType.DURATION, 2),\n    CONSUMER_MEAN_EXECUTION_TIME(\"consrmean\", ConverterType.DURATION, 2),\n    CONSUMER_P25_EXECUTION_TIME(\"consrp25\", ConverterType.DURATION, 2),\n    CONSUMER_P50_EXECUTION_TIME(\"consrp50\", ConverterType.DURATION, 2),\n    CONSUMER_P75_EXECUTION_TIME(\"consrp75\", ConverterType.DURATION, 2),\n    CONSUMER_P95_EXECUTION_TIME(\"consrp95\", ConverterType.DURATION, 2),\n    CONSUMER_P98_EXECUTION_TIME(\"consrp98\", ConverterType.DURATION, 2),\n    CONSUMER_P99_EXECUTION_TIME(\"consrp99\", ConverterType.DURATION, 2),\n    CONSUMER_P999_EXECUTION_TIME(\"consrp999\", ConverterType.DURATION, 2),\n    NONE(\"\", ConverterType.RATE, 0);\n}\n```\n\n### 3.MetricValueFetcher\n\n[com.megaease.easeagent.plugin.api.metric.name.MetricValueFetcher](../plugin-api/src/main/java/com/megaease/easeagent/plugin/api/metric/name/MetricValueFetcher.java)\n\nMetric has different types, and each type can calculate different data.\n\nFor example: Counter can only get Count, Timer can get Max, Mean, Min, P95 ...\n\nRegarding the value that the type can calculate, we have encapsulated it:\n\n```java\npublic enum MetricValueFetcher {\n    CountingCount(Counter::getCount, Counter.class),\n    SnapshotMaxValue(Snapshot::getMax, Snapshot.class),\n    SnapshotMeanValue(Snapshot::getMean, Snapshot.class),\n    SnapshotMinValue(Snapshot::getMin, Snapshot.class),\n    Snapshot25Percentile(s -> s.getValue(0.25), Snapshot.class),\n    SnapshotMedianValue(Snapshot::getMedian, Snapshot.class),\n    Snapshot50PercentileValue(Snapshot::getMedian, Snapshot.class),\n    Snapshot75PercentileValue(Snapshot::get75thPercentile, Snapshot.class),\n    Snapshot95PercentileValue(Snapshot::get95thPercentile, Snapshot.class),\n    Snapshot98PercentileValue(Snapshot::get98thPercentile, Snapshot.class),\n    Snapshot99PercentileValue(Snapshot::get99thPercentile, Snapshot.class),\n    Snapshot999PercentileValue(Snapshot::get999thPercentile, Snapshot.class),\n    MeteredM1Rate(Meter::getOneMinuteRate, Meter.class),\n    MeteredM1RateIgnoreZero(Meter::getOneMinuteRate, Meter.class, aDouble -> aDouble),\n    MeteredM5Rate(Meter::getFiveMinuteRate, Meter.class),\n    MeteredM15Rate(Meter::getFifteenMinuteRate, Meter.class),\n    MeteredMeanRate(Meter::getMeanRate, Meter.class),\n    MeteredCount(Meter::getCount, Meter.class);\n}\n```\n\n### 4. NameFactory\n\n[com.megaease.easeagent.plugin.api.metric.name.NameFactory](../plugin-api/src/main/java/com/megaease/easeagent/plugin/api/metric/name/NameFactory.java)\n\nAccording to the value type and naming combination, we standardize a NameFactory.\n\n#### Example:\n\n```java\nclass ServerMetric{\n    @Nonnull\n    public static NameFactory nameFactory() {\n        return NameFactory.createBuilder()\n            .counterType(MetricSubType.DEFAULT, ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                .put(MetricField.EXECUTION_COUNT, MetricValueFetcher.CountingCount)\n                .build())\n            .meterType(MetricSubType.DEFAULT, ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                .put(MetricField.M1_RATE, MetricValueFetcher.MeteredM1RateIgnoreZero)\n                .put(MetricField.M5_RATE, MetricValueFetcher.MeteredM5Rate)\n                .put(MetricField.M15_RATE, MetricValueFetcher.MeteredM15Rate)\n                .put(MetricField.MEAN_RATE, MetricValueFetcher.MeteredMeanRate)\n                .build())\n            .counterType(MetricSubType.ERROR, ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                .put(MetricField.EXECUTION_ERROR_COUNT, MetricValueFetcher.CountingCount)\n                .build())\n            .meterType(MetricSubType.ERROR, ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                .put(MetricField.M1_ERROR_RATE, MetricValueFetcher.MeteredM1Rate)\n                .put(MetricField.M5_ERROR_RATE, MetricValueFetcher.MeteredM5Rate)\n                .put(MetricField.M15_ERROR_RATE, MetricValueFetcher.MeteredM15Rate)\n                .put(MetricField.MEAN_RATE, MetricValueFetcher.MeteredMeanRate)\n                .build())\n            .gaugeType(MetricSubType.DEFAULT, new HashMap<>())\n            .timerType(MetricSubType.DEFAULT,\n                ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                    .put(MetricField.MIN_EXECUTION_TIME, MetricValueFetcher.SnapshotMinValue)\n                    .put(MetricField.MAX_EXECUTION_TIME, MetricValueFetcher.SnapshotMaxValue)\n                    .put(MetricField.MEAN_EXECUTION_TIME, MetricValueFetcher.SnapshotMeanValue)\n                    .put(MetricField.P25_EXECUTION_TIME, MetricValueFetcher.Snapshot25Percentile)\n                    .put(MetricField.P50_EXECUTION_TIME, MetricValueFetcher.Snapshot50PercentileValue)\n                    .put(MetricField.P75_EXECUTION_TIME, MetricValueFetcher.Snapshot75PercentileValue)\n                    .put(MetricField.P95_EXECUTION_TIME, MetricValueFetcher.Snapshot95PercentileValue)\n                    .put(MetricField.P98_EXECUTION_TIME, MetricValueFetcher.Snapshot98PercentileValue)\n                    .put(MetricField.P99_EXECUTION_TIME, MetricValueFetcher.Snapshot99PercentileValue)\n                    .put(MetricField.P999_EXECUTION_TIME, MetricValueFetcher.Snapshot999PercentileValue)\n                    .build())\n            .build();\n    }\n}\n```\n\n### 4. Registry\n \nInherit the ServiceMetric class in order to obtain the MetricRegistry.\n[com.megaease.easeagent.plugin.api.metric.ServiceMetric](../plugin-api/src/main/java/com/megaease/easeagent/plugin/api/metric/ServiceMetric.java)\n\nUse the ServiceMetricRegistry interface to register and create a singleton.\n\n[com.megaease.easeagent.plugin.api.metric.ServiceMetricRegistry](../plugin-api/src/main/java/com/megaease/easeagent/plugin/api/metric/ServiceMetricRegistry.java)\n\n##### Config\n\nMetric configuration follows plugin configuration rules [metric config](user-manual.md#metric)\n\n##### Tags\n\nMetrics have values as well as tags. EaseAgent supports custom tags, but these tags must be predictable and given in advance.\n\nTo better support the business, three tags must be given: category，type and key\n\nSo it is fixed in Tags and must provide three pieces of information: category，type and keyFieldName\n\n[com.megaease.easeagent.plugin.api.metric.name.Tags](../plugin-api/src/main/java/com/megaease/easeagent/plugin/api/metric/name/Tags.java)\n\nIts tag is copied as follows:\n```\noutput.put(\"category\", tags.category)\noutput.put(\"type\", tags.type)\noutput.put(tags.keyFieldName, {@link NameFactory}.key[?])\ntags.tags.forEach((k,v)->{\n    output.put(k,v)\n})\n```\n\n##### Singleton\n\nThe Key of the singleton is: `domain`, `namespace`, `id`, `tags` and the `type` of class.\n\n\n#### Example:\n```java\npublic class ServerMetric extends ServiceMetric {\n\n    public ServerMetric(@Nonnull MetricRegistry metricRegistry, @Nonnull NameFactory nameFactory) {\n        super(metricRegistry, nameFactory);\n    }\n\n    public void collectMetric(String key, int statusCode, Throwable throwable, long startMillis, long endMillis) {\n        Timer timer = metricRegistry.timer(nameFactory.timerName(key, MetricSubType.DEFAULT));\n        timer.update(Duration.ofMillis(endMillis - startMillis));\n        final Meter errorMeter = metricRegistry.meter(nameFactory.meterName(key, MetricSubType.ERROR));\n        final Meter meter = metricRegistry.meter(nameFactory.meterName(key, MetricSubType.DEFAULT));\n        Counter errorCounter = metricRegistry.counter(nameFactory.counterName(key, MetricSubType.ERROR));\n        Counter counter = metricRegistry.counter(nameFactory.counterName(key, MetricSubType.DEFAULT));\n        boolean hasException = throwable != null;\n        if (statusCode >= 400 || hasException) {\n            errorMeter.mark();\n            errorCounter.inc();\n        }\n        counter.inc();\n        meter.mark();\n\n        metricRegistry.gauge(nameFactory.gaugeName(key, MetricSubType.DEFAULT), () -> () -> {\n            BigDecimal m1ErrorPercent = BigDecimal.ZERO;\n            BigDecimal m5ErrorPercent = BigDecimal.ZERO;\n            BigDecimal m15ErrorPercent = BigDecimal.ZERO;\n            BigDecimal error = BigDecimal.valueOf(errorMeter.getOneMinuteRate()).setScale(5, BigDecimal.ROUND_HALF_DOWN);\n            BigDecimal n = BigDecimal.valueOf(meter.getOneMinuteRate());\n            if (n.compareTo(BigDecimal.ZERO) != 0) {\n                m1ErrorPercent = error.divide(n, 2, BigDecimal.ROUND_HALF_UP);\n            }\n            error = BigDecimal.valueOf(errorMeter.getFiveMinuteRate()).setScale(5, BigDecimal.ROUND_HALF_DOWN);\n            n = BigDecimal.valueOf(meter.getFiveMinuteRate());\n            if (n.compareTo(BigDecimal.ZERO) != 0) {\n                m5ErrorPercent = error.divide(n, 2, BigDecimal.ROUND_HALF_UP);\n            }\n\n            error = BigDecimal.valueOf(errorMeter.getFifteenMinuteRate()).setScale(5, BigDecimal.ROUND_HALF_DOWN);\n            n = BigDecimal.valueOf(meter.getFifteenMinuteRate());\n            if (n.compareTo(BigDecimal.ZERO) != 0) {\n                m15ErrorPercent = error.divide(n, 2, BigDecimal.ROUND_HALF_UP);\n            }\n            return new ErrorPercentModelGauge(m1ErrorPercent, m5ErrorPercent, m15ErrorPercent);\n        });\n    }\n\n    @Nonnull\n    public static NameFactory nameFactory() {\n        return NameFactory.createBuilder()\n            .counterType(MetricSubType.DEFAULT, ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                .put(MetricField.EXECUTION_COUNT, MetricValueFetcher.CountingCount)\n                .build())\n            .meterType(MetricSubType.DEFAULT, ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                .put(MetricField.M1_RATE, MetricValueFetcher.MeteredM1RateIgnoreZero)\n                .put(MetricField.M5_RATE, MetricValueFetcher.MeteredM5Rate)\n                .put(MetricField.M15_RATE, MetricValueFetcher.MeteredM15Rate)\n                .put(MetricField.MEAN_RATE, MetricValueFetcher.MeteredMeanRate)\n                .build())\n            .counterType(MetricSubType.ERROR, ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                .put(MetricField.EXECUTION_ERROR_COUNT, MetricValueFetcher.CountingCount)\n                .build())\n            .meterType(MetricSubType.ERROR, ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                .put(MetricField.M1_ERROR_RATE, MetricValueFetcher.MeteredM1Rate)\n                .put(MetricField.M5_ERROR_RATE, MetricValueFetcher.MeteredM5Rate)\n                .put(MetricField.M15_ERROR_RATE, MetricValueFetcher.MeteredM15Rate)\n                .put(MetricField.MEAN_RATE, MetricValueFetcher.MeteredMeanRate)\n                .build())\n            .gaugeType(MetricSubType.DEFAULT, new HashMap<>())\n            .timerType(MetricSubType.DEFAULT,\n                ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                    .put(MetricField.MIN_EXECUTION_TIME, MetricValueFetcher.SnapshotMinValue)\n                    .put(MetricField.MAX_EXECUTION_TIME, MetricValueFetcher.SnapshotMaxValue)\n                    .put(MetricField.MEAN_EXECUTION_TIME, MetricValueFetcher.SnapshotMeanValue)\n                    .put(MetricField.P25_EXECUTION_TIME, MetricValueFetcher.Snapshot25Percentile)\n                    .put(MetricField.P50_EXECUTION_TIME, MetricValueFetcher.Snapshot50PercentileValue)\n                    .put(MetricField.P75_EXECUTION_TIME, MetricValueFetcher.Snapshot75PercentileValue)\n                    .put(MetricField.P95_EXECUTION_TIME, MetricValueFetcher.Snapshot95PercentileValue)\n                    .put(MetricField.P98_EXECUTION_TIME, MetricValueFetcher.Snapshot98PercentileValue)\n                    .put(MetricField.P99_EXECUTION_TIME, MetricValueFetcher.Snapshot99PercentileValue)\n                    .put(MetricField.P999_EXECUTION_TIME, MetricValueFetcher.Snapshot999PercentileValue)\n                    .build())\n            .build();\n    }\n}\n\npublic class DoFilterMetricInterceptor extends Interceptor {\n    private static volatile ServerMetric SERVER_METRIC = null;\n    private static final Object START_MACK = new Object();\n    \n    @Override\n    public void init(Config config, String className, String methodName, String methodDescriptor) {\n        SERVER_METRIC = ServiceMetricRegistry.getOrCreate(config, new Tags(\"application\", \"http-request\", \"url\"),\n            new ServiceMetricSupplier<ServerMetric>() {\n                @Override\n                public NameFactory newNameFactory() {\n                    return ServerMetric.nameFactory();\n                }\n        \n                @Override\n                public ServerMetric newInstance(MetricRegistry metricRegistry, NameFactory nameFactory) {\n                    return new ServerMetric(metricRegistry, nameFactory);\n                }\n            });\n    }\n    \n    @Override\n    public void before(MethodInfo methodInfo, Context context){\n        context.put(START_MACK, System.currentTimeMillis());\n    }\n    \n    @Override\n    public void after(MethodInfo methodInfo, Context context){\n        long start = context.remove(START_MACK);\n        long end = System.currentTimeMillis();\n        String key = \"GET /demo\";\n        int statusCode = 200;\n        SERVER_METRIC.collectMetric(key, statusCode, throwable, start, end);\n    }\n}\n```\n\n### 5. Metric\n\nEven though EaseAgent's Metric is inspired by Dropwizard, it still hopes to decouple its implementation from Dropwizard.\n\nSo EaseAgent has its own API, although the implementation scheme currently used is Dropwizard.\n\n[com.megaease.easeagent.plugin.api.metric.MetricRegistry](../plugin-api/src/main/java/com/megaease/easeagent/plugin/api/metric/MetricRegistry.java)\n\n[com.megaease.easeagent.plugin.api.metric.Counter](../plugin-api/src/main/java/com/megaease/easeagent/plugin/api/metric/Counter.java)\n\n[com.megaease.easeagent.plugin.api.metric.Histogram](../plugin-api/src/main/java/com/megaease/easeagent/plugin/api/metric/Histogram.java)\n\n[com.megaease.easeagent.plugin.api.metric.Meter](../plugin-api/src/main/java/com/megaease/easeagent/plugin/api/metric/Meter.java)\n\n[com.megaease.easeagent.plugin.api.metric.Snapshot](../plugin-api/src/main/java/com/megaease/easeagent/plugin/api/metric/Snapshot.java)\n\n[com.megaease.easeagent.plugin.api.metric.Timer](../plugin-api/src/main/java/com/megaease/easeagent/plugin/api/metric/Timer.java)\n\n\n### 6. Customize\n\nWhen you are sure that you want to implement your own metric, then you only need a metric output class, and finally use this output class to output to Kafka or backend. \n\nWe provide such an output interface, this interface accepts string output.\n\nThe standard of EaseMonitor is to use json format, if unnecessary, please use json as output.\n\n[com.megaease.easeagent.plugin.api.Reporter](../plugin-api/src/main/java/com/megaease/easeagent/plugin/api/Reporter.java)\n\n[com.megaease.easeagent.plugin.bridge.EaseAgent.metricReporterFactory](../plugin-api/src/main/java/com/megaease/easeagent/plugin/bridge/EaseAgent.java)\n\nThe obtained Reporter is a singleton, and the key of the singleton is `namespace`.\n\nIts output configuration complies with metric configuration rules: [metric config](user-manual.md#metric)\n\n#### Example:\n\n```java\npublic class MD5ReportConsumer {\n    private final Config config;\n    private final Reporter reporter;\n    public MD5ReportConsumer() {\n        this.config = AutoRefreshRegistry.getOrCreate(\"observability\", \"md5Dictionary\", \"metric\");\n        this.reporter = EaseAgent.metricReporterFactory(config);\n    }\n\n    \n    @Override\n    public void accept(Map<String, String> map) {\n        if (!this.config.enabled()) {\n            return;\n        }\n        map.put(\"host_name\", HostAddress.localhost());\n        map.put(\"host_ipv4\", HostAddress.getHostIpv4());\n        map.put(\"category\", \"application\");\n        String json = JsonUtil.toJson(item);\n        this.reporter.report(json);\n    }\n}\n```\n\n### 7.PrometheusExports\n\nWe have redefined Prometheus Exports according to the following naming standard and statistics rules.\n\n#### Metric Name\n\nFor better readability, we get the `category` and `type` from `Tags`, and add `MetricField.field` as the metric name\n\nName format: `${category}_${type}_${MetricField.field}`\n\nExample: `application_http_request_m1`\n\n##### Metric Label\n\nExcept `category`, `type`, `MetricField.field` and `value`, other fields will be exported in the form of labels.\n \nBy default, there will be the following labels.\n\n| label name           | label value            | Description                                                                                                |\n|:---------------------|:-----------------------|:-----------------------------------------------------------------------------------------------------------|\n| MetricSubType        | enum                   | The enum MetricSubType value: `DEFAULT,ERROR,CHANNEL,CONSUMER,PRODUCER,CONSUMER_ERROR,PRODUCER_ERROR,NONE` |\n| MetricType           | enum                   | The Metric Type by metric calculate: `TimerType,HistogramType,MeterType,CounterType,GaugeType`             |\n| host_ipv4            | String, xxx.xxx.xxx.xx | The ipv4 by host.                                                                                          |\n| host_name            | String                 | host name.                                                                                                 |\n| service              | String                 | The `name` read from the configuration. for you service name.                                              |\n| system               | String                 | The `system` read from the configuration. for you system name.                                             |\n| ${Tags.keyFieldName} | ${metricKey}           | The metric label by metric key.                                                                            |\n| ${Tags.tags().key}   | ${Tags.tags().value}   | Your custom label, which comes from `Tags`                                                                 |\n\n##### Metric Value\n\nUnder normal circumstances, each metric name should correspond to a calculation method.\n\n##### Example :\n\nMetric name is `application_http_request_m1`, the value is `1.0`, which is the weighted average QPS of the last minute calculated.\n\nIt is calculated using `Meter::getOneMinuteRate`. Its corresponding `MetricValueFetcher` is `MeteredM1RateIgnoreZero`.\n\n```java\npublic class M1MetricCollect {\n    static final M1Metric m1Metric;\n\n    static {\n        IPluginConfig config = EaseAgent.getConfig(\"observability\", \"collectM1\", ConfigConst.PluginID.METRIC);\n        Tags tags = new Tags(\"application\", \"http-request\", \"url\").put(\"city\", \"beijing\");\n        ServiceMetricSupplier<M1Metric> m1MetricSupplier = new ServiceMetricSupplier<M1Metric>() {\n            @Override\n            public NameFactory newNameFactory() {\n                return NameFactory.createBuilder()\n                    .meterType(MetricSubType.DEFAULT, ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                        .put(MetricField.M1_RATE, MetricValueFetcher.MeteredM1RateIgnoreZero)\n                        .build())\n                    .build();\n            }\n\n            @Override\n            public M1Metric newInstance(MetricRegistry metricRegistry, NameFactory nameFactory) {\n                return new M1Metric(metricRegistry, nameFactory);\n            }\n        };\n\n        m1Metric = EaseAgent.getOrCreateServiceMetric(config, tags, m1MetricSupplier);\n    }\n\n    public void collectM1() {\n        String url = \"GET /web_client\";\n        for (int i = 0; i < 100; i++) {\n            m1Metric.collectMetric(url);\n        }\n    }\n\n    public static class M1Metric extends ServiceMetric {\n\n        public M1Metric(@Nonnull MetricRegistry metricRegistry, @Nonnull NameFactory nameFactory) {\n            super(metricRegistry, nameFactory);\n        }\n\n        public void collectMetric(String key) {\n            final Meter meter = meter(key, MetricSubType.DEFAULT);\n            meter.mark();\n        }\n    }\n}\n```\n\nThe above example will export metrics like the following\n\nName and Label: \n\n`application_http_request_m1{MetricSubType=\"DEFAULT\",MetricType=\"MeterType\",city=\"beijing\",host_ipv4=\"10.127.48.163\",host_name=\"MacBook-Pro.local\",service=\"demo-service\",system=\"demo-system\",url=\"GET /web_client\"}`\n\nvalue : \n\n`90.0` \n \n![image](./images/prometheus-demo.jpg)\n\n"
  },
  {
    "path": "doc/plugin-unit-test.md",
    "content": "# Plugin Unit Test\n\nThis guide walks you through the process of unit testing a Plugin with Mock.\n\n## Add Mock jar\n\nFor Maven builds, you can do that with the following:\n\n```xml\n<dependency>\n    <groupId>com.megaease.easeagent</groupId>\n    <artifactId>plugin-api-mock</artifactId>\n    <version>${project.version}</version>\n    <scope>test</scope>\n</dependency>\n```\n\n## Create Test class\n\nNow you can create a test class for a Interceptor, as the file (from `src/test/java/com/megaease/easeagent/plugin/interceptor/RunnableInterceptorTest.java`) shows:\n\n```java\npackage com.megaease.easeagent.plugin.interceptor;\n\nimport com.megaease.easeagent.mock.plugin.api.MockEaseAgent;\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class RunnableInterceptorTest {\n\n    @Test\n    public void before() throws InterruptedException {\n        Context context = EaseAgent.getContext();\n        final Span span = context.nextSpan();\n        span.start();\n        span.cacheScope();\n        RunnableInterceptor runnableInterceptor = new RunnableInterceptor();\n        AtomicInteger run = new AtomicInteger();\n        Runnable runnable = () -> {\n            Context runCont = EaseAgent.getContext();\n            assertTrue(runCont.currentTracing().hasCurrentSpan());\n            Span span1 = runCont.nextSpan();\n            assertEquals(span.traceId(), span1.traceId());\n            assertEquals(span.spanId(), span1.parentId());\n            assertNotEquals(span.spanId(), span1.spanId());\n            run.incrementAndGet();\n        };\n        MethodInfo methodInfo = MethodInfo.builder()\n            .invoker(\"\")\n            .type(\"\")\n            .method(\"\")\n            .args(new Object[]{runnable})\n            .build();\n        runnableInterceptor.before(methodInfo, context);\n        Thread thread = new Thread((Runnable) methodInfo.getArgs()[0]);\n        thread.start();\n        thread.join();\n        assertEquals(run.get(), 1);\n        span.finish();\n\n        ReportSpan span1 = MockEaseAgent.getLastSpan();\n        assertEquals(span.traceIdString(), span1.traceId());\n        assertEquals(span.parentIdString(), span1.parentId());\n        assertEquals(span.spanIdString(), span1.id());\n        System.out.println(\"run count: \" + run.get());\n    }\n}\n```\n \nThe class is flagged as a `@RunWith(EaseAgentJunit4ClassRunner.class)`, meaning it is ready for use by Mock EaseAgent to handle junit4 test. \nThe method is flagged as a `@Test` is junit annotation. \n\n## Unit Test API\n\nWhen the class is flagged with `@RunWith(EaseAgentJunit4ClassRunner.class)`, you can use the EaseAgent API in the method like Interceptor\n\nExample:\n```\nContext context = EaseAgent.getContext();\n        final Span span = context.nextSpan();\n        span.start();\n```\n```\nMethodInfo methodInfo = MethodInfo.builder()\n    .invoker(\"\")\n    .type(\"\")\n    .method(\"\")\n    .args(new Object[]{runnable})\n    .build();\n```\n\nYou may have tested the Tracing or Metric generation process. At this time, you need an additional API to get the result, and then verify\n\n### MockEaseAgent API\nSee:\n\n* [MockEaseAgent](../mock/plugin-api-mock/src/main/java/com/megaease/easeagent/mock/plugin/api/MockEaseAgent.java)\n\n* [TagVerifier](../mock/plugin-api-mock/src/main/java/com/megaease/easeagent/mock/plugin/api/utils/TagVerifier.java)\n\n* [LastJsonReporter](../mock/report-mock/src/main/java/com/megaease/easeagent/mock/report/impl/LastJsonReporter.java)\n\n* [ReportSpan](../plugin-api/src/main/java/com/megaease/easeagent/plugin/report/tracing/ReportSpan.java)\n\n\n```\n// Verify that the Span has been started and finished and reports\nSpan span = EaseAgent.getContext().nextSpan();\nspan.start().finish();\nReportSpan reportSpan = MockEaseAgent.getLastSpan();\nSpanTestUtils.sameId(span, MockEaseAgent.getLastSpan());\n\n//verify metric count==1\nLastJsonReporter lastJsonReporter = MockEaseAgent.lastMetricJsonReporter(TagVerifier.build(tags, key)::verifyAnd);\nMap<String, Object> metric = lastJsonReporter.flushAndOnlyOne();\nassertEquals(1, metric.get(MetricField.EXECUTION_COUNT.getField()));\n```\n\nFor example see: \n* [MockEaseAgentTest](../mock/plugin-api-mock/src/test/java/com/megaease/easeagent/mock/plugin/api/demo/MockEaseAgentTest.java)\n* [InterceptorTest](../mock/plugin-api-mock/src/test/java/com/megaease/easeagent/mock/plugin/api/demo/InterceptorTest.java)\n\n### Unit test utils\n\nIn order to facilitate testing, several of our commonly used utils are under the `com.megaease.easeagent.mock.plugin.api.utils` package.\n\n* [ConfigTestUtils](../mock/plugin-api-mock/src/main/java/com/megaease/easeagent/mock/plugin/api/utils/ConfigTestUtils.java)\n* [InterceptorTestUtils](../mock/plugin-api-mock/src/main/java/com/megaease/easeagent/mock/plugin/api/utils/InterceptorTestUtils.java)\n* [SpanTestUtils](../mock/plugin-api-mock/src/main/java/com/megaease/easeagent/mock/plugin/api/utils/SpanTestUtils.java)\n\n"
  },
  {
    "path": "doc/prometheus-metric-schedule.md",
    "content": "# Prometheus Metric Schedule\n\nPrometheus Exports Rules: [Prometheus Exports](./metric-api.md#7prometheusexports)\n\n## Common label\n\n| label name    |    Value Example    | Description                                                                                                       |\n|:--------------|:-------------------:|:------------------------------------------------------------------------------------------------------------------|\n| MetricSubType |      `DEFAULT`      | The enum MetricSubType value: `DEFAULT, ERROR, CHANNEL, CONSUMER, PRODUCER, CONSUMER_ERROR, PRODUCER_ERROR, NONE` |\n| MetricType    |    `CounterType`    | The Metric Type by metric calculate: `TimerType, HistogramType, MeterType, CounterType, GaugeType`                |\n| host_ipv4     |   `10.127.48.163`   | The ipv4 by host: xxx.xxx.xxx.xx                                                                                  |\n| host_name     | `MacBook-Pro.local` | host name.                                                                                                        |\n| service       |     `demo-name`     | The `name` read from the configuration. for you service name.                                                     |\n| system        |    `demo-system`    | The `system` read from the configuration. for you system name.                                                    |\n\n## Metric Schedule\n\n### HTTP Request\nHTTP Request schema describes key metrics of service APIs, which include:\n* Total execution count (cnt)\n* Throughput (m1, m5, m15)\n* Error throughput (m1err, m5err, m15err)\n* Error throughput percentage (m1errpct, m5errpct, m15errpct)\n* Latency (p25, p50, p75, p95, p98, p99)\n* Execution duration (min, mean, max)\n\n| Metric Name                        |  Type   | Description                                                                                            |\n|:-----------------------------------|:-------:|:-------------------------------------------------------------------------------------------------------|\n| application_http_request_cnt       | integer | The total count of the request executed                                                                |\n| application_http_request_errcnt    | integer | The total error count of the request executed                                                          |\n| application_http_request_m1        | double  | The HTTP request executions per second (exponentially-weighted moving average) in last 1 minute        |\n| application_http_request_m5        | double  | The HTTP request executions per second (exponentially-weighted moving average) in last 5 minute.       |\n| application_http_request_m15       | double  | The HTTP request executions per second (exponentially-weighted moving average) in last 15 minute.      |\n| application_http_request_m1err     | double  | The HTTP error request executions per second (exponentially-weighted moving average) in last 1 minute  |\n| application_http_request_m5err     | double  | The HTTP error request executions per second (exponentially-weighted moving average) in last 5 minute. |\n| application_http_request_m15err    | double  | The HTTP error request executions per second (exponentially-weighted moving average) in last 15 minute |\n| application_http_request_m1errpct  | double  | error percentage in last 1 minute                                                                      |\n| application_http_request_m5errpct  | double  | error percentage in last 5 minute                                                                      |\n| application_http_request_m15errpct | double  | error percentage in last 15 minute                                                                     |\n| application_http_request_min       | double  | The http-request minimal execution duration in milliseconds.                                           |\n| application_http_request_max       | double  | The http-request maximal execution duration in milliseconds.                                           |\n| application_http_request_mean      | double  | The http-request mean execution duration in milliseconds.                                              |\n| application_http_request_p25       | double  | TP25: The http-request execution duration in milliseconds for 25% user.                                |\n| application_http_request_p50       | double  | TP50: The http-request execution duration in milliseconds for 50% user.                                |\n| application_http_request_p75       | double  | TP75: The http-request execution duration in milliseconds for 75% user.                                |\n| application_http_request_p95       | double  | TP95: The http-request execution duration in milliseconds for 95% user.                                |\n| application_http_request_p98       | double  | TP98: The http-request execution duration in milliseconds for 98% user.                                |\n| application_http_request_p99       | double  | TP99: The http-request execution duration in milliseconds for 99% user.                                |\n\n#### Dedicated label\n| Label Name | Essential |   Value Example   | Description            |\n|:-----------|:---------:|:-----------------:|:-----------------------|\n| url        |   true    | `GET /web_client` | the URL of the request |\n\n### JDBC Statement\nJDBC Statement schema describes key metrics of JDBC SQL Statement, which include:\n* Execution count (cnt, errcnt)\n* Throughput (m1, m5, m15)\n* Error throughput (m1err, m5err, m15err)\n* Latency (p25, p50, p75, p95, p98, p99, p999)\n* Execution duration (min, mean, max)\n\n| Metric Name                       |  Type   | Description                                                                                           |\n|:----------------------------------|:-------:|:------------------------------------------------------------------------------------------------------|\n| application_jdbc_statement_cnt    | integer | The total count of JDBC method executed                                                               |\n| application_jdbc_statement_errcnt | integer | The total error count of JDBC method executed                                                         |\n| application_jdbc_statement_m1     | double  | The JDBC method executions per second (exponentially-weighted moving average) in last 1 minute.       |\n| application_jdbc_statement_m5     | double  | The JDBC method executions per second (exponentially-weighted moving average) in last 5 minutes.      |\n| application_jdbc_statement_m15    | double  | The JDBC method executions per second (exponentially-weighted moving average) in last 15 minutes.     |\n| application_jdbc_statement_m1err  | double  | The JDBC method error executions per second (exponentially-weighted moving average) in last 1 minute  |\n| application_jdbc_statement_m5err  | double  | The JDBC method error executions per second (exponentially-weighted moving average) in last 5 minute. |\n| application_jdbc_statement_m15err | double  | The JDBC method error executions per second (exponentially-weighted moving average) in last 15 minute |\n| application_jdbc_statement_min    | double  | The JDBC method minimal execution duration in milliseconds.                                           |\n| application_jdbc_statement_max    | double  | The JDBC method maximal execution duration in milliseconds.                                           |\n| application_jdbc_statement_mean   | double  | The JDBC method mean execution duration in milliseconds.                                              |\n| application_jdbc_statement_p25    | double  | TP25: The JDBC method execution duration in milliseconds for 25% user.                                |\n| application_jdbc_statement_p50    | double  | TP50: The JDBC method execution duration in milliseconds for 50% user.                                |\n| application_jdbc_statement_p75    | double  | TP75: The JDBC method execution duration in milliseconds for 75% user.                                |\n| application_jdbc_statement_p95    | double  | TP95: The JDBC method execution duration in milliseconds for 95% user.                                |\n| application_jdbc_statement_p98    | double  | TP98: The JDBC method execution duration in milliseconds for 98% user.                                |\n| application_jdbc_statement_p99    | double  | TP99: The JDBC method execution duration in milliseconds for 99% user.                                |\n| application_jdbc_statement_p999   | double  | TP99.9: The JDBC method execution duration in milliseconds for 99.9% user.                            |\n\n#### Dedicated label\n| Label Name | Essential |           Value Example            | Description                                                                                                                                                                                                                                           |\n|:-----------|:---------:|:----------------------------------:|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| signature  |   true    | `440580e6c3215eceb4ef992d60adde9a` | Executed JDBC method signature. By default, It is an MD5 of SQL like `select * from data`. It can be SQL statement via turning off the switch: `plugin.observability.jdbc.sql.compress.enabled=false`. [Details](./user-manual.md#tracing-and-metric) |\n\n\n### JDBC Connection\nJDBC Connection schema describes key metrics of Getting Connection, which include:\n* Execution count (cnt, errcnt)\n* Throughput (m1, m5, m15)\n* Error throughput (m1err, m5err, m15err)\n* Latency (p25, p50, p75, p95, p98, p99, p999)\n* Execution duration (min, mean, max)\n\n| Metric Name                        |  Type   | Description                                                                                               |\n|:-----------------------------------|:-------:|:----------------------------------------------------------------------------------------------------------|\n| application_jdbc_connection_cnt    | integer | The total number of database connections                                                                  |\n| application_jdbc_connection_errcnt | integer | The total error number of database connections                                                            |\n| application_jdbc_connection_m1     | double  | The JDBC connection establishment per second (exponentially-weighted moving average) in last 1 minute.    |\n| application_jdbc_connection_m5     | double  | The JDBC connection establishment per second (exponentially-weighted moving average) in last 5 minutes.   |\n| application_jdbc_connection_m15    | double  | The JDBC connection establishment per second (exponentially-weighted moving average) in last 15 minutes.  |\n| application_jdbc_connection_m1err  | double  | The JDBC connection error executions per second (exponentially-weighted moving average) in last 1 minute  |\n| application_jdbc_connection_m5err  | double  | The JDBC connection error executions per second (exponentially-weighted moving average) in last 5 minute. |\n| application_jdbc_connection_m15err | double  | The JDBC connection error executions per second (exponentially-weighted moving average) in last 15 minute |\n| application_jdbc_connection_min    | double  | The JDBC connection minimal establishment duration in milliseconds.                                       |\n| application_jdbc_connection_max    | double  | The JDBC connection maximal establishment duration in milliseconds.                                       |\n| application_jdbc_connection_mean   | double  | The JDBC connection mean establishment duration in milliseconds.                                          |\n| application_jdbc_connection_p25    | double  | TP25: The JDBC connection establishment duration in milliseconds for 25% user.                            |\n| application_jdbc_connection_p50    | double  | TP50: The JDBC connection establishment duration in milliseconds for 50% user.                            |\n| application_jdbc_connection_p75    | double  | TP75: The JDBC connection establishment duration in milliseconds for 75% user.                            |\n| application_jdbc_connection_p95    | double  | TP95: The JDBC connection establishment duration in milliseconds for 95% user.                            |\n| application_jdbc_connection_p98    | double  | TP98: The JDBC connection establishment duration in milliseconds for 98% user.                            |\n| application_jdbc_connection_p99    | double  | TP99: The JDBC connection establishment duration in milliseconds for 99% user.                            |\n| application_jdbc_connection_p999   | double  | TP99.9: The JDBC connection establishment duration in milliseconds for 99.9% user.                        |\n\n\n#### Dedicated label\n| Label Name | Essential |    Value Example     | Description                     |\n|:-----------|:---------:|:--------------------:|:--------------------------------|\n| url        |   true    | `jdbc:hsqldb:mem:7e` | The url of database connections |\n\n\n### JVM Memory\nJVM Memory schema describes key metrics of Java memory usage, which include:\n* bytes-init\n* bytes-used\n* bytes-committed\n* bytes-max\n\n\n| Metric Name                            |  Type   | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |\n|:---------------------------------------|:-------:|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| application_jvm_memory_bytes_init      | integer | The value represents the initial amount of memory in bytes unit that the JVM requests from the operating system for memory management during startup. The JVM may request additional memory from the operating system and may also release memory to the system over time. The value of init may be undefined (value -1).                                                                                                                                                                                            |\n| application_jvm_memory_bytes_used      | integer | The value represents the amount of memory currently used in bytes unit.                                                                                                                                                                                                                                                                                                                                                                                                                                              |\n| application_jvm_memory_bytes_committed | integer | The value represents the amount of memory in bytes unit that is guaranteed to be available for use by the JVM. The amount of committed memory may change over time (increase or decrease). The JVM may release memory to the system and committed could be less than init. Value committed will always be greater than or equal to used.                                                                                                                                                                             |\n| application_jvm_memory_bytes_max       | integer | The value represents the maximum amount of memory in bytes unit that can be used for memory management. Its value may be undefined (value -1). The maximum amount of memory may change over time if defined. The amount of used and committed memory will always be less than or equal to max if max is defined. A memory allocation may fail if it attempts to increase the used memory such that used > committed even if used <= max would still be true (for example, when the system is low on virtual memory). |\n\n#### Dedicated label\n| Label Name | Essential | Value Example      | Description                                                                                                                                                                                                    |\n|:-----------|:---------:|:-------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| resource   |   true    | `pools.G1-Old-Gen` | Memory pool name. The Java virtual machine can have one or more memory pools. Reference list: [Platform](https://docs.oracle.com/en/middleware/standalone/coherence/14.1.1.0/rest-reference/api-platform.html) |\n\n\n### JVM GC\nJVM GC schema describes key metrics of JVM garbage collection, which include:\n* total_collection_time\n* times\n* times_rate\n\n| Metric Name                              |  Type   | Description                                                                               |\n|:-----------------------------------------|:-------:|:------------------------------------------------------------------------------------------|\n| application_jvm_gc_total_collection_time | integer | The value represents the total time for garbage collection operation in millisecond unit. |\n| application_jvm_gc_times                 | integer | The value represents the total garbage collection times.                                  |\n| application_jvm_gc_times_rate            | integer | The number of gc times per second.                                                        |\n\n#### Dedicated label\n| Label Name | Essential |     Value Example     | Description                                                                                                                                                                                                                                                                                                                                          |\n|:-----------|:---------:|:---------------------:|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| resource   |   true    | `G1 Young Generation` | GC name. Different JVM startup arguments will have different GC. Reference: [GC tuning](https://docs.oracle.com/en/java/javase/17/gctuning/introduction-garbage-collection-tuning.html#GUID-326EB4CF-8C8C-4267-8355-21AB04F0D304) , [Platform](https://docs.oracle.com/en/middleware/standalone/coherence/14.1.1.0/rest-reference/api-platform.html) |\n\n\n### Kafka Client\nKafka Client schema describes key metrics of Kafka client invoking, which include:\n* Producer\n  * Throughput (prodrm1, prodrm5, prodrm15)\n  * Error throughput (prodrm1err, prodrm5err, prodrm15err)\n  * Execution duration (prodrmin, prodrmean, prodrmax)\n  * Latency (prodrp25, prodrp50, prodrp75, prodrp95, prodrp98, prodrp99, prodrp999)\n* Consumer\n  * Throughput (consrm1, consrm5, consrm15)\n  * Error throughput (consrm1err, consrm5err, consrm15err)\n  * Execution duration (consrmin, consrmean, consrmax)\n  * Latency (consrp25, consrp50, consrp75, consrp95, consrp98, consrp99, consrp999)\n\n| Metric Name                  |  Type  | Description                                                                                          |\n|:-----------------------------|:------:|:-----------------------------------------------------------------------------------------------------|\n| application_kafka_prodrm1    | double | The executions per second (exponentially-weighted moving average) in last 1 minute (producer)        |\n| application_kafka_prodrm5    | double | The executions per second (exponentially-weighted moving average) in last 5 minute (producer)        |\n| application_kafka_prodrm15   | double | The executions per second (exponentially-weighted moving average) in last 15 minute (producer)       |\n| application_kafka_consrm1    | double | The executions per second (exponentially-weighted moving average) in last 1 minute (consumer)        |\n| application_kafka_consrm5    | double | The executions per second (exponentially-weighted moving average) in last 5 minute (consumer)        |\n| application_kafka_consrm15   | double | The executions per second (exponentially-weighted moving average) in last 15 minute (consumer)       |\n| application_kafka_prodrm1err | double | The error executions per second (exponentially-weighted moving average) in last 1 minute (producer)  |\n| application_kafka_prodrm5err | double | The executions per second (exponentially-weighted moving average) in last 5 minute (producer)        |\n| application_kafka_prodrm5err | double | The error executions per second (exponentially-weighted moving average) in last 15 minute (producer) |\n| application_kafka_consrm1err | double | The error executions per second (exponentially-weighted moving average) in last 1 minute (consumer)  |\n| application_kafka_consrm5err | double | The error executions per second (exponentially-weighted moving average) in last 5 minute (consumer)  |\n| application_kafka_consrm5err | double | The error executions per second (exponentially-weighted moving average) in last 15 minute (consumer) |\n| application_kafka_prodrmin   | double | The minimal execution duration in milliseconds.                                                      |\n| application_kafka_prodrmax   | double | The maximal execution duration in milliseconds.                                                      |\n| application_kafka_prodrmean  | double | The mean execution duration in milliseconds.                                                         |\n| application_kafka_prodrp25   | double | TP25: The execution duration in milliseconds for 25% user.                                           |\n| application_kafka_prodrp50   | double | TP50: The execution duration in milliseconds for 50% user.                                           |\n| application_kafka_prodrp75   | double | TP75: The execution duration in milliseconds for 75% user.                                           |\n| application_kafka_prodrp95   | double | TP95: The execution duration in milliseconds for 95% user.                                           |\n| application_kafka_prodrp98   | double | TP98: The execution duration in milliseconds for 98% user.                                           |\n| application_kafka_prodrp99   | double | TP99: The execution duration in milliseconds for 99% user.                                           |\n| application_kafka_prodrp999  | double | TP99.9: The execution duration in milliseconds for 99.9% user.                                       |\n| application_kafka_consrmin   | double | The minimal execution duration in milliseconds.                                                      |\n| application_kafka_consrmax   | double | The maximal execution duration in milliseconds.                                                      |\n| application_kafka_consrmean  | double | The mean execution duration in milliseconds.                                                         |\n| application_kafka_consrp25   | double | TP25: The execution duration in milliseconds for 25% user.                                           |\n| application_kafka_consrp50   | double | TP50: The execution duration in milliseconds for 50% user.                                           |\n| application_kafka_consrp75   | double | TP75: The execution duration in milliseconds for 75% user.                                           |\n| application_kafka_consrp95   | double | TP95: The execution duration in milliseconds for 95% user.                                           |\n| application_kafka_consrp98   | double | TP98: The execution duration in milliseconds for 98% user.                                           |\n| application_kafka_consrp99   | double | TP99: The execution duration in milliseconds for 99% user.                                           |\n| application_kafka_consrp999  | double | TP99.9: The execution duration in milliseconds for 99.9% user.                                       |\n\n#### Dedicated label\n| Label Name | Essential | Value Example | Description |\n|:-----------|:---------:|:-------------:|:------------|\n| resource   |   true    |     `log`     | topic name  |\n\n\n### RabbitMQ Producer\nRabbitMQ Producer schema describes key metrics of RabbitMQ client publishing message, which include:\n* Throughput (prodrm1, prodrm5, prodrm15)\n* Error throughput (prodrm1err, prodrm5err, prodrm15err)\n* Execution duration (min, mean, max)\n* Latency (p25, p50, p75, p95, p98, p99)\n\n| Metric Name                           |  Type  | Description                                                                                          |\n|:--------------------------------------|:------:|:-----------------------------------------------------------------------------------------------------|\n| application_rabbitmq_ex_ro_prodrm1    | double | The executions of producer per second (exponentially-weighted moving average) in last 1 minute       |\n| application_rabbitmq_ex_ro_prodrm5    | double | The executions of producer per second (exponentially-weighted moving average) in last 5 minute       |\n| application_rabbitmq_ex_ro_prodrm15   | double | The executionsof producer per second (exponentially-weighted moving average) in last 15 minute       |\n| application_rabbitmq_ex_ro_prodrm1err | double | The error executions per second (exponentially-weighted moving average) in last 1 minute (producer)  |\n| application_rabbitmq_ex_ro_prodrm5err | double | The executions per second (exponentially-weighted moving average) in last 5 minute (producer)        |\n| application_rabbitmq_ex_ro_prodrm5err | double | The error executions per second (exponentially-weighted moving average) in last 15 minute (producer) |\n| application_rabbitmq_ex_ro_min        | double | The producer minimal execution duration in milliseconds.                                             |\n| application_rabbitmq_ex_ro_max        | double | The producer maximal execution duration in milliseconds.                                             |\n| application_rabbitmq_ex_ro_mean       | double | The producer mean execution duration in milliseconds.                                                |\n| application_rabbitmq_ex_ro_p25        | double | TP25: The producer execution duration in milliseconds for 25% user.                                  |\n| application_rabbitmq_ex_ro_p50        | double | TP50: The producer execution duration in milliseconds for 50% user.                                  |\n| application_rabbitmq_ex_ro_p75        | double | TP75: The producer execution duration in milliseconds for 75% user.                                  |\n| application_rabbitmq_ex_ro_p95        | double | TP95: The producer execution duration in milliseconds for 95% user.                                  |\n| application_rabbitmq_ex_ro_p98        | double | TP98: The producer execution duration in milliseconds for 98% user.                                  |\n| application_rabbitmq_ex_ro_p99        | double | TP99: The producer execution duration in milliseconds for 99% user.                                  |\n| application_rabbitmq_ex_ro_p999       | double | TP99.9: The execution duration in milliseconds for 99.9% user.                                       |\n\n#### Dedicated label\n| Label Name | Essential |    Value Example     | Description                        |\n|:-----------|:---------:|:--------------------:|:-----------------------------------|\n| resource   |   true    | `myExchange-myQueue` | rabbitmq ${exchange}-${routingkey} |\n\n\n### RabbitMQ Consumer\nRabbitMQ Consumer schema describes key metrics of RabbitMQ client consuming message, which include:\n* Throughput (queue_m1_rate, queue_m5_rate, queue_m15_rate)\n* Error throughput (queue_m1_error_rate, queue_m5_error_rate, queue_m15_error_rate)\n* Execution duration (min, mean, max)\n* Latency (p25, p50, p75, p95, p98, p99)\n\n| Metric Name                                        |  Type  | Description                                                                                       |\n|:---------------------------------------------------|:------:|:--------------------------------------------------------------------------------------------------|\n| application_rabbitmq_consumer_queue_m1_rate        | double | The executions of queue per second (exponentially-weighted moving average) in last 1 minute       |\n| application_rabbitmq_consumer_queue_m5_rate        | double | The executions of queue per second (exponentially-weighted moving average) in last 5 minute       |\n| application_rabbitmq_consumer_queue_m15_rate       | double | The executionsof queue per second (exponentially-weighted moving average) in last 15 minute       |\n| application_rabbitmq_consumer_queue_m1_error_rate  | double | The error executions per second (exponentially-weighted moving average) in last 1 minute (queue)  |\n| application_rabbitmq_consumer_queue_m5_error_rate  | double | The error executions per second (exponentially-weighted moving average) in last 5 minute (queue)  |\n| application_rabbitmq_consumer_queue_m15_error_rate | double | The error executions per second (exponentially-weighted moving average) in last 15 minute (queue) |\n| application_rabbitmq_consumer_min                  | double | The consumer minimal execution duration in milliseconds.                                          |\n| application_rabbitmq_consumer_max                  | double | The consumer maximal execution duration in milliseconds.                                          |\n| application_rabbitmq_consumer_mean                 | double | The consumer mean execution duration in milliseconds.                                             |\n| application_rabbitmq_consumer_p25                  | double | TP25: The consumer execution duration in milliseconds for 25% user.                               |\n| application_rabbitmq_consumer_p50                  | double | TP50: The consumer execution duration in milliseconds for 50% user.                               |\n| application_rabbitmq_consumer_p75                  | double | TP75: The consumer execution duration in milliseconds for 75% user.                               |\n| application_rabbitmq_consumer_p95                  | double | TP95: The consumer execution duration in milliseconds for 95% user.                               |\n| application_rabbitmq_consumer_p98                  | double | TP98: The consumer execution duration in milliseconds for 98% user.                               |\n| application_rabbitmq_consumer_p99                  | double | TP99: The consumer execution duration in milliseconds for 99% user.                               |\n| application_rabbitmq_consumer_p999                 | double | TP99.9: The execution duration in milliseconds for 99.9% user.                                    |\n\n#### Dedicated label\n| Label Name | Essential | Value Example | Description         |\n|:-----------|:---------:|:-------------:|:--------------------|\n| resource   |   true    |   `myQueue`   | rabbitmq routingKey |\n\n\n### Spring AMQP on Message Listener\n\nMessage Listener schema describes key metrics of Spring AMQP RabbitMQ Message Queue, which include:\n* Throughput (queue_m1_rate, queue_m5_rate, queue_m15_rate)\n* Error throughput (queue_m1_error_rate, queue_m5_error_rate, queue_m15_error_rate)\n* Execution duration (min, mean, max)\n* Latency (p25, p50, p75, p95, p98, p99)\n\n| Metric Name                                     |  Type  | Description                                                                                       |\n|:------------------------------------------------|:------:|:--------------------------------------------------------------------------------------------------|\n| application_rabbitmq_queue_queue_m1_rate        | double | The executions of queue per second (exponentially-weighted moving average) in last 1 minute       |\n| application_rabbitmq_queue_queue_m5_rate        | double | The executions of queue per second (exponentially-weighted moving average) in last 5 minute       |\n| application_rabbitmq_queue_queue_m15_rate       | double | The executionsof queue per second (exponentially-weighted moving average) in last 15 minute       |\n| application_rabbitmq_queue_queue_m1_error_rate  | double | The error executions per second (exponentially-weighted moving average) in last 1 minute (queue)  |\n| application_rabbitmq_queue_queue_m5_error_rate  | double | The error executions per second (exponentially-weighted moving average) in last 5 minute (queue)  |\n| application_rabbitmq_queue_queue_m15_error_rate | double | The error executions per second (exponentially-weighted moving average) in last 15 minute (queue) |\n| application_rabbitmq_queue_min                  | double | The AMQP Message Listener minimal execution duration in milliseconds.                             |\n| application_rabbitmq_queue_max                  | double | The AMQP Message Listener maximal execution duration in milliseconds.                             |\n| application_rabbitmq_queue_mean                 | double | The AMQP Message Listener mean execution duration in milliseconds.                                |\n| application_rabbitmq_queue_p25                  | double | TP25: The AMQP Message Listener execution duration in milliseconds for 25% user.                  |\n| application_rabbitmq_queue_p50                  | double | TP50: The AMQP Message Listener execution duration in milliseconds for 50% user.                  |\n| application_rabbitmq_queue_p75                  | double | TP75: The AMQP Message Listener execution duration in milliseconds for 75% user.                  |\n| application_rabbitmq_queue_p95                  | double | TP95: The AMQP Message Listener execution duration in milliseconds for 95% user.                  |\n| application_rabbitmq_queue_p98                  | double | TP98: The AMQP Message Listener execution duration in milliseconds for 98% user.                  |\n| application_rabbitmq_queue_p99                  | double | TP99: The AMQP Message Listener execution duration in milliseconds for 99% user.                  |\n| application_rabbitmq_queue_p999                 | double | TP99.9: The execution duration in milliseconds for 99.9% user.                                    |\n\n#### Dedicated label\n| Label Name | Essential | Value Example | Description    |\n|:-----------|:---------:|:-------------:|:---------------|\n| resource   |   true    |   `myQueue`   | rabbitmq queue |\n\n\n### Elasticsearch\nElasticsearch schema describes key metrics of Elasticsearch client invoking, which include:\n* Total execution count (cnt, errcnt, m1cnt, m5cnt, m15cnt)\n* Throughput (m1, m5, m15, mean_rate)\n* Error throughput (m1err, m5err, m15err)\n* Execution duration (min, mean, max)\n* Latency (p25, p50, p75, p95, p98, p99)\n\n| Metric Name                         |  Type   | Description                                                                                                     |\n|:------------------------------------|:-------:|:----------------------------------------------------------------------------------------------------------------|\n| application_elasticsearch_cnt       | integer | The total count of the request executed                                                                         |\n| application_elasticsearch_errcnt    | integer | The total error count of the request executed                                                                   |\n| application_elasticsearch_m1cnt     | integer | The total count of the request executed in last 1 minute                                                        |\n| application_elasticsearch_m5cnt     | integer | The total count of the request executed in last 5 minute                                                        |\n| application_elasticsearch_m15cnt    | integer | The total count of the request executed in last 15 minute                                                       |\n| application_elasticsearch_m1        | double  | The Elasticsearch request executions per second (exponentially-weighted moving average) in last 1 minute        |\n| application_elasticsearch_m5        | double  | The Elasticsearch request executions per second (exponentially-weighted moving average) in last 5 minute.       |\n| application_elasticsearch_m15       | double  | The Elasticsearch request executions per second (exponentially-weighted moving average) in last 15 minute.      |\n| application_elasticsearch_mean_rate | double  | The Elasticsearch request executions per second (exponentially-weighted moving average) in last 15 minute.      |\n| application_elasticsearch_m1err     | double  | The Elasticsearch error request executions per second (exponentially-weighted moving average) in last 1 minute  |\n| application_elasticsearch_m5err     | double  | The Elasticsearch error request executions per second (exponentially-weighted moving average) in last 5 minute. |\n| application_elasticsearch_m15err    | double  | The Elasticsearch error request executions per second (exponentially-weighted moving average) in last 15 minute |\n| application_elasticsearch_min       | double  | The Elasticsearch minimal execution duration in milliseconds.                                                   |\n| application_elasticsearch_max       | double  | The Elasticsearch maximal execution duration in milliseconds.                                                   |\n| application_elasticsearch_mean      | double  | The Elasticsearch mean execution duration in milliseconds.                                                      |\n| application_elasticsearch_p25       | double  | TP25: The Elasticsearch execution duration in milliseconds for 25% user.                                        |\n| application_elasticsearch_p50       | double  | TP50: The Elasticsearch execution duration in milliseconds for 50% user.                                        |\n| application_elasticsearch_p75       | double  | TP75: The Elasticsearch execution duration in milliseconds for 75% user.                                        |\n| application_elasticsearch_p95       | double  | TP95: The Elasticsearch execution duration in milliseconds for 95% user.                                        |\n| application_elasticsearch_p98       | double  | TP98: The Elasticsearch execution duration in milliseconds for 98% user.                                        |\n| application_elasticsearch_p99       | double  | TP99: The Elasticsearch execution duration in milliseconds for 99% user.                                        |\n\n#### Dedicated label\n| Label Name | Essential |      Value Example       | Description                  |\n|:-----------|:---------:|:------------------------:|:-----------------------------|\n| index      |   true    | `log-tracing-2022.01.11` | The Elasticsearch index name |\n\n\n### MongoDB\nMongoDB schema describes key metrics of MongoDB client invoking, which include:\n* Total execution count (cnt, errcnt, m1cnt, m5cnt, m15cnt)\n* Throughput (m1, m5, m15, mean_rate)\n* Error throughput (m1err, m5err, m15err)\n* Execution duration (min, mean, max)\n* Latency (p25, p50, p75, p95, p98, p99)\n\n| Metric Name                         |  Type   | Description                                                                                               |\n|:------------------------------------|:-------:|:----------------------------------------------------------------------------------------------------------|\n| application_mongodbclient_cnt       | integer | The total count of the request executed                                                                   |\n| application_mongodbclient_errcnt    | integer | The total error count of the request executed                                                             |\n| application_mongodbclient_m1cnt     | integer | The total count of the request executed in last 1 minute                                                  |\n| application_mongodbclient_m5cnt     | integer | The total count of the request executed in last 5 minute                                                  |\n| application_mongodbclient_m15cnt    | integer | The total count of the request executed in last 15 minute                                                 |\n| application_mongodbclient_m1        | double  | The MongoDB request executions per second (exponentially-weighted moving average) in last 1 minute        |\n| application_mongodbclient_m5        | double  | The MongoDB request executions per second (exponentially-weighted moving average) in last 5 minute.       |\n| application_mongodbclient_m15       | double  | The MongoDB request executions per second (exponentially-weighted moving average) in last 15 minute.      |\n| application_mongodbclient_mean_rate | double  | The MongoDB request executions per second (exponentially-weighted moving average) in last 15 minute.      |\n| application_mongodbclient_m1err     | double  | The MongoDB error request executions per second (exponentially-weighted moving average) in last 1 minute  |\n| application_mongodbclient_m5err     | double  | The MongoDB error request executions per second (exponentially-weighted moving average) in last 5 minute. |\n| application_mongodbclient_m15err    | double  | The MongoDB error request executions per second (exponentially-weighted moving average) in last 15 minute |\n| application_mongodbclient_min       | double  | The MongoDB minimal execution duration in milliseconds.                                                   |\n| application_mongodbclient_max       | double  | The MongoDB maximal execution duration in milliseconds.                                                   |\n| application_mongodbclient_mean      | double  | The MongoDB mean execution duration in milliseconds.                                                      |\n| application_mongodbclient_p25       | double  | TP25: The MongoDB execution duration in milliseconds for 25% user.                                        |\n| application_mongodbclient_p50       | double  | TP50: The MongoDB execution duration in milliseconds for 50% user.                                        |\n| application_mongodbclient_p75       | double  | TP75: The MongoDB execution duration in milliseconds for 75% user.                                        |\n| application_mongodbclient_p95       | double  | TP95: The MongoDB execution duration in milliseconds for 95% user.                                        |\n| application_mongodbclient_p98       | double  | TP98: The MongoDB execution duration in milliseconds for 98% user.                                        |\n| application_mongodbclient_p99       | double  | TP99: The MongoDB execution duration in milliseconds for 99% user.                                        |\n\n#### Dedicated label\n| Label Name | Essential | Value Example | Description                                                                                                                            |\n|:-----------|:---------:|:-------------:|:---------------------------------------------------------------------------------------------------------------------------------------|\n| operation  |   true    |   `insert`    | The MongoDB request command name: insert, update or find etc. Reference: [Command](https://docs.mongodb.com/manual/reference/command/) |\n\n\n### Motan\nMotan schema describes key metrics of Motan client invoking, which include:\n* Total execution count (cnt, errcnt, m1cnt, m5cnt, m15cnt)\n* Throughput (m1, m5, m15, mean_rate)\n* Error throughput (m1err, m5err, m15err)\n* Execution duration (min, mean, max)\n* Latency (p25, p50, p75, p95, p98, p99, p999)\n\n| Metric Name                         |  Type   | Description                                                                                               |\n|:------------------------------------| :-----: |:----------------------------------------------------------------------------------------------------------|\n| application_motan_cnt               | integer | The total count of the Motan method executed                                                              |\n| application_motan_errcnt            | integer | The total error count of the Motan method executed                                                        |\n| application_motan_m1cnt             | integer | The total count of the Motan method executed in last 1 minute                                             |\n| application_motan_m5cnt             | integer | The total count of the Motan method executed in last 5 minute                                             |\n| application_motan_m15cnt            | integer | The total count of the Motan method executed in last 15 minute                                            |\n| application_motan_m1                | double  | The Motan method executions per second (exponentially-weighted moving average) in last 1 minute           |\n| application_motan_m5                | double  | The Motan method executions per second (exponentially-weighted moving average) in last 5 minute.          |\n| application_motan_m15               | double  | The Motan method executions per second (exponentially-weighted moving average) in last 15 minute.         |\n| application_motan_mean_rate         | double  | The Motan method executions per second (exponentially-weighted moving average) in last 15 minute.         |\n| application_motan_m1err             | double  | The Motan method error executions per second (exponentially-weighted moving average) in last 1 minute     |\n| application_motan_m5err             | double  | The Motan method error executions per second (exponentially-weighted moving average) in last 5 minute.    |\n| application_motan_m15err            | double  | The Motan method error executions per second (exponentially-weighted moving average) in last 15 minute    |\n| application_motan_min               | double  | The Motan method minimal execution duration in milliseconds.                                              |\n| application_motan_max               | double  | The Motan method maximal execution duration in milliseconds.                                              |\n| application_motan_mean              | double  | The Motan method mean execution duration in milliseconds.                                                 |\n| application_motan_p25               | double  | TP25: The Motan method execution duration in milliseconds for 25% user.                                   |\n| application_motan_p50               | double  | TP50: The Motan method execution duration in milliseconds for 50% user.                                   |\n| application_motan_p75               | double  | TP75: The Motan method execution duration in milliseconds for 75% user.                                   |\n| application_motan_p95               | double  | TP95: The Motan method execution duration in milliseconds for 95% user.                                   |\n| application_motan_p98               | double  | TP98: The Motan method execution duration in milliseconds for 98% user.                                   |\n| application_motan_p99               | double  | TP99: The Motan method execution duration in milliseconds for 99% user.                                   |\n| application_motan_p999              | double  | TP999: The Motan method execution duration in milliseconds for 99.9% user.                                |\n\n#### Dedicated label\n| Label Name | Essential |                        Value Example                         | Description                                                                                                                                                            |\n|:-----------|:---------:|:------------------------------------------------------------:|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| interface  |   true    | `com.megaease.easeagent.motan.api.TestService/sayHi(String)` | The full signature of the `Motan` call interface, like this `com.megaease.easyagent.motan.api.TestService/sayHi(String)`. [Details](./user-manual.md#tracing-and-metric) |\n\n\n### Dubbo\nDubbo schema describes key metrics of Dubbo client invoking, which include:\n* Total execution count (cnt, errcnt, m1cnt, m5cnt, m15cnt)\n* Throughput (m1, m5, m15, mean_rate)\n* Error throughput (m1err, m5err, m15err)\n* Execution duration (min, mean, max)\n* Latency (p25, p50, p75, p95, p98, p99, p999)\n\n| Metric Name                         |  Type   | Description                                                                                               |\n|:------------------------------------| :-----: |:----------------------------------------------------------------------------------------------------------|\n| application_dubbo_cnt               | integer | The total count of the Dubbo method executed                                                              |\n| application_dubbo_errcnt            | integer | The total error count of the Dubbo method executed                                                        |\n| application_dubbo_m1cnt             | integer | The total count of the Dubbo method executed in last 1 minute                                             |\n| application_dubbo_m5cnt             | integer | The total count of the Dubbo method executed in last 5 minute                                             |\n| application_dubbo_m15cnt            | integer | The total count of the Dubbo method executed in last 15 minute                                            |\n| application_dubbo_m1                | double  | The Dubbo method executions per second (exponentially-weighted moving average) in last 1 minute           |\n| application_dubbo_m5                | double  | The Dubbo method executions per second (exponentially-weighted moving average) in last 5 minute.          |\n| application_dubbo_m15               | double  | The Dubbo method executions per second (exponentially-weighted moving average) in last 15 minute.         |\n| application_dubbo_mean_rate         | double  | The Dubbo method executions per second (exponentially-weighted moving average) in last 15 minute.         |\n| application_dubbo_m1err             | double  | The Dubbo method error executions per second (exponentially-weighted moving average) in last 1 minute     |\n| application_dubbo_m5err             | double  | The Dubbo method error executions per second (exponentially-weighted moving average) in last 5 minute.    |\n| application_dubbo_m15err            | double  | The Dubbo method error executions per second (exponentially-weighted moving average) in last 15 minute    |\n| application_dubbo_min               | double  | The Dubbo method minimal execution duration in milliseconds.                                              |\n| application_dubbo_max               | double  | The Dubbo method maximal execution duration in milliseconds.                                              |\n| application_dubbo_mean              | double  | The Dubbo method mean execution duration in milliseconds.                                                 |\n| application_dubbo_p25               | double  | TP25: The Dubbo method execution duration in milliseconds for 25% user.                                   |\n| application_dubbo_p50               | double  | TP50: The Dubbo method execution duration in milliseconds for 50% user.                                   |\n| application_dubbo_p75               | double  | TP75: The Dubbo method execution duration in milliseconds for 75% user.                                   |\n| application_dubbo_p95               | double  | TP95: The Dubbo method execution duration in milliseconds for 95% user.                                   |\n| application_dubbo_p98               | double  | TP98: The Dubbo method execution duration in milliseconds for 98% user.                                   |\n| application_dubbo_p99               | double  | TP99: The Dubbo method execution duration in milliseconds for 99% user.                                   |\n| application_dubbo_p999              | double  | TP999: The Dubbo method execution duration in milliseconds for 99.9% user.                                |\n\n#### Dedicated label\n| Label Name | Essential |                        Value Example                         | Description                                                                                                                                                              |\n|:-----------|:---------:|:------------------------------------------------------------:|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| interface  |   true    | `com.megaease.easeagent.dubbo.api.TestService/sayHi(String)` | The full signature of the `Dubbo` call interface, like this `com.megaease.easyagent.dubbo.api.TestService/sayHi(String)`. [Details](./user-manual.md#tracing-and-metric) |\n\n\n### SOFARPC\nSOFARPC schema describes key metrics of SOFARPC client invoking, which include:\n* Total execution count (cnt, errcnt, m1cnt, m5cnt, m15cnt)\n* Throughput (m1, m5, m15, mean_rate)\n* Error throughput (m1err, m5err, m15err)\n* Execution duration (min, mean, max)\n* Latency (p25, p50, p75, p95, p98, p99, p999)\n\n| Metric Name                         |  Type   | Description                                                                                              |\n|:------------------------------------| :-----: |:---------------------------------------------------------------------------------------------------------|\n| application_sofarpc_cnt               | integer | The total count of the SOFARPC method executed                                                           |\n| application_sofarpc_errcnt            | integer | The total error count of the SOFARPC method executed                                                     |\n| application_sofarpc_m1cnt             | integer | The total count of the SOFARPC method executed in last 1 minute                                          |\n| application_sofarpc_m5cnt             | integer | The total count of the SOFARPC method executed in last 5 minute                                          |\n| application_sofarpc_m15cnt            | integer | The total count of the SOFARPC method executed in last 15 minute                                         |\n| application_sofarpc_m1                | double  | The SOFARPC method executions per second (exponentially-weighted moving average) in last 1 minute        |\n| application_sofarpc_m5                | double  | The SOFARPC method executions per second (exponentially-weighted moving average) in last 5 minute.       |\n| application_sofarpc_m15               | double  | The SOFARPC method executions per second (exponentially-weighted moving average) in last 15 minute.      |\n| application_sofarpc_mean_rate         | double  | The SOFARPC method executions per second (exponentially-weighted moving average) in last 15 minute.      |\n| application_sofarpc_m1err             | double  | The SOFARPC method error executions per second (exponentially-weighted moving average) in last 1 minute  |\n| application_sofarpc_m5err             | double  | The SOFARPC method error executions per second (exponentially-weighted moving average) in last 5 minute. |\n| application_sofarpc_m15err            | double  | The SOFARPC method error executions per second (exponentially-weighted moving average) in last 15 minute |\n| application_sofarpc_min               | double  | The SOFARPC method minimal execution duration in milliseconds.                                           |\n| application_sofarpc_max               | double  | The SOFARPC method maximal execution duration in milliseconds.                                           |\n| application_sofarpc_mean              | double  | The SOFARPC method mean execution duration in milliseconds.                                              |\n| application_sofarpc_p25               | double  | TP25: The SOFARPC method execution duration in milliseconds for 25% user.                      |\n| application_sofarpc_p50               | double  | TP50: The SOFARPC method execution duration in milliseconds for 50% user.                      |\n| application_sofarpc_p75               | double  | TP75: The SOFARPC method execution duration in milliseconds for 75% user.                                |\n| application_sofarpc_p95               | double  | TP95: The SOFARPC method execution duration in milliseconds for 95% user.                                |\n| application_sofarpc_p98               | double  | TP98: The SOFARPC method execution duration in milliseconds for 98% user.                                |\n| application_sofarpc_p99               | double  | TP99: The SOFARPC method execution duration in milliseconds for 99% user.                                |\n| application_sofarpc_p999              | double  | TP999: The SOFARPC method execution duration in milliseconds for 99.9% user.                             |\n\n#### Dedicated label\n| Label Name | Essential |                        Value Example                         | Description                                                                                                                                                                  |\n|:-----------|:---------:|:------------------------------------------------------------:|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| interface  |   true    | `com.megaease.easeagent.sofarpc.api.TestService/sayHi(String)` | The full signature of the `SOFARPC` call interface, like this `com.megaease.easyagent.sofarpc.api.TestService/sayHi(String)`. [Details](./user-manual.md#tracing-and-metric) |\n"
  },
  {
    "path": "doc/report-development-guide.md",
    "content": "# \nToDo...\n"
  },
  {
    "path": "doc/spring-boot-3.x.x-demo.md",
    "content": "# Spring Boot 3.5.3 Demo\n\nYou need Java 17+, Maven 3.9.10 and easeagent:\n\nIf you don't already have easeagent, get it and set $EASE_AGENT_PATH: [EaseAgent](../README.md#get-and-set-environment-variable)\n\n## 1. Building the application.\nThere is a [demo](https://github.com/megaease/easeagent-spring-boot-demo-3.5.3) which is spring web and client\n```\n$ git clone https://github.com/megaease/easeagent-spring-boot-demo-3.5.3.git\n$ cd easeagent-spring-boot-demo-3.5.3\n$ mvn clean package -Dmaven.test.skip\n```\n## 2. Add code version to config\n\nadd two configurations for the `${EASE_AGENT_PATH}/agent.properties` to take effect:\n```properties\nruntime.code.version.points.jdk=jdk17\nruntime.code.version.points.spring-boot=3.x.x\n```\n\n## 3. Run the demo application with EaseAgent.\n```\n# Open another console\n$ export EASE_AGENT_PATH=[Replace with agent path]\n$ java \"-javaagent:${EASE_AGENT_PATH}/easeagent-dep.jar\" -Deaseagent.config.path=${EASE_AGENT_PATH}/agent.properties -Deaseagent.server.port=9900 -jar spring-web/target/spring-web-0.0.1-SNAPSHOT.jar\n\n```\n\nOpen another console, run curl to access the test url for several times.\n\n```\n$ for i in {1..1000}; do curl -v http://127.0.0.1:18889/web_client;sleep 0.1; done\n```\n\n## 4. How to verify it？\n\n### * Tracing\n\nIf the tracing data is send to console, there would be some tracing log in console like this:\n```\n[{\"traceId\":\"5a8800b902703307\",\"parentId\":\"84c4cba42fb92788\",\"id\":\"fd00a1705c88cbb2\",\"kind\":\"SERVER\",\"name\":\"get\",\"timestamp\":1639493283759877,\"duration\":217545,\"shared\":true,\"localEndpoint\":{\"serviceName\":\"demo-service\",\"ipv4\":\"192.168.0.102\"},\"remoteEndpoint\":{\"ipv4\":\"127.0.0.1\",\"port\":55809},\"tags\":{\"http.method\":\"GET\",\"http.path\":\"/hello\",\"http.route\":\"/hello\",\"i\":\"ServerName.local\"},\"type\":\"log-tracing\",\"service\":\"demo-service\",\"system\":\"demo-system\"},{\"traceId\":\"5a8800b902703307\",\"id\":\"5a8800b902703307\",\"kind\":\"SERVER\",\"name\":\"get\",\"timestamp\":1639493283753466,\"duration\":228827,\"localEndpoint\":{\"serviceName\":\"demo-service\",\"ipv4\":\"192.168.0.102\"},\"remoteEndpoint\":{\"ipv4\":\"127.0.0.1\",\"port\":55851},\"tags\":{\"http.method\":\"GET\",\"http.path\":\"/web_client\",\"i\":\"ServerName.local\"},\"type\":\"log-tracing\",\"service\":\"demo-service\",\"system\":\"demo-system\"}]\n...\n```\n\n### * Metric\n\n#### Integrate with Prometheus\n\nAdding the following configuration in `prometheus.yml`\n```yaml\n  - job_name: 'spring-web-service'\n    static_configs:\n      - targets: ['localhost:9900']\n    metrics_path: \"/prometheus/metrics\"\n```\n\nStart Prometheus\n```bash\n$ ./prometheus --config.file=prometheus.yml\n```\n\nOpen Browser to visit [http://localhost:9090](http://localhost:9090).\n\nPrometheus Metric Schedule: [Prometheus Metric](./prometheus-metric-schedule.md)\n\nsearch `application_http_request_m1{url=\"GET /web_client\"}`. You will see as following.\n\n![image](./images/prometheus-demo.jpg)\n\n## Configuration\n\n* Modify service name, default configuration is `demo-service`.\n```\nname=[app-name]\n```\n* Modify kafka server config, default configuration is `127.0.0.1:9092`.\n  Both `tracing` data and `metric` data will be send to kafka server by default configuration.\n```\n#reporter.outputServer.bootstrapServer=[ip:port]\n```\n\n* Modify output configuration, if you want to watch log information in console.\n```\n# metric output\nplugin.observability.global.metric.appendType=console\n\n# tracings output to console\nreporter.tracing.sender.appendType=console\n```\n\n* Sending tracing data to zipkin server\n```\n# [zipkin]: send data to zipkin server\n# [system]: send data to kafka\nreporter.tracing.sender.appendType=http\nreporter.tracing.sender.url=http://localhost:9411/api/v2/spans\n```\n"
  },
  {
    "path": "doc/spring-boot-upgrade.md",
    "content": "# Spring Boot upgrade\n\n## Background\n\nDifferent versions of spring-boot may use different technologies, depend on different jar packages, and implement\ndifferent methods.\n\nFor example, in spring-boot 2 and spring-boot 3, the httpserver of spring-boot 2 uses httpservlet, while the httpserver\nof spring-boot 3 uses tomcat.\n\nSome classes or methods have been deprecated after upgrading to spring-boot 3, and the old sampling entry points will\nalso be deprecated.\n\nTherefore, different entry points and different sampling implementations are required according to different code\nversions.\n\n## Plugin Dependencies\n\n### jdk dependencies\n\n| spring-boot 2.x | spring-boot 3.x | \n|:----------------|:----------------|\n| jdk8            | jdk17           |\n\n### plugin dependencies\n\n| plugin name       | spring-boot 2.x jar | spring-boot 3.x jar                               | \n|:------------------|:--------------------|:--------------------------------------------------|\n| httpURLConnection | httpurlconnection   | httpurlconnection-jdk17                           |\n| httpServlet       | httpservlet         |                                                   |\n| tomcat            |                     | tomcat-jdk17                                      |\n| springGateway     | spring-gateway      | spring-boot-3.5.3/spring-boot-gateway-3.5.3       |\n| resTemplate       | springweb           | spring-boot-3.5.3/spring-boot-rest-template-3.5.3 |\n| serviceName       | servicename         | spring-boot-3.5.3/spring-boot-servicename-3.5.3   |\n\n## Base config\n\nWhen your code uses Spring Boot 3.x.x, it means that your code depends on JDK 17+ and Spring Boot 3+.\n\nIn this case, you need to add two configurations for the agent to take effect:\n\n```properties\nruntime.code.version.points.jdk=jdk17\nruntime.code.version.points.spring-boot=3.x.x\n```\n\n## Change Tag\nrelease and change message: [Release-v2.3.0](https://github.com/megaease/easeagent/releases/tag/v2.3.0) \n\n## Features\nEasy to upgrade. If you upgrade your spring-boot version in the future, you only need to add the missing plugins.\n\nIf HTTP Sever is changed from tomcat to jetty, add the jetty plugin.\n\nIf there are incompatible methods in the plug-in below spring-boot-3.5.3, you only need to copy spring-boot-3.5.3 to a higher version, find the incompatible class or method, and implement the sampling logic.\n\nAdd different versions of development methods: [development-guide code version](./development-guide.md#code-version)\n\n\n"
  },
  {
    "path": "doc/spring-petclinic-demo.md",
    "content": "# Spring Petclinic Demo\n\nYou need Java 1.8+ and easeagent:\n\nIf you haven't `easeagent`, get it and set $EASE_AGENT_PATH: [EaseAgent](../README.md#get-and-set-environment-variable)\n\n### Step 1\nBuilding the demo application.\n```\n$ git clone https://github.com/akwei/spring-petclinic-microservices.git\n$ cd spring-petclinic-microservices\n$ mvn -DskipTests=true package\n```\n\n### Step 2\nRun the demo application with EaseAgent.\n```\n# Run Spring Cloud Config Server\n$ java -jar spring-petclinic-config-server/target/spring-petclinic-config-server-2.4.2.jar\n\n# Run Spring Cloud Service Discovery Server\n$ java -jar spring-petclinic-discovery-server/target/spring-petclinic-discovery-server-2.4.2.jar\n\n# Run Spring Cloud Application - Vets Service\n$ export EASE_AGENT_PATH=[Replace with agent path]\n$ cp $EASE_AGENT_PATH/agent.properties $EASE_AGENT_PATH/vets-agent.properties\n$ # vi $EASE_AGENT_PATH/vets-agent.properties replease name to \"petclinic-vets-service\"\n$ java \"-javaagent:${EASE_AGENT_PATH}/easeagent.jar\" -Deaseagent.config.path=${EASE_AGENT_PATH}/vets-agent.properties -Deaseagent.server.port=9900 -jar spring-petclinic-vets-service/target/spring-petclinic-vets-service-2.4.2.jar\n\n# Run Spring Cloud Application - Visits Service\n$ export EASE_AGENT_PATH=[Replace with agent path]\n$ cp $EASE_AGENT_PATH/agent.properties $EASE_AGENT_PATH/visits-agent.properties\n$ vi $EASE_AGENT_PATH/visits-agent.properties # replease name to \"petclinic-visits-service\"\n$ java \"-javaagent:${EASE_AGENT_PATH}/easeagent.jar\" -Deaseagent.config.path=${EASE_AGENT_PATH}/visits-agent.properties -Deaseagent.server.port=9901 -jar spring-petclinic-visits-service/target/spring-petclinic-visits-service-2.4.2.jar\n\n# Run Spring Cloud Application - Customers Service\n$ export EASE_AGENT_PATH=/[Replace with agent path]\n$ cp $EASE_AGENT_PATH/agent.properties $EASE_AGENT_PATH/customers-agent.properties\n$ vi $EASE_AGENT_PATH/customers-agent.properties # replease name to \"petclinic-customers-service\"\n$ java \"-javaagent:${EASE_AGENT_PATH}/easeagent.jar\" -Deaseagent.config.path=${EASE_AGENT_PATH}/customers-agent.properties -Deaseagent.server.port=9902 -jar spring-petclinic-customers-service/target/spring-petclinic-customers-service-2.4.2.jar\n\n# Run Spring Cloud API Gateway\n$ export EASE_AGENT_PATH=[Replace with agent path]\n$ cp $EASE_AGENT_PATH/agent.properties $EASE_AGENT_PATH/api-gateway-agent.properties\n$ vi $EASE_AGENT_PATH/api-gateway-agent.properties # replease name to \"petclinic-api-gateway\"\n$ java \"-javaagent:${EASE_AGENT_PATH}/easeagent.jar\" -Deaseagent.config.path=${EASE_AGENT_PATH}/api-gateway-agent.properties -Deaseagent.server.port=9903 -jar spring-petclinic-api-gateway/target/spring-petclinic-api-gateway-2.4.2.jar\n\n```\n\n### Step 3\nAdding the following configuration in `prometheus.yml`\n```\n  - job_name: 'petclinic-vets-service'\n    static_configs:\n    - targets: ['localhost:9900']\n    metrics_path: \"/prometheus/metrics\"\n\n  - job_name: 'petclinic-visits-service'\n    static_configs:\n    - targets: ['localhost:9901']\n    metrics_path: \"/prometheus/metrics\"\n\n  - job_name: 'petclinic-customers-service'\n    static_configs:\n    - targets: ['localhost:9902']\n    metrics_path: \"/prometheus/metrics\"\n\n  - job_name: 'petclinic-api-gateway'\n    static_configs:\n    - targets: ['localhost:9903']\n    metrics_path: \"/prometheus/metrics\"\n\n```\nStart Prometheus\n```\n$ ./prometheus --config.file=prometheus.yml\n```\n\n### Step 4\nOpen Browser to visit [http://localhost:8080](http://localhost:8080).\n\nAfter visit more pages, open Prometheus manager [http://localhost:9090](http://localhost:9090), and search `application_http_request_cnt`. You will see as following.\n\n![image](./images/prometheus-petclinic-demo.png)\n"
  },
  {
    "path": "doc/tracing-api.md",
    "content": "#  Tracing API\n\nIn Easeagent, Tracing is inspired by Dapper, using Zipkin as the implementation, but Dapper is only the basis of Tracing.\n\nIn order to be applicable to the business, we additionally encapsulate and define a dedicated interface.\n\nThese interfaces are related to the context, so we put all the newly defined interfaces together with the context.\n\n\n\nAccording to the information transmission method, the Tracing interface is divided into four types:\n\n1. Cross-thread Tracing: async\n\n    ![image](images/Cross-thread-tracing-UML.png)\n\n2. Cross-server Tracing: Server And Client\n\n    ![image](images/Cross-server-tracing-UML.png)\n\n3. Message Tracing: Producer and Consumer\n\n    ![image](images/MessageTracing-UML.png)\n\n4. Span\n\n    ![image](images/Span-tracing-UML.png)\n\n\n[com.megaease.easeagent.plugin.api.Context](../plugin-api/src/main/java/com/megaease/easeagent/plugin/api/Context.java)\n\n```java\ninterface Context{\n    //---------------------------------- 1. Cross-thread ------------------------------------------\n    // When you import and export the AsyncContext, you will also import and export the Tracing context for Thread.\n    // go to Context, see : \n    //     AsyncContext exportAsync(); \n    //     Cleaner importAsync(AsyncContext snapshot);\n    //     Runnable wrap(Runnable task); \n    //     boolean isWrapped(Runnable task);\n\n\n    //----------------------------------2. Cross-server ------------------------------------------\n\n    /**\n     * Create a RequestContext for the next Server\n     * It will pass multiple key:value values required by Trace and EaseAgent through\n     * {@link Request#setHeader(String, String)}, And set the Span's kind, name and\n     * cached scope through {@link Request#kind()}, {@link Request#name()} and {@link Request#cacheScope()}.\n     * <p>\n     * When you want to call the next Server, you can pass the necessary key:value to the next Server\n     * by implementing {@link Request#setHeader(String, String)}, or you can get the {@link RequestContext} of return,\n     * call {@link RequestContext#getHeaders()} to get it and pass it on.\n     * <p>\n     * It is usually called on the client request when collaboration between multiple server is required.\n     * {@code client.clientRequest(Request.setHeader<spanId,root-source...>) --> server }\n     * or\n     * {@code client.clientRequest(Request).getHeaders<spanId,root-source...> --> server }\n     * <p>\n     * The Scope must be close after plugin:\n     *\n     * <pre>{@code\n     *    void after(...){\n     *       RequestContext rc = context.get(...)\n     *       try{\n     *\n     *       }finally{\n     *           rc.scope().close();\n     *       }\n     *    }\n     * }</pre>\n     *\n     * @param request {@link Request}\n     * @return {@link RequestContext}\n     */\n    RequestContext clientRequest(Request request);\n\n\n    /**\n     * Obtain key:value from the request passed by a parent Server and create a RequestContext\n     * <p>\n     * It will not only obtain the key:value required by Trace from the {@link Request#header(String)},\n     * but also other necessary key:value of EaseAgent, such as the key configured in the configuration file:\n     * {@link ProgressFields#EASEAGENT_PROGRESS_FORWARDED_HEADERS_CONFIG}\n     * <p>\n     * If there is no Tracing Header, it will create a Root Span\n     * <p>\n     * It will set the Span's kind, name and cached scope through {@link Request#kind()}, {@link Request#name()}\n     * and {@link Request#cacheScope()}.\n     * <p>\n     * It is usually called on the server receives a request when collaboration between multiple server is required.\n     * {@code client --> server.serverReceive(Request<spanId,root-source...>) }\n     *\n     * The Scope must be close after plugin:\n     *\n     * <pre>{@code\n     *    void after(...){\n     *       RequestContext rc = context.get(...)\n     *       try{\n     *\n     *       }finally{\n     *           rc.scope().close();\n     *       }\n     *    }\n     * }</pre>\n     *\n     * @param request {@link Request}\n     * @return {@link RequestContext}\n     */\n    RequestContext serverReceive(Request request);\n\n    //---------------------------------- 3. Message Tracing ------------------------------------------\n    /**\n     * Obtain key:value from the message request and create a Span, Examples: kafka consumer, rabbitMq consumer\n     * <p>\n     * It will set the Span's kind, name and cached scope through {@link Request#kind()}, {@link Request#name()}\n     * and {@link Request#cacheScope()}.\n     *\n     * <p>\n     * It will set the Span's tags \"messaging.operation\", \"messaging.channel_kind\" and \"messaging.channel_name\" from request\n     * {@link MessagingRequest#operation()} {@link MessagingRequest#channelKind()} {@link MessagingRequest#channelName()}\n     *\n     * <p>\n     * It is usually called on the consumer.\n     * {@code Kafka Server --> consumer.consumerSpan(Record<spanId,X-EG-Circuit-Breaker...>) }\n     *\n     * @param request {@link MessagingRequest}\n     * @return {@link Span}\n     */\n    Span consumerSpan(MessagingRequest request);\n\n\n    /**\n     * Create a Span for message producer. Examples: kafka producer, rabbitMq producer\n     * <p>\n     * It will set the Span's tags \"messaging.operation\", \"messaging.channel_kind\", \"messaging.channel_name\" from request\n     * {@link MessagingRequest#operation()} {@link MessagingRequest#channelKind()} {@link MessagingRequest#channelName()}\n     * And set the Span's kind, name and cached scope through {@link Request#kind()}, {@link Request#name()} and\n     * {@link Request#cacheScope()}.\n     *\n     * <p>\n     * It will not only pass multiple key:value values required by Trace through {@link Request#setHeader(String, String)},\n     * but also other necessary key:value of EaseAgent, such as the key configured in the configuration file:\n     * {@link ProgressFields#EASEAGENT_PROGRESS_FORWARDED_HEADERS_CONFIG}\n     * <p>\n     * <p>\n     * It is usually called on the producer.\n     * {@code producer.producerSpan(Record) -- Record<spanId,root-source...> --> Message Server}\n     *\n     * @param request {@link MessagingRequest}\n     * @return {@link Span}\n     */\n    Span producerSpan(MessagingRequest request);\n\n    /**\n     * Inject Consumer's Span key:value and Forwarded Headers to Request {@link MessagingRequest#setHeader(String, String)}.\n     *\n     * @param span    key:value from\n     * @param request key:value to\n     * @see Request#setHeader(String, String)\n     */\n    void consumerInject(Span span, MessagingRequest request);\n\n    /**\n     * Inject Producer's Span and Forwarded Headers key:value to Request {@link MessagingRequest#setHeader(String, String)}.\n     *\n     * @param span    key:value from\n     * @param request key:value to\n     * @see Request#setHeader(String, String)\n     */\n    void producerInject(Span span, MessagingRequest request);\n\n\n    //---------------------------------- 4. Small Tracing ------------------------------------------\n    /**\n     * Returns a new child span if there's a {@link Tracing#currentSpan()} or a new trace if there isn't.\n     *\n     * @return {@link Span}\n     */\n    Span nextSpan();\n}\n```\n\n[com.megaease.easeagent.plugin.api.context.RequestContext](../plugin-api/src/main/java/com/megaease/easeagent/plugin/api/context/RequestContext.java)\n```java\n/**\n * A cross-process data context, including tracing and Forwarded Headers\n * <p>\n * The Scope must be close after plugin:\n *\n * <pre>{@code\n *    void after(...){\n *       RequestContext rc = context.get(...)\n *       try{\n *\n *       }finally{\n *           rc.scope().close();\n *       }\n *    }\n * }</pre>\n */\npublic interface RequestContext extends Setter {\n    /**\n     * When true, do nothing and nothing is reported . However, this RequestContext should\n     * still be injected into outgoing requests. Use this flag to avoid performing expensive\n     * computation.\n     */\n    boolean isNoop();\n\n    /**\n     * @return {@link Span} for next progress client span\n     */\n    Span span();\n\n    /**\n     * The Scope must be close after plugin:\n     *\n     * <pre>{@code\n     *    void after(...){\n     *       RequestContext rc = context.get(...)\n     *       try{\n     *\n     *       }finally{\n     *           rc.scope().close();\n     *       }\n     *    }\n     * }</pre>\n     *\n     * @return {@link Scope} for current Span\n     */\n    Scope scope();\n\n    /**\n     * set header for next progress\n     *\n     * @param name  of header\n     * @param value of header\n     */\n    void setHeader(String name, String value);\n\n    /**\n     * @return headers from the progress data context\n     */\n    Map<String, String> getHeaders();\n\n    /**\n     * finish the progress span and save tag from {@link Response#header(String)}\n     *\n     * @param response {@link Response}\n     */\n    void finish(Response response);\n}\n```\n\n## Tracing\nThe implementation of our Tracing Api and Tracing has been decoupled. For details, see:\n* [com.megaease.easeagent.plugin.api.trace.ITracing](../plugin-api/src/main/java/com/megaease/easeagent/plugin/api/trace/ITracing.java)\n\n\n## Span\n\nIn a Tracing, each node is a Span, here we also encapsulate the Span.\n\nAccording to the agreement, we still abide by Dapper\n\n* [com.megaease.easeagent.plugin.api.trace.Span](../plugin-api/src/main/java/com/megaease/easeagent/plugin/api/trace/Span.java)\n\n## Other\n\nIn cross-process information transmission, there is the concept of request and response. Here we also encapsulate to facilitate the transfer and extraction of data.\n\n1. We define two interfaces in cross-process\n\n    * [com.megaease.easeagent.plugin.api.trace.Request](../plugin-api/src/main/java/com/megaease/easeagent/plugin/api/trace/Request.java)\n    * [com.megaease.easeagent.plugin.api.trace.Response](../plugin-api/src/main/java/com/megaease/easeagent/plugin/api/trace/Response.java)\n\n2. We define a interface in data queue\n\n    * [com.megaease.easeagent.plugin.api.trace.MessagingRequest](../plugin-api/src/main/java/com/megaease/easeagent/plugin/api/trace/MessagingRequest.java)\n\n"
  },
  {
    "path": "doc/user-manual.md",
    "content": "\n# User Manual\n\n- [User Manual](#user-manual)\n  - [Configuration](#configuration)\n    - [Getting the configuration file](#getting-the-configuration-file)\n    - [Global Configuration](#global-configuration)\n      - [Agent Configuration](#agent-configuration)\n      - [Runtime Code Version Configuration](#runtime-code-version-configuration)\n        - [Jdk](#jdk)\n        - [Spring Boot](#spring-boot)\n      - [Internal HTTP Server](#internal-http-server)\n      - [Output Data Server: Kafka and HTTP/Zipkin Server](#output-data-server-kafka-and-httpzipkin-server)\n      - [Progress Configuration](#progress-configuration)\n        - [Forwarded headers config](#forwarded-headers-config)\n        - [Tracing config](#tracing-config)\n    - [Plugin Configuration](#plugin-configuration)\n      - [Tracing and Metric](#tracing-and-metric)\n      - [Application Log](#application-log)\n      - [Redirect](#redirect)\n      - [Forwarded headers plugin enabled](#forwarded-headers-plugin-enabled)\n      - [Service Name Head](#service-name-head)\n      - [Plugin Http configuration modification api](#plugin-http-configuration-modification-api)\n  - [Logging](#logging)\n    - [EaseAgent log](#easeagent-log)\n    - [MDC](#mdc)\n  - [Prometheus Support](#prometheus-support)\n  - [Health Check and Readiness Check Endpoint](#health-check-and-readiness-check-endpoint)\n  - [Agent info Endpoint](#agent-info-endpoint)\n  - [Tracing](#tracing)\n    - [Tracing Component](#tracing-component)\n    - [Tracing Component Config Description](#tracing-component-config-description)\n    - [Custom Span Tag](#custom-span-tag)\n      - [JDBC](#jdbc)\n      - [Cache](#cache)\n      - [RabbitMQ Producer And Consumer](#rabbitmq-producer-and-consumer)\n      - [Kafka Producer And Consumer](#kafka-producer-and-consumer)\n      - [Dubbo Client and Server](#dubbo-client-and-server)\n      - [Motan Client and Server](#motan-client-and-server)\n      - [SOFARPC Client and Server](#sofarpc-client-and-server)\n  - [Metric](#metric)\n    - [Metric Field](#metric-field)\n      - [HTTP Request](#http-request)\n      - [JDBC Statement](#jdbc-statement)\n      - [JDBC Connection](#jdbc-connection)\n      - [JVM Memory](#jvm-memory)\n      - [JVM GC](#jvm-gc)\n      - [Kafka Client](#kafka-client)\n      - [RabbitMQ Producer](#rabbitmq-producer)\n      - [RabbitMQ Consumer](#rabbitmq-consumer)\n      - [Spring AMQP on Message Listener](#spring-amqp-on-message-listener)\n      - [Elasticsearch](#elasticsearch)\n      - [MongoDB](#mongodb)\n      - [Dubbo](#dubbo)\n      - [Motan](#motan)\n      - [SOFARPC](#sofarpc)\n  - [Application Log](#application-log-1)\n\n## Configuration\nThe EaseAgent configuration information can be divided into two categories, one is the **global configuration** and the other is the **plugin configuration**.  \nGlobal configuration include dedicated parameters for controlling metrics and tracing collection behavior via **agent.properties**. These parameters include:\n\n* Data reporting frequency\n* Data reporting output type\n* Kafka topic of data reporting\n* Data collecting and reporting switch\n* Queue depth in process for high throughput\n\nPlugin level configuration provides more granular control and customizable configuration.\n\n## Configuring priority\nThe agent can consume configuration from one or more of the following sources (ordered from highest to lowest priority):\n\n- system properties\n- environment variables\n- the configuration file\n\n### Getting the configuration file\nYou may extract default configuration from the JAR file or create new properties from a blank file.\n```\n$ jar xf easeagent.jar agent.properties easeagent-log4j2.xml\n```\nRun the user application with EaseAgent\n```\n$ export EASE_AGENT_PATH=[Replace with agent path]\n$ java \"-javaagent:${EASE_AGENT_PATH}/easeagent.jar\" -Deaseagent.config.path=${EASE_AGENT_PATH}/agent.properties -jar user-app.jar\n\nor\n\n$ export EASE_AGENT_PATH=[Replace with agent path]\n$ export EASEAGENT_CONFIG_PATH=${EASE_AGENT_PATH}/agent.properties\n$ java \"-javaagent:${EASE_AGENT_PATH}/easeagent.jar\" -jar user-app.jar\n```\n\n\n### Global Configuration\n\n#### Agent Configuration\n\n| System property   | Environment variable \t | Configuration File Key | Description                   |\n|-------------------|------------------------|------------------------|-------------------------------|\n| `easeagent.name`  | `EASEAGENT_NAME`       | `name`                 | Specify logical service name. |\n| `easeagent.system` | `EASEAGENT_SYSTEM`     | `system`               | Specify logical service system. |\n\n#### Runtime Code Version Configuration\n\n##### jdk\n\n* `System property`: `runtime.code.version.points.jdk`\n* `version`: `jdk8`,`jdk17`\n* `description`: specify the JDK version of the running environment\n* `plugins`:\n  * httpurlconnection: `default`,`jdk8` \n  * httpurlconnection-jdk17: `jdk17`\n  * tomcat-jdk17: `jdk17`\n* eg. `runtime.code.version.points.jdk=jdk17`\n\n##### spring-boot\n\n* `System property`: `runtime.code.version.points.spring-boot`\n* `version`: `2.x.x`,`3.x.x`\n* `description`: specify the spring-boot version of the running environment\n* `plugins`:\n    * servicename: `default`,`2.x.x`\n    * spring-gateway: `default`,`2.x.x`\n    * springweb(resTemplate): `default`,`2.x.x`\n    * springweb(feignClient,webclient): `default`\n    * spring-boot-gateway-3.5.3: `3.x.x`\n    * spring-boot-rest-template-3.5.3: `3.x.x`\n    * spring-boot-servicename-3.5.3: `3.x.x`\n* eg. `runtime.code.version.points.spring-boot=3.x.x`\n\n###### about spring boot 3.x.x\nWhen your code uses Spring Boot 3.x.x, it means that your code depends on JDK 17+ and Spring Boot 3+.\n\nIn this case, you need to add two configurations for the agent to take effect:\n```properties\nruntime.code.version.points.jdk=jdk17\nruntime.code.version.points.spring-boot=3.x.x\n```\n\n###### about doc\n[spring-boot-3.x.x-demo](spring-boot-3.x.x-demo.md)\n\n[spring-boot-upgrade](spring-boot-upgrade.md)\n\n#### Internal HTTP Server\nEaseAgent opens port `9900` by default to receive configuration change notifications and Prometheus requests.\n\n| System property            | Environment variable \t | DefaultValue | Description                                                                                                                                                                                                                                                                            |\n|----------------------------|---------------------------|------| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `easeagent.server.enabled` | `EASEAGENT_SERVER_ENABLED` | true | Enable Internal HTTP Server. `false` can disable it. EaseAgent will no longer accept any HTTP requests (`Prometheus`、`Health Check`、`Readiness Check`、`Agent Info`) when the Internal HTTP Server is disabled. User can add VM parameter:`-Deaseagent.server.enabled=[true or false]` to override. |\n| `easeagent.server.port`    | `EASEAGENT_SERVER_PORT`    | 9900 | Internal HTTP Server port. User can add VM parameter:`-Deaseagent.server.port=[new port]` to override.                                                                                                                                                                                 |\n\n\n#### Output Data Server: Kafka and HTTP/Zipkin Server\nTracing and metric data can be output to kafka server.\n\n| Key                                     | Default Value  | Description                                                                  |\n| --------------------------------------- | -------------- | ---------------------------------------------------------------------------- |\n| `reporter.outputServer.bootstrapServer` | 127.0.0.1:9092 | Kafka server host and port. Tracing and metric data will be output to kafka. |\n| `reporter.outputServer.timeout`         | 10000          | Connect timeout. Time Unit: millisecond.                                     |\n\nGlobal configuration for tracing output\n\n| Key                                  | Default Value                          | Description                                                                                                                             |\n| ------------------------------------ | -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |\n| `reporter.tracing.sender.appendType` | console                                | `console` : output tracing to console; `kafka` : output tracing to kafka output server; `http`: send data to http server(zipkin) server |\n| `reporter.tracing.sender.url`        | [http://localhost:9411/api/v2/spans]() | Zipkin(HTTP) server url, only available when `reporter.tracing.sender.appendType=http`                                                  |\n| `reporter.tracing.sender.topic`      | log-tracing                            | kafka topic, only available when `reporter.tracing.sender.appendType=kafka`                                                             |\n\n\nFollowing tracing sender configuration items:\n\n| Key                                        | Default Value | Description                                                                                                                                                                            |\n| ------------------------------------------ | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `reporter.tracings.output.enabled`         | true          | `true`: enable output tracing data;<br /> `false`: disable all tracing data output                                                                                                     |\n| `reporter.tracings.output.messageMaxBytes` | 999900        | Maximum bytes sendable per message including encoding overhead.                                                                                                                        |\n| `reporter.tracings.output.queuedMaxSpans`  | 1000          | Maximum backlog of spans reported before sent.                                                                                                                                         |\n| `reporter.tracings.output.queuedMaxSize`   | 1000000       | Maximum backlog of span bytes reported before sent.                                                                                                                                    |\n| `reporter.tracings.output.messageTimeout`  | 1000          | Spans are bundled into messages, up to `messageMaxBytes`. This timeout starts when the first unsent span is reported, which ensures that spans are not stuck in an incomplete message. |\n\n\nConfiguration for access log output are similar to tracing:\n\n| Key                              | Default Value    | Description                                                                                                                     |\n| -------------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------- |\n| `reporter.log.sender.appendType` | console          | `console` : output log to console; `kafka` : output log to kafka output server; `http`: send data to http server(zipkin) server |\n| `reporter.log.sender.url`        | /application-log | HTTP server url, only available when `reporter.log.sender.appendType=http`                                              |\n| `reporter.log.sender.topic`      | applicaton-log   | kafka topic, only available when `reporter.log.sender.appendType=kafka`                                                         |\n\n\n| Key                                   | Default Value | Description                                                                                                                                                                         |\n| ------------------------------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `reporter.log.output.enabled`         | true          | `true`: enable output log data;<br /> `false`: disable all log data output                                                                                                          |\n| `reporter.log.output.messageMaxBytes` | 999900        | Maximum bytes sendable per message including encoding overhead.                                                                                                                     |\n| `reporter.log.output.queuedMaxLogs`   | 1000          | Maximum backlog of logs reported before sent.                                                                                                                                       |\n| `reporter.log.output.queuedMaxSize`   | 1000000       | Maximum backlog of log bytes reported before sent.                                                                                                                                  |\n| `reporter.log.output.messageTimeout`  | 1000          | Logs are bundled into messages, up to `messageMaxBytes`. This timeout starts when the first unsent log is reported, which ensures that logs are not stuck in an incomplete message. |\n\n\n\n#### Progress Configuration\n\n##### Forwarded headers config\n\nEaseagent provides a header pass-through plugin.\n\nConfig format: \n\n`easeagent.progress.forwarded.headers.{key}={headerName}`\n\n1. {key} indicates the unique key of the header configuration, used to identify the configuration modification\n2. {headerName} is the Header Name you need to pass through\n\nExample:\n```properties\neaseagent.progress.forwarded.headers.canary.0=X-Mesh-Canary\n```\nIn the process of supporting easemesh traffic coloring, the request header `X-Mesh-Canary` needs to be deeply passed through.\n\n```\n(add header: X-Mesh-Canary=lv1) -> serviceA(X-Mesh-Canary=lv1) -> mesh(check  X-Mesh-Canary) --> servcieB\n                                                                                         |\n                                                                                         |_____> servcieB-canary(X-Mesh-Canary=lv1)\n```\n\nplugin enabled config: [Enabled](#forwarded-headers-plugin-enabled)\n\n\n##### Tracing config\n\nEaseagent supports the original sampling model of zipkin. Currently, there are three types of models supported: `counting`, `rate_limiting`, and `boundary`.\n\nUse the configuration control: `observability.tracings.sampledType`\n\n1. `counting`: percentage sampling, sampled limit 0.01 to 1, 1 is always sample, 0 is never sample, 0.1 is ten samples per hundred\n2. `rate_limiting`: traces per second, sampled >= 0, 0 is never sample, 10 is max 10 traces per second\n3. `boundary`: percentage sampling by traceId, sampled limit 0.0001 to 1, 1 is always sample, 0 is never sample.\n               if sampled=0.001, when (traceId^random)%10000<=(0.001*10000) sampled\n\nsampledType must be used with sampled, otherwise the default value is used Sampler.ALWAYS_SAMPLE\n\nConfig format:\n```properties\nobservability.tracings.sampledType=counting\nobservability.tracings.sampled=0.01\n```\n\nEaseagent will grab the header from the response of the process, and put the name and value of the header as a tag in the Span of Tracing.\n\nConfig format:\n`observability.tracings.tag.response.headers.{key}={value}`\n\n1. {key} indicates the unique key of the header configuration, used to identify the configuration modification\n2. {headerName} is the Header Name you need to tag\n\nExample:\n```properties\nobservability.tracings.tag.response.headers.eg.0=X-EG-Circuit-Breaker\n```\n\nIn the process of supporting sidecars (such as easemesh), the sidecars will hijack or color traffic according to the situation.\n\nIn order to facilitate observation and drawing, sidecars should add header information in the response header and record the tag in Tracing.\n\nexample:\neasemesh adds the following header information: `X-EG-Circuit-Breaker`, `X-EG-Retryer`, `X-EG-Rate-Limiter`, `X-EG-Time-Limiter`\n\nThe tag will be added to the Tracing Span of the request client:\n\n```json\n{\"kind\": \"CLIENT\", \"tags\": {\"X-EG-Circuit-Breaker\":\"aaaa\", \"X-EG-Retryer\":\"bbbb\", \"X-EG-Rate-Limiter\":\"cccc\", \"X-EG-Time-Limiter\":\"dddd\"}}\n```\n\n### Plugin Configuration\nMost capabilities of Easeagent, such as tracing and metric, are provided through plugins.\nThe format of the plugin configuration is defined as follows.\n```\nplugin.[domain].[namespace].[function].[key] = [value]\n```\nTake the tracing switch of `httpclient` as an example.\n```\nplugin.observability.httpclient.tracing.enabled=true\n\ndomain          : observability\nnamespace       : httpclient\nfunction        : tracing\nkey             : enabled\nvalue           : true\n```\n\n`[domain]` and `[namespace]` and `[function]` are defined by plugins, and further details can be found in the [plugin development guide](./development-guide.md).\n\nFor plugin level configuration, EaseAgent defines a spacial **namespace** of `global` in which user can define default configuration for any `function`, like `metric`, and each namespace plugin of this `function` will uses the default configuration when it does not create configuration with its own namespace.\n\nFor example, Metric have a set of default plugin configuration as follows:\n```\nplugin.observability.global.metric.enabled=true\nplugin.observability.global.metric.interval=30\nplugin.observability.global.metric.topic=application-meter\nplugin.observability.global.metric.appendType=kafka\n```\n\nAll metric plugins will inherit default configuration, unless they have configured a configuration item with the same [key] and replaced the `global` namespace with its own namespace to override.\n\n```\n# this configuration item of rabbitmq indicate that rabbitmq's metirc data is printed to console instead of send to kafka server as configured by the default.\n\nplugin.observability.rabbitmq.metric.appendType=console\n```\nBut the switch configuration item using `enabled` as key cannot be overridden, for `boolean` type configuration is determined by a \"logical AND\" operation between the global and its own namespace configuration.\n\nThe following sections describe the metric and tracing configuration items,  as well as the currently supported plugins and their corresponding namespaces\n\n#### Tracing and Metric\n\n| Key                                             | Default Value     | Description                                                                                                                                                                                                       |\n| ----------------------------------------------- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `plugin.observability.global.tracing.enabled`   | true              | Enable all tracing collection. `false`: Disable all tracing collection.                                                                                                                                           |\n| `plugin.observability.global.metric.enabled`    | true              | Enable all metrics collection. `false`: Disable all metrics collection.                                                                                                                                           |\n| `plugin.observability.global.metric.interval`   | 30                | Time interval between two outputs. Time Unit: second.                                                                                                                                                             |\n| `plugin.observability.global.metric.topic`      | application-meter | Send metric data to the specified kafka topic, only avaliable when `appendType` is `kafka`.                                                                                                                       |\n| `plugin.observability.global.metric.url`        | /metrics          | Send metric data to the specified http URI, which will be appended to `reporter.outputServer.bootstrapServer`, to form a full url, only avaliable when `appendType` is `http`.                                    |\n| `plugin.observability.global.metric.appendType` | kafka             | The value should be `kafka`, `console` or `http`. `kafka`: EaseAgent will output metric data to kafka server. `console`: EaseAgent will output metric data to console; `http`: output metric data to http server. |\n\nSupported components and corresponding namespaces:\n\n| Plugin/Components | Namespace        | Description                                                                                                                                                                                                                                                                                                                                                                |\n|-------------------|------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| httpservlet       | `httpServlet`    | Http Request Metric                                                                                                                                                                                                                                                                                                                                                        |\n| tomcat            | `tomcat`         | Http Request Metric                                                                                                                                                                                                                                                                                                                                                        |\n| spring-gateway    | `springGateway`  | Http Request Metric                                                                                                                                                                                                                                                                                                                                                        |\n| jdbcConnection    | `jdbcConnection` | JDBC Connection Metric                                                                                                                                                                                                                                                                                                                                                     |\n| jdbcStatement     | `jdbcStatement`  | JDBC SQL Metric. When using SQL as a tag, the string length of SQL is often very long, which will consume network bandwidth and CPU to a great extent. Our solution is to use SQL's MD5 as an indicator, which is associated with the storage and front-end.Closed configuration: `plugin.observability.jdbc.sql.compress.enabled=false`                                   |\n| md5Dictionary     | `md5Dictionary`  | SQL-MD5Dictionary. `When EaseAgent is used with EaseMesh, tracing and metric data will be stored in Elasticsearch. In order to reduce the space occupied by SQL in Elasticsearch, EaseAgent uses md5 to reduce the length of SQL, and then periodically stores it in Kafka, and finally stores it in Elasticsearch. Only one copy of sql will be stored in Elasticsearch.` |\n| redis             | `redis`          | Redis Metric                                                                                                                                                                                                                                                                                                                                                               |\n| kafka             | `kafka`          | Kafka Metric                                                                                                                                                                                                                                                                                                                                                               |\n| rabbitmq          | `rabbitmq`       | RabbitMQ Metric                                                                                                                                                                                                                                                                                                                                                            |\n| jvmGc             | `jvmGc`          | JVM GC Metric                                                                                                                                                                                                                                                                                                                                                              |\n| JVM Memory        | `jvmMemory`      | JVM Memory Metric                                                                                                                                                                                                                                                                                                                                                          |\n| dubbo             | `dubbo`          | dubbo Metric                                                                                                                                                                                                                                                                                                                                                               |\n| motan             | `motan`          | Motan Metric                                                                                                                                                                                                                                                                                                                                                               |\n| sofarpc           | `sofarpc`        | SOFARPC  Metric                                                                                                                                                                                                                                                                                                                                                            |\n\n#### Application Log\nApplication log modules collecting application logs printed by the application.\n\nSupported components/plugins and corresponding namespaces:\n\n| Plugin/Components | Namespace       | Description               |\n| ----------------- | --------------- | ------------------------- |\n| logback           | `logback`       | Support logback library   |\n| log4j2            | `log4j2 `       | Support log4j2 library         |\n| access            | `access`        | Access log module         |\n\nThe default configuration is as follows:\n\n```\nplugin.observability.global.log.enabled=true\nplugin.observability.global.log.appendType=console\nplugin.observability.global.log.topic=application-log\nplugin.observability.global.log.url=/application-log\nplugin.observability.global.log.level=INFO\n\n\nplugin.observability.global.log.encoder=LogDataJsonEncoder\nplugin.observability.global.log.encoder.timestamp=%d{UNIX_MILLIS}\nplugin.observability.global.log.encoder.logLevel=%-5level\nplugin.observability.global.log.encoder.threadId=%thread\nplugin.observability.global.log.encoder.location=%logger{36}\nplugin.observability.global.log.encoder.message=%msg%n\n\nplugin.observability.access.log.encoder=AccessLogJsonEncoder\n\nplugin.observability.logback.log.enabled=false\nplugin.observability.log4j2.log.enabled=false\n```\n\nThe `logback` and `log4j2` modules are disabled by default, and they can be `enabled` in the user configuration file to enable collecting logs printed by the application.\nThe `LogDataJsonEncoder` supports `log4j2` style pattern configuration for each field.\n\nTo send logs data to `Opentelemetry` compatible backend, the corresponding `Encoder` need to be developed.\n\n#### Redirect\nRedirection feature combined with `EaseMesh` to direct traffic to shadow services to simulate real traffic for the whole site performance test in the production environment in an effective and safe way.\nFor more detail, please reference [EaseMesh](https://megaease.com/easemesh/) documents.\n\nThe default configuration has only one item:\n\n```\nplugin.integrability.global.redirect.enabled=true\n```\n\nSupported components/plugins and corresponding namespaces:\n\n| Plugin/Components | Namespace       | Description               |\n| ----------------- | --------------- | ------------------------- |\n| jdbc              | `jdbc`          | Database Redirection      |\n| redis             | `redis`         | Redis Redirection         |\n| kafka             | `kafka`         | Kafka Redirection         |\n| rabbitmq          | `rabbitmq`      | RabbitMQ Redirection      |\n| elasticsearch     | `elasticsearch` | Elasticsearch Redirection |\n\n#### Forwarded headers plugin enabled\n\nEaseagent provides a header pass-through plugin.\n\n```\nplugin.integrability.global.forwarded.enabled=true\n```\n\n\n#### Service Name Head\n\nTo support easemesh, we have added a new plugin called \"servicename\".\n\nIt will get the service name in advance, and then put the service name in the HTTP request header.\n\nheader name config:\n```properties\nplugin.integrability.serviceName.addServiceNameHead.propagate.head=X-Mesh-RPC-Service\n```\n\nThe current way to obtain ServiceName only supports service discovery using Spring Cloud.\n\n#### Plugin Http configuration modification api\n\nAfter the EaseAgent enabled the http port, the http api can be used to modify the configuration of the plugin.\n\n1. The plugin configuration items can be modified directly from the configuration information mapping:\n    ```\n    GET /plugins/domains/{domain}/namespaces/{namespace}/{id}/properties/{property}/{value}/{version}\n    ```\n\n2. API supports passing json one-time \"modification/addition\" content instead of setting them one by one. For example:\n    ```\n    POST /plugins/domains/{domain}/namespaces/{namespace}/{id}/properties\n    \n    {\n         \"version\": \"1\",\n         \"property1\": \"value1\",\n         \"property2\": \"value2\"\n    }\n    ```\n\nthe {version} can be any information\n\n\n\n## Logging\n\n### EaseAgent log\nEaseAgent use `Log4j2` for all internal logging, the default log level is `INFO`, and the logs will be outputted to the `Console`. User can modify the log level and appender in the `easeagent-log4j2.xml` file.\n\nAfter modification, User can run the application with EaseAgent.\n```\n$ export EASE_AGENT_PATH=[Replace with agent path]\n$ java \"-javaagent:${EASE_AGENT_PATH}/easeagent.jar -Deaseagent.log.conf=${EASE_AGENT_PATH}/easeagent-log4j2.xml\" -jar user-app.jar\n```\nor\n```\n$ export EASE_AGENT_PATH=[Replace with agent path]\n$ export EASEAGENT_LOG_CONF=/your/log4j2/config/filepath\n$ java \"-javaagent:${EASE_AGENT_PATH}/easeagent.jar -jar user-app.jar\n```\n\n### MDC\nEaseagent automatically adds TraceId and SpanId to the MDC (Mapped Diagnostic Context) when creating a new Span. You can configure your slf4j or logback files by adding parameters to display these IDs, or retrieve them directly from the MDC in your code.\n\nHere is an example configuration file that displays `traceId` and `spaceId` in the log output::\n\n```xml\n<appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n    <encoder>\n        <pattern>!-%d{HH:mm:ss.SSS} [%thread] %-5level %logger{16} - [%X{traceId}/%X{spanId}] %msg%n</pattern>\n    </encoder>\n</appender>\n```\n\nHere is an example of how to retrieve `traceId` and `spanId` in code.\n```java\nString traceId = org.slf4j.MDC.get(\"traceId\");\nString spanId = org.slf4j.MDC.get(\"spanId\");\n```\n\n## Prometheus Support\nWhen Internal HTTP Server is enabled, User can use Prometheus to collect metrics information.\n* Adding the following configuration in `prometheus.yml`\n```\n  - job_name: 'user-app'\n    static_configs:\n      - targets: ['localhost:9900']\n    metrics_path: \"/prometheus/metrics\"\n```\n\n## Health Check and Readiness Check Endpoint\nEaseAgent supply the `health check`、`readiness check` endpoint.\n\n* `Health Check` Endpoint\n```\n[GET] http://[ip]:[easeagent.server.port]/health\nThe response status will be 200(OK)\n```\n\n* `Readiness Check` Endpoint\nAfter Spring sending `ApplicationReadyEvent`, EaseAgent will change readiness status to `true`\n```\n[GET] http://[ip]:[easeagent.server.port]/health/readiness\nThe response status will be 200(OK)\n```\n\n## Agent info Endpoint\nEaseAgent supply the `agent info` http api.\n```\n[GET] http://[ip]:[easeagent.server.port]/agent-info\n```\nThe response status will be 200(OK)\nResponse Body:\n```json\n{\n    \"type\": \"EaseAgent\",\n    \"version\": \"x.x.x\"\n}\n```\n\n## Tracing\nEaseAgent use [brave](https://github.com/openzipkin/brave) to collect tracing logs.The data format stored in `Kafka`  is [Zipkin Data Model](https://zipkin.io/pages/data_model.html). User can send tracing logs to [Zipkin server](https://zipkin.io/pages/quickstart.html).\n\n### Tracing Component\n| Component Type | Component                                      | Reference                                                                                                                                                                                                                                                                                                           |\n| -------------- |------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| HTTP Client    | `RestTemplate`、 `WebClient`、 `FeignClient`     | [brave-instrumentation-http](https://github.com/openzipkin/brave/tree/master/instrumentation/http)                                                                                                                                                                                                                  |\n| HTTP Server    | `Servlet`、`Tomcat`、`Filter`                    | [brave-instrumentation-http](https://github.com/openzipkin/brave/tree/master/instrumentation/http)                                                                                                                                                                                                                  |\n| DataBase       | `JDBC`                                         | [Brave](https://github.com/openzipkin/brave/tree/master/brave)                                                                                                                                                                                                                                                      |\n| Cache          | `Jedis`、`Lettuce`                              | [Brave](https://github.com/openzipkin/brave/tree/master/brave)                                                                                                                                                                                                                                                      |\n| Message        | `RabbitMQ`、`Kafka`                             | [brave-instrumentation-messaging](https://github.com/openzipkin/brave/tree/master/instrumentation/messaging) 、[Brave Kafka instrumentation](https://github.com/openzipkin/brave/tree/master/instrumentation/kafka-clients)                                                                                          |\n| Logging        | `Log4j2`、`Logback`                             | [brave-context-log4j2](https://github.com/openzipkin/brave/tree/master/context/log4j2) 、[brave-context-slf4j](https://github.com/openzipkin/brave/tree/master/context/slf4j)                                                                                                                                        |\n| RPC            | `AlibabaDubbo`、`ApacheDubbo`、`Motan`,`SOFARPC` | [brave-instrumentation-dubbo](https://github.com/openzipkin/brave/tree/master/instrumentation/dubbo) 、[brave-instrumentation-dubbo-rpc](https://github.com/openzipkin/brave/tree/master/instrumentation/dubbo-rpc)、[brave-instrumentation-rpc](https://github.com/openzipkin/brave/tree/master/instrumentation/rpc) |\n\n### Tracing Component Config Description\n| Component Type | Component | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |\n| -------------- |-----------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| RPC            | `Motan`   | The motan plugin adds a dynamic switch to collect interface parameters and return values for troubleshooting purposes. The display format is json (Notes: The library used is [Jackson](https://github.com/FasterXML/jackson.git)), which is disabled by default. You can enable the parameter collection switch with this configuration: `plugin.observability.motan.tracing.args.collect.enabled=ture`, and the return values collection switch with this configuration: `plugin.observability.motan.tracing.result.collect.enabled=ture`.     |\n| RPC            | `Dubbo`   | The motan plugin adds a dynamic switch to collect interface parameters and return values for troubleshooting purposes. The display format is json (Notes: The library used is [Jackson](https://github.com/FasterXML/jackson.git)), which is disabled by default. You can enable the parameter collection switch with this configuration: `plugin.observability.dubbo.tracing.args.collect.enabled=ture`, and the return values collection switch with this configuration: `plugin.observability.dubbo.tracing.result.collect.enabled=ture`.     |\n| RPC            | `SOFARPC`  | The motan plugin adds a dynamic switch to collect interface parameters and return values for troubleshooting purposes. The display format is json (Notes: The library used is [Jackson](https://github.com/FasterXML/jackson.git)), which is disabled by default. You can enable the parameter collection switch with this configuration: `plugin.observability.sofarpc.tracing.args.collect.enabled=ture`, and the return values collection switch with this configuration: `plugin.observability.sofarpc.tracing.result.collect.enabled=ture`. |\n\n\n### Custom Span Tag\n\n#### JDBC\n| Tag             | Description                                                            |\n| --------------- | ---------------------------------------------------------------------- |\n| sql             | Sql text in user application                                           |\n| local-component | Default value = 'database'                                             |\n| url             | Connection information. Example: `jdbc:mysql://localhost:3306/db_demo` |\n| error           | SQLException information                                               |\n\n#### Cache\n| Tag          | Description                           |\n| ------------ | ------------------------------------- |\n| redis.method | Redis command. Example: `MGET`、`GET` |\n\n#### RabbitMQ Producer And Consumer\n| Tag                | Description         |\n| ------------------ | ------------------- |\n| rabbit.exchange    | RabbitMQ exchange   |\n| rabbit.routing_key | RabbitMQ routingKey |\n| rabbit.queue       | RabbitMQ routingKey |\n\n#### Kafka Producer And Consumer\n| Tag          | Description               |\n| ------------ | ------------------------- |\n| kafka.key    | Kafka consumer record Key |\n| kafka.topic  | Kafka topic               |\n| kafka.broker | Kafka url                 |\n\n\n#### Dubbo Client and Server\n| Tag                      | Description                       |\n|--------------------------|-----------------------------------|\n| dubbo.args               | dubbo interface arguments         |\n| dubbo.result             | dubbo interface return value      |\n| dubbo.group              | dubbo client group name           |\n| dubbo.service            | dubbo service interface full name |\n| dubbo.method             | dubbo service method signature    |\n| dubbo.service.version    | dubbo service interface version   |\n| dubbo.client.application | dubbo client application name     |\n| dubbo.server.application | dubbo server application name     |\n\n#### Motan Client and Server\n| Tag                   | Description                       |\n|-----------------------|-----------------------------------|\n| motan.args            | motan interface arguments         |\n| motan.result          | motan interface return value      |\n| motan.service         | motan service interface full name |\n| motan.method          | motan service method signature    |\n| motan.service.version | motan service interface version   |\n| motan.application     | motan client application name     |\n| motan.module          | motan server module name          |\n| motan.group           | motan client group name           |\n\n#### SOFARPC Client and Server\n| Tag                         | Description                          |\n|-----------------------------|--------------------------------------|\n| sofarpc.args               | SOFARPC interface arguments         |\n| sofarpc.result             | SOFARPC interface return value      |\n| sofarpc.service            | SOFARPC service interface full name |\n| sofarpc.method             | SOFARPC service method signature    |\n| sofarpc.service.uniqueId   | SOFARPC service interface uniqueId  |\n| sofarpc.client.application | SOFARPC client application name     |\n| sofarpc.server.application | SOFARPC server module name          |\n\n## Metric\nEaseAgent use [io.dropwizard.metrics](https://github.com/dropwizard/metrics) to collect metric information.\n\nPrometheus Metric Schedule: [Prometheus Metric](./prometheus-metric-schedule.md)\n\nPrometheus Exports Rules: [Prometheus Exports](./metric-api.md#7prometheusexports)\n\n\n### Metric Field\nEaseAgent output metric data to kafka. The data stored in kafka is in JSON format.\n\nFor Example: EaseAgent collect metric of HTTP Request. The collected metric data are as follows:\n```json\n{\n  \"m15err\" : 0,\n  \"m5err\" : 0,\n  \"cnt\" : 1,\n  \"url\" : \"GET \\/\",\n  \"m5\" : 0.050990000000000001,\n  \"max\" : 823,\n  \"mean\" : 823,\n  \"p98\" : 823,\n  \"errcnt\" : 0,\n  \"host_name\" : \"akwei\",\n  \"min\" : 823,\n  \"category\" : \"application\",\n  \"system\" : \"none\",\n  \"type\" : \"http-request\",\n  \"mean_rate\" : 0,\n  \"p99\" : 823,\n  \"p95\" : 823,\n  \"m15\" : 0.12681999999999999,\n  \"timestamp\" : 1621567320892,\n  \"service\" : \"unknown-service\",\n  \"m1\" : 0.00022000000000000001,\n  \"m5errpct\" : 0,\n  \"p25\" : 823,\n  \"p75\" : 823,\n  \"p50\" : 823,\n  \"host_ipv4\" : \"192.168.2.5\",\n  \"m1errpct\" : 0,\n  \"m15errpct\" : 0,\n  \"m1err\" : 0,\n  \"p999\" : 823\n}\n```\n\nFor different kind of metrics, we have different schemas:\n\n#### HTTP Request\n`httpServlet` and `Tomcat` both support HTTP Request \nHTTP Request schema describes key metrics of service APIs, which include:\n* Total execution count (cnt, errcnt)\n* Throughput (m1, m5, m15)\n* Error throughput (m1err, m5err, m15err)\n* Error throughput percentage (m1errpct, m5errpct, m15errpct)\n* Latency (p25, p50, p75, p95, p98, p99)\n* Execution duration (min, mean, max)\n\n| Field     |  Type   | Description                                                                                            |\n| :-------- | :-----: | :----------------------------------------------------------------------------------------------------- |\n| url       | string  | the URL of the request                                                                                 |\n| cnt       | integer | The total count of the request executed                                                                |\n| errcnt    | integer | The total error count of the request executed                                                          |\n| m1        | double  | The HTTP request executions per second (exponentially-weighted moving average) in last 1 minute        |\n| m5        | double  | The HTTP request executions per second (exponentially-weighted moving average) in last 5 minute.       |\n| m15       | double  | The HTTP request executions per second (exponentially-weighted moving average) in last 15 minute.      |\n| m1err     | double  | The HTTP error request executions per second (exponentially-weighted moving average) in last 1 minute  |\n| m5err     | double  | The HTTP error request executions per second (exponentially-weighted moving average) in last 5 minute. |\n| m15err    | double  | The HTTP error request executions per second (exponentially-weighted moving average) in last 15 minute |\n| m1errpct  | double  | error percentage in last 1 minute                                                                      |\n| m5errpct  | double  | error percentage in last 5 minute                                                                      |\n| m15errpct | double  | error percentage in last 15 minute                                                                     |\n| min       | double  | The http-request minimal execution duration in milliseconds.                                           |\n| max       | double  | The http-request maximal execution duration in milliseconds.                                           |\n| mean      | double  | The http-request mean execution duration in milliseconds.                                              |\n| p25       | double  | TP25: The http-request execution duration in milliseconds for 25% user.                                |\n| p50       | double  | TP50: The http-request execution duration in milliseconds for 50% user.                                |\n| p75       | double  | TP75: The http-request execution duration in milliseconds for 75% user.                                |\n| p95       | double  | TP95: The http-request execution duration in milliseconds for 95% user.                                |\n| p98       | double  | TP98: The http-request execution duration in milliseconds for 98% user.                                |\n| p99       | double  | TP99: The http-request execution duration in milliseconds for 99% user.                                |\n\n#### JDBC Statement\nJDBC Statement schema describes key metrics of JDBC SQL Statement, which include:\n* Execution count (cnt, errcnt)\n* Throughput (m1, m5, m15)\n* Error throughput (m1err, m5err, m15err)\n* Latency (p25, p50, p75, p95, p98, p99, p999)\n* Execution duration (min, mean, max)\n\n| Field     |  Type   | Description                                                                                           |\n| :-------- | :-----: | :---------------------------------------------------------------------------------------------------- |\n| signature | string  | Executed JDBC method signature.                                                                       |\n| cnt       | integer | The total count of JDBC method executed                                                               |\n| errcnt    | integer | The total error count of JDBC method executed                                                         |\n| m1        | double  | The JDBC method executions per second (exponentially-weighted moving average) in last 1 minute.       |\n| m5        | double  | The JDBC method executions per second (exponentially-weighted moving average) in last 5 minutes.      |\n| m15       | double  | The JDBC method executions per second (exponentially-weighted moving average) in last 15 minutes.     |\n| m1err     | double  | The JDBC method error executions per second (exponentially-weighted moving average) in last 1 minute  |\n| m5err     | double  | The JDBC method error executions per second (exponentially-weighted moving average) in last 5 minute. |\n| m15err    | double  | The JDBC method error executions per second (exponentially-weighted moving average) in last 15 minute |\n| min       | double  | The JDBC method minimal execution duration in milliseconds.                                           |\n| max       | double  | The JDBC method maximal execution duration in milliseconds.                                           |\n| mean      | double  | The JDBC method mean execution duration in milliseconds.                                              |\n| p25       | double  | TP25: The JDBC method execution duration in milliseconds for 25% user.                                |\n| p50       | double  | TP50: The JDBC method execution duration in milliseconds for 50% user.                                |\n| p75       | double  | TP75: The JDBC method execution duration in milliseconds for 75% user.                                |\n| p95       | double  | TP95: The JDBC method execution duration in milliseconds for 95% user.                                |\n| p98       | double  | TP98: The JDBC method execution duration in milliseconds for 98% user.                                |\n| p99       | double  | TP99: The JDBC method execution duration in milliseconds for 99% user.                                |\n| p999      | double  | TP99.9: The JDBC method execution duration in milliseconds for 99.9% user.                            |\n\n#### JDBC Connection\nJDBC Connection schema describes key metrics of Getting Connection, which include:\n* Execution count (cnt, errcnt)\n* Throughput (m1, m5, m15)\n* Error throughput (m1err, m5err, m15err)\n* Latency (p25, p50, p75, p95, p98, p99, p999)\n* Execution duration (min, mean, max)\n\n| Field  |  Type   | Description                                                                                               |\n| :----- | :-----: | :-------------------------------------------------------------------------------------------------------- |\n| url    | string  | The url of database connections                                                                           |\n| cnt    | integer | The total number of database connections                                                                  |\n| errcnt | integer | The total error number of database connections                                                            |\n| m1     | double  | The JDBC connection establishment per second (exponentially-weighted moving average) in last 1 minute.    |\n| m5     | double  | The JDBC connection establishment per second (exponentially-weighted moving average) in last 5 minutes.   |\n| m15    | double  | The JDBC connection establishment per second (exponentially-weighted moving average) in last 15 minutes.  |\n| m1err  | double  | The JDBC connection error executions per second (exponentially-weighted moving average) in last 1 minute  |\n| m5err  | double  | The JDBC connection error executions per second (exponentially-weighted moving average) in last 5 minute. |\n| m15err | double  | The JDBC connection error executions per second (exponentially-weighted moving average) in last 15 minute |\n| min    | double  | The JDBC connection minimal establishment duration in milliseconds.                                       |\n| max    | double  | The JDBC connection maximal establishment duration in milliseconds.                                       |\n| mean   | double  | The JDBC connection mean establishment duration in milliseconds.                                          |\n| p25    | double  | TP25: The JDBC connection establishment duration in milliseconds for 25% user.                            |\n| p50    | double  | TP50: The JDBC connection establishment duration in milliseconds for 50% user.                            |\n| p75    | double  | TP75: The JDBC connection establishment duration in milliseconds for 75% user.                            |\n| p95    | double  | TP95: The JDBC connection establishment duration in milliseconds for 95% user.                            |\n| p98    | double  | TP98: The JDBC connection establishment duration in milliseconds for 98% user.                            |\n| p99    | double  | TP99: The JDBC connection establishment duration in milliseconds for 99% user.                            |\n| p999   | double  | TP99.9: The JDBC connection establishment duration in milliseconds for 99.9% user.                        |\n\n#### JVM Memory\nJVM Memory schema describes key metrics of Java memory usage, which include:\n* bytes-init\n* bytes-used\n* bytes-committed\n* bytes-max\n\n\n| Field           |  Type   | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |\n| :-------------- | :-----: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| resource        | String  | memory pool name                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |\n| bytes-init      | integer | The value represents the initial amount of memory in bytes unit that the JVM requests from the operating system for memory management during startup. The JVM may request additional memory from the operating system and may also release memory to the system over time. The value of init may be undefined (value -1).                                                                                                                                                                                            |\n| bytes-used      | integer | The value represents the amount of memory currently used in bytes unit.                                                                                                                                                                                                                                                                                                                                                                                                                                              |\n| bytes-committed | integer | The value represents the amount of memory in bytes unit that is guaranteed to be available for use by the JVM. The amount of committed memory may change over time (increase or decrease). The JVM may release memory to the system and committed could be less than init. Value committed will always be greater than or equal to used.                                                                                                                                                                             |\n| bytes-max       | integer | The value represents the maximum amount of memory in bytes unit that can be used for memory management. Its value may be undefined (value -1). The maximum amount of memory may change over time if defined. The amount of used and committed memory will always be less than or equal to max if max is defined. A memory allocation may fail if it attempts to increase the used memory such that used > committed even if used <= max would still be true (for example, when the system is low on virtual memory). |\n\n#### JVM GC\nJVM GC schema describes key metrics of JVM garbage collection, which include:\n* total_collection_time\n* times\n* times_rate\n\n| Field                 |  Type   | Description                                                                               |\n| :-------------------- | :-----: | :---------------------------------------------------------------------------------------- |\n| resource              | string  | gc name                                                                                   |\n| total_collection_time | integer | The value represents the total time for garbage collection operation in millisecond unit. |\n| times                 | integer | The value represents the total garbage collection times.                                  |\n| times_rate            | integer | The number of gc times per second.                                                        |\n\n#### Kafka Client\nKafka Client schema describes key metrics of Kafka client invoking, which include:\n* Producer\n  * Throughput (prodrm1, prodrm5, prodrm15)\n  * Error throughput (prodrm1err, prodrm5err, prodrm15err)\n  * Execution duration (prodrmin, prodrmean, prodrmax)\n  * Latency (prodrp25, prodrp50, prodrp75, prodrp95, prodrp98, prodrp99, prodrp999)\n* Consumer\n  * Throughput (consrm1, consrm5, consrm15)\n  * Error throughput (consrm1err, consrm5err, consrm15err)\n  * Execution duration (consrmin, consrmean, consrmax)\n  * Latency (consrp25, consrp50, consrp75, consrp95, consrp98, consrp99, consrp999)\n\n| Field      |  Type  | Description                                                                                          |\n| :--------- | :----: | :--------------------------------------------------------------------------------------------------- |\n| resource   | string | topic name                                                                                           |\n| prodrm1    | double | The executions per second (exponentially-weighted moving average) in last 1 minute (producer)        |\n| prodrm5    | double | The executions per second (exponentially-weighted moving average) in last 5 minute (producer)        |\n| prodrm15   | double | The executions per second (exponentially-weighted moving average) in last 15 minute (producer)       |\n| consrm1    | double | The executions per second (exponentially-weighted moving average) in last 1 minute (consumer)        |\n| consrm5    | double | The executions per second (exponentially-weighted moving average) in last 5 minute (consumer)        |\n| consrm15   | double | The executions per second (exponentially-weighted moving average) in last 15 minute (consumer)       |\n| prodrm1err | double | The error executions per second (exponentially-weighted moving average) in last 1 minute (producer)  |\n| prodrm5err | double | The executions per second (exponentially-weighted moving average) in last 5 minute (producer)        |\n| prodrm5err | double | The error executions per second (exponentially-weighted moving average) in last 15 minute (producer) |\n| consrm1err | double | The error executions per second (exponentially-weighted moving average) in last 1 minute (consumer)  |\n| consrm5err | double | The error executions per second (exponentially-weighted moving average) in last 5 minute (consumer)  |\n| consrm5err | double | The error executions per second (exponentially-weighted moving average) in last 15 minute (consumer) |\n| prodrmin   | double | The minimal execution duration in milliseconds.                                                      |\n| prodrmax   | double | The maximal execution duration in milliseconds.                                                      |\n| prodrmean  | double | The mean execution duration in milliseconds.                                                         |\n| prodrp25   | double | TP25: The execution duration in milliseconds for 25% user.                                           |\n| prodrp50   | double | TP50: The execution duration in milliseconds for 50% user.                                           |\n| prodrp75   | double | TP75: The execution duration in milliseconds for 75% user.                                           |\n| prodrp95   | double | TP95: The execution duration in milliseconds for 95% user.                                           |\n| prodrp98   | double | TP98: The execution duration in milliseconds for 98% user.                                           |\n| prodrp99   | double | TP99: The execution duration in milliseconds for 99% user.                                           |\n| prodrp999  | double | TP99.9: The execution duration in milliseconds for 99.9% user.                                       |\n| consrmin   | double | The minimal execution duration in milliseconds.                                                      |\n| consrmax   | double | The maximal execution duration in milliseconds.                                                      |\n| consrmean  | double | The mean execution duration in milliseconds.                                                         |\n| consrp25   | double | TP25: The execution duration in milliseconds for 25% user.                                           |\n| consrp50   | double | TP50: The execution duration in milliseconds for 50% user.                                           |\n| consrp75   | double | TP75: The execution duration in milliseconds for 75% user.                                           |\n| consrp95   | double | TP95: The execution duration in milliseconds for 95% user.                                           |\n| consrp98   | double | TP98: The execution duration in milliseconds for 98% user.                                           |\n| consrp99   | double | TP99: The execution duration in milliseconds for 99% user.                                           |\n| consrp999  | double | TP99.9: The execution duration in milliseconds for 99.9% user.                                       |\n\n#### RabbitMQ Producer\nRabbitMQ Producer schema describes key metrics of RabbitMQ client publishing message, which include:\n* Throughput (prodrm1, prodrm5, prodrm15)\n* Error throughput (prodrm1err, prodrm5err, prodrm15err)\n* Execution duration (min, mean, max)\n* Latency (p25, p50, p75, p95, p98, p99)\n\n| Field      |  Type  | Description                                                                                          |\n| :--------- | :----: | :--------------------------------------------------------------------------------------------------- |\n| resource   | string | rabbitmq exchange or routingkey                                                                      |\n| prodrm1    | double | The executions of producer per second (exponentially-weighted moving average) in last 1 minute       |\n| prodrm5    | double | The executions of producer per second (exponentially-weighted moving average) in last 5 minute       |\n| prodrm15   | double | The executionsof producer per second (exponentially-weighted moving average) in last 15 minute       |\n| prodrm1err | double | The error executions per second (exponentially-weighted moving average) in last 1 minute (producer)  |\n| prodrm5err | double | The executions per second (exponentially-weighted moving average) in last 5 minute (producer)        |\n| prodrm5err | double | The error executions per second (exponentially-weighted moving average) in last 15 minute (producer) |\n| min        | double | The producer minimal execution duration in milliseconds.                                             |\n| max        | double | The producer maximal execution duration in milliseconds.                                             |\n| mean       | double | The producer mean execution duration in milliseconds.                                                |\n| p25        | double | TP25: The producer execution duration in milliseconds for 25% user.                                  |\n| p50        | double | TP50: The producer execution duration in milliseconds for 50% user.                                  |\n| p75        | double | TP75: The producer execution duration in milliseconds for 75% user.                                  |\n| p95        | double | TP95: The producer execution duration in milliseconds for 95% user.                                  |\n| p98        | double | TP98: The producer execution duration in milliseconds for 98% user.                                  |\n| p99        | double | TP99: The producer execution duration in milliseconds for 99% user.                                  |\n| p999       | double | TP99.9: The execution duration in milliseconds for 99.9% user.                                       |\n\n#### RabbitMQ Consumer\nRabbitMQ Consumer schema describes key metrics of RabbitMQ client consuming message, which include:\n* Throughput (queue_m1_rate, queue_m5_rate, queue_m15_rate)\n* Error throughput (queue_m1_error_rate, queue_m5_error_rate, queue_m15_error_rate)\n* Execution duration (min, mean, max)\n* Latency (p25, p50, p75, p95, p98, p99)\n\n| Field                |  Type  | Description                                                                                       |\n| :------------------- | :----: | :------------------------------------------------------------------------------------------------ |\n| resource             | string | rabbitmq routingKey                                                                               |\n| queue_m1_rate        | double | The executions of queue per second (exponentially-weighted moving average) in last 1 minute       |\n| queue_m5_rate        | double | The executions of queue per second (exponentially-weighted moving average) in last 5 minute       |\n| queue_m15_rate       | double | The executionsof queue per second (exponentially-weighted moving average) in last 15 minute       |\n| queue_m1_error_rate  | double | The error executions per second (exponentially-weighted moving average) in last 1 minute (queue)  |\n| queue_m5_error_rate  | double | The error executions per second (exponentially-weighted moving average) in last 5 minute (queue)  |\n| queue_m15_error_rate | double | The error executions per second (exponentially-weighted moving average) in last 15 minute (queue) |\n| min                  | double | The consumer minimal execution duration in milliseconds.                                          |\n| max                  | double | The consumer maximal execution duration in milliseconds.                                          |\n| mean                 | double | The consumer mean execution duration in milliseconds.                                             |\n| p25                  | double | TP25: The consumer execution duration in milliseconds for 25% user.                               |\n| p50                  | double | TP50: The consumer execution duration in milliseconds for 50% user.                               |\n| p75                  | double | TP75: The consumer execution duration in milliseconds for 75% user.                               |\n| p95                  | double | TP95: The consumer execution duration in milliseconds for 95% user.                               |\n| p98                  | double | TP98: The consumer execution duration in milliseconds for 98% user.                               |\n| p99                  | double | TP99: The consumer execution duration in milliseconds for 99% user.                               |\n| p999                 | double | TP99.9: The execution duration in milliseconds for 99.9% user.                                    |\n\n#### Spring AMQP on Message Listener\nMessage Listener schema describes key metrics of Spring AMQP RabbitMQ Message Queue, which include:\n* Throughput (queue_m1_rate, queue_m5_rate, queue_m15_rate)\n* Error throughput (queue_m1_error_rate, queue_m5_error_rate, queue_m15_error_rate)\n* Execution duration (min, mean, max)\n* Latency (p25, p50, p75, p95, p98, p99)\n\n| Field                |  Type  | Description                                                                                       |\n| :------------------- | :----: | :------------------------------------------------------------------------------------------------ |\n| resource             | string | rabbitmq queue                                                                                    |\n| queue_m1_rate        | double | The executions of queue per second (exponentially-weighted moving average) in last 1 minute       |\n| queue_m5_rate        | double | The executions of queue per second (exponentially-weighted moving average) in last 5 minute       |\n| queue_m15_rate       | double | The executionsof queue per second (exponentially-weighted moving average) in last 15 minute       |\n| queue_m1_error_rate  | double | The error executions per second (exponentially-weighted moving average) in last 1 minute (queue)  |\n| queue_m5_error_rate  | double | The error executions per second (exponentially-weighted moving average) in last 5 minute (queue)  |\n| queue_m15_error_rate | double | The error executions per second (exponentially-weighted moving average) in last 15 minute (queue) |\n| min                  | double | The AMQP Message Listener minimal execution duration in milliseconds.                             |\n| max                  | double | The AMQP Message Listener maximal execution duration in milliseconds.                             |\n| mean                 | double | The AMQP Message Listener mean execution duration in milliseconds.                                |\n| p25                  | double | TP25: The AMQP Message Listener execution duration in milliseconds for 25% user.                  |\n| p50                  | double | TP50: The AMQP Message Listener execution duration in milliseconds for 50% user.                  |\n| p75                  | double | TP75: The AMQP Message Listener execution duration in milliseconds for 75% user.                  |\n| p95                  | double | TP95: The AMQP Message Listener execution duration in milliseconds for 95% user.                  |\n| p98                  | double | TP98: The AMQP Message Listener execution duration in milliseconds for 98% user.                  |\n| p99                  | double | TP99: The AMQP Message Listener execution duration in milliseconds for 99% user.                  |\n| p999                 | double | TP99.9: The execution duration in milliseconds for 99.9% user.                                    |\n\n#### Elasticsearch\nElasticsearch schema describes key metrics of Elasticsearch client invoking, which include:\n* Total execution count (cnt, errcnt, m1cnt, m5cnt, m15cnt)\n* Throughput (m1, m5, m15, mean_rate)\n* Error throughput (m1err, m5err, m15err)\n* Execution duration (min, mean, max)\n* Latency (p25, p50, p75, p95, p98, p99)\n\n| Field     |  Type   | Description                                                                                                     |\n| :-------- | :-----: | :-------------------------------------------------------------------------------------------------------------- |\n| index     | string  | The Elasticsearch index name                                                                                    |\n| cnt       | integer | The total count of the request executed                                                                         |\n| errcnt    | integer | The total error count of the request executed                                                                   |\n| m1cnt     | integer | The total count of the request executed in last 1 minute                                                        |\n| m5cnt     | integer | The total count of the request executed in last 5 minute                                                        |\n| m15cnt    | integer | The total count of the request executed in last 15 minute                                                       |\n| m1        | double  | The Elasticsearch request executions per second (exponentially-weighted moving average) in last 1 minute        |\n| m5        | double  | The Elasticsearch request executions per second (exponentially-weighted moving average) in last 5 minute.       |\n| m15       | double  | The Elasticsearch request executions per second (exponentially-weighted moving average) in last 15 minute.      |\n| mean_rate | double  | The Elasticsearch request executions per second (exponentially-weighted moving average) in last 15 minute.      |\n| m1err     | double  | The Elasticsearch error request executions per second (exponentially-weighted moving average) in last 1 minute  |\n| m5err     | double  | The Elasticsearch error request executions per second (exponentially-weighted moving average) in last 5 minute. |\n| m15err    | double  | The Elasticsearch error request executions per second (exponentially-weighted moving average) in last 15 minute |\n| min       | double  | The Elasticsearch minimal execution duration in milliseconds.                                                   |\n| max       | double  | The Elasticsearch maximal execution duration in milliseconds.                                                   |\n| mean      | double  | The Elasticsearch mean execution duration in milliseconds.                                                      |\n| p25       | double  | TP25: The Elasticsearch execution duration in milliseconds for 25% user.                                        |\n| p50       | double  | TP50: The Elasticsearch execution duration in milliseconds for 50% user.                                        |\n| p75       | double  | TP75: The Elasticsearch execution duration in milliseconds for 75% user.                                        |\n| p95       | double  | TP95: The Elasticsearch execution duration in milliseconds for 95% user.                                        |\n| p98       | double  | TP98: The Elasticsearch execution duration in milliseconds for 98% user.                                        |\n| p99       | double  | TP99: The Elasticsearch execution duration in milliseconds for 99% user.                                        |\n\n#### MongoDB\nMongoDB schema describes key metrics of MongoDB client invoking, which include:\n* Total execution count (cnt, errcnt, m1cnt, m5cnt, m15cnt)\n* Throughput (m1, m5, m15, mean_rate)\n* Error throughput (m1err, m5err, m15err)\n* Execution duration (min, mean, max)\n* Latency (p25, p50, p75, p95, p98, p99)\n\n| Field     |  Type   | Description                                                                                               |\n| :-------- | :-----: | :-------------------------------------------------------------------------------------------------------- |\n| operation | string  | The MongoDB request command name                                                                          |\n| cnt       | integer | The total count of the request executed                                                                   |\n| errcnt    | integer | The total error count of the request executed                                                             |\n| m1cnt     | integer | The total count of the request executed in last 1 minute                                                  |\n| m5cnt     | integer | The total count of the request executed in last 5 minute                                                  |\n| m15cnt    | integer | The total count of the request executed in last 15 minute                                                 |\n| m1        | double  | The MongoDB request executions per second (exponentially-weighted moving average) in last 1 minute        |\n| m5        | double  | The MongoDB request executions per second (exponentially-weighted moving average) in last 5 minute.       |\n| m15       | double  | The MongoDB request executions per second (exponentially-weighted moving average) in last 15 minute.      |\n| mean_rate | double  | The MongoDB request executions per second (exponentially-weighted moving average) in last 15 minute.      |\n| m1err     | double  | The MongoDB error request executions per second (exponentially-weighted moving average) in last 1 minute  |\n| m5err     | double  | The MongoDB error request executions per second (exponentially-weighted moving average) in last 5 minute. |\n| m15err    | double  | The MongoDB error request executions per second (exponentially-weighted moving average) in last 15 minute |\n| min       | double  | The MongoDB minimal execution duration in milliseconds.                                                   |\n| max       | double  | The MongoDB maximal execution duration in milliseconds.                                                   |\n| mean      | double  | The MongoDB mean execution duration in milliseconds.                                                      |\n| p25       | double  | TP25: The MongoDB execution duration in milliseconds for 25% user.                                        |\n| p50       | double  | TP50: The MongoDB execution duration in milliseconds for 50% user.                                        |\n| p75       | double  | TP75: The MongoDB execution duration in milliseconds for 75% user.                                        |\n| p95       | double  | TP95: The MongoDB execution duration in milliseconds for 95% user.                                        |\n| p98       | double  | TP98: The MongoDB execution duration in milliseconds for 98% user.                                        |\n| p99       | double  | TP99: The MongoDB execution duration in milliseconds for 99% user.                                        |\n\n\n#### Dubbo\nDubbo schema describes key metrics of Dubbo client invoking, which include:\n* Total execution count (cnt, errcnt, m1cnt, m5cnt, m15cnt)\n* Throughput (m1, m5, m15, mean_rate)\n* Error throughput (m1err, m5err, m15err)\n* Execution duration (min, mean, max)\n* Latency (p25, p50, p75, p95, p98, p99, p999)\n\n| Field     |  Type   | Description                                                                                            |\n|:----------| :-----: |:-------------------------------------------------------------------------------------------------------|\n| interface | string  | Dubbo full method signature.                                                                 |\n| cnt       | integer | The total count of the Dubbo method executed                                                           |\n| errcnt    | integer | The total error count of the Dubbo method executed                                                     |\n| m1cnt     | integer | The total count of the Dubbo method executed in last 1 minute                                          |\n| m5cnt     | integer | The total count of the Dubbo method executed in last 5 minute                                          |\n| m15cnt    | integer | The total count of the Dubbo method executed in last 15 minute                                         |\n| m1        | double  | The Dubbo method executions per second (exponentially-weighted moving average) in last 1 minute        |\n| m5        | double  | The Dubbo method executions per second (exponentially-weighted moving average) in last 5 minute.       |\n| m15       | double  | The Dubbo method executions per second (exponentially-weighted moving average) in last 15 minute.      |\n| mean_rate | double  | The Dubbo method executions per second (exponentially-weighted moving average) in last 15 minute.      |\n| m1err     | double  | The Dubbo method error executions per second (exponentially-weighted moving average) in last 1 minute  |\n| m5err     | double  | The Dubbo method error executions per second (exponentially-weighted moving average) in last 5 minute. |\n| m15err    | double  | The Dubbo method error executions per second (exponentially-weighted moving average) in last 15 minute |\n| min       | double  | The Dubbo method minimal execution duration in milliseconds.                                           |\n| max       | double  | The Dubbo method maximal execution duration in milliseconds.                                           |\n| mean      | double  | The Dubbo method mean execution duration in milliseconds.                                              |\n| p25       | double  | TP25: The Dubbo method execution duration in milliseconds for 25% user.                                |\n| p50       | double  | TP50: The Dubbo method execution duration in milliseconds for 50% user.                                |\n| p75       | double  | TP75: The Dubbo method execution duration in milliseconds for 75% user.                                |\n| p95       | double  | TP95: The Dubbo method execution duration in milliseconds for 95% user.                                |\n| p98       | double  | TP98: The Dubbo method execution duration in milliseconds for 98% user.                                |\n| p99       | double  | TP99: The Dubbo method execution duration in milliseconds for 99% user.                                |\n| p999      | double  | TP999: The Dubbo method execution duration in milliseconds for 99.9% user.                             |\n\n#### Motan\nMotan schema describes key metrics of Motan client invoking, which include:\n* Total execution count (cnt, errcnt, m1cnt, m5cnt, m15cnt)\n* Throughput (m1, m5, m15, mean_rate)\n* Error throughput (m1err, m5err, m15err)\n* Execution duration (min, mean, max)\n* Latency (p25, p50, p75, p95, p98, p99, p999)\n\n| Field     |  Type   | Description                                                                                            |\n|:----------| :-----: |:-------------------------------------------------------------------------------------------------------|\n| interface | string  | Motan full method signature.                                                                           |\n| cnt       | integer | The total count of the Motan method executed                                                           |\n| errcnt    | integer | The total error count of the Motan method executed                                                     |\n| m1cnt     | integer | The total count of the Motan method executed in last 1 minute                                          |\n| m5cnt     | integer | The total count of the Motan method executed in last 5 minute                                          |\n| m15cnt    | integer | The total count of the Motan method executed in last 15 minute                                         |\n| m1        | double  | The Motan method executions per second (exponentially-weighted moving average) in last 1 minute        |\n| m5        | double  | The Motan method executions per second (exponentially-weighted moving average) in last 5 minute.       |\n| m15       | double  | The Motan method executions per second (exponentially-weighted moving average) in last 15 minute.      |\n| mean_rate | double  | The Motan method executions per second (exponentially-weighted moving average) in last 15 minute.      |\n| m1err     | double  | The Motan method error executions per second (exponentially-weighted moving average) in last 1 minute  |\n| m5err     | double  | The Motan method error executions per second (exponentially-weighted moving average) in last 5 minute. |\n| m15err    | double  | The Motan method error executions per second (exponentially-weighted moving average) in last 15 minute |\n| min       | double  | The Motan method minimal execution duration in milliseconds.                                           |\n| max       | double  | The Motan method maximal execution duration in milliseconds.                                           |\n| mean      | double  | The Motan method mean execution duration in milliseconds.                                              |\n| p25       | double  | TP25: The Motan method execution duration in milliseconds for 25% user.                                |\n| p50       | double  | TP50: The Motan method execution duration in milliseconds for 50% user.                                |\n| p75       | double  | TP75: The Motan method execution duration in milliseconds for 75% user.                                |\n| p95       | double  | TP95: The Motan method execution duration in milliseconds for 95% user.                                |\n| p98       | double  | TP98: The Motan method execution duration in milliseconds for 98% user.                                |\n| p99       | double  | TP99: The Motan method execution duration in milliseconds for 99% user.                                |\n| p999      | double  | TP999: The Motan method execution duration in milliseconds for 99.9% user.                             |\n\n#### SOFARPC\nSOFARPC schema describes key metrics of SOFARPC client invoking, which include:\n* Total execution count (cnt, errcnt, m1cnt, m5cnt, m15cnt)\n* Throughput (m1, m5, m15, mean_rate)\n* Error throughput (m1err, m5err, m15err)\n* Execution duration (min, mean, max)\n* Latency (p25, p50, p75, p95, p98, p99, p999)\n\n| Field     |  Type   | Description                                                                                            |\n|:----------| :-----: |:-------------------------------------------------------------------------------------------------------|\n| interface | string  | SOFARPC method signature.                                                                                |\n| cnt       | integer | The total count of the SOFARPC method executed                                                           |\n| errcnt    | integer | The total error count of the SOFARPC method executed                                                     |\n| m1cnt     | integer | The total count of the SOFARPC method executed in last 1 minute                                          |\n| m5cnt     | integer | The total count of the SOFARPC method executed in last 5 minute                                          |\n| m15cnt    | integer | The total count of the SOFARPC method executed in last 15 minute                                         |\n| m1        | double  | The SOFARPC method executions per second (exponentially-weighted moving average) in last 1 minute        |\n| m5        | double  | The SOFARPC method executions per second (exponentially-weighted moving average) in last 5 minute.       |\n| m15       | double  | The SOFARPC method executions per second (exponentially-weighted moving average) in last 15 minute.      |\n| mean_rate | double  | The SOFARPC method executions per second (exponentially-weighted moving average) in last 15 minute.      |\n| m1err     | double  | The SOFARPC method error executions per second (exponentially-weighted moving average) in last 1 minute  |\n| m5err     | double  | The SOFARPC method error executions per second (exponentially-weighted moving average) in last 5 minute. |\n| m15err    | double  | The SOFARPC method error executions per second (exponentially-weighted moving average) in last 15 minute |\n| min       | double  | The SOFARPC method minimal execution duration in milliseconds.                                           |\n| max       | double  | The SOFARPC method maximal execution duration in milliseconds.                                           |\n| mean      | double  | The SOFARPC method mean execution duration in milliseconds.                                              |\n| p25       | double  | TP25: The SOFARPC method execution duration in milliseconds for 25% user.                                |\n| p50       | double  | TP50: The SOFARPC method execution duration in milliseconds for 50% user.                                |\n| p75       | double  | TP75: The SOFARPC method execution duration in milliseconds for 75% user.                                |\n| p95       | double  | TP95: The SOFARPC method execution duration in milliseconds for 95% user.                                |\n| p98       | double  | TP98: The SOFARPC method execution duration in milliseconds for 98% user.                                |\n| p99       | double  | TP99: The SOFARPC method execution duration in milliseconds for 99% user.                                |\n| p999      | double  | TP999: The SOFARPC method execution duration in milliseconds for 99.9% user.                             |\n\n## Application Log\n\n"
  },
  {
    "path": "httpserver/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright (c) 2021, MegaEase\n  ~ All rights reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~   http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>easeagent</artifactId>\n        <groupId>com.megaease.easeagent</groupId>\n        <version>2.3.0</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>httpserver</artifactId>\n\n    <properties>\n        <maven.compiler.source>8</maven.compiler.source>\n        <maven.compiler.target>8</maven.compiler.target>\n    </properties>\n\n    <dependencies>\n        <dependency>\n            <groupId>org.apache.kafka</groupId>\n            <artifactId>kafka-clients</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>com.fasterxml.jackson.core</groupId>\n            <artifactId>jackson-core</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>com.fasterxml.jackson.core</groupId>\n            <artifactId>jackson-databind</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>plugin-api</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n\n        <!--\n        <dependency>\n            <groupId>org.nanohttpd</groupId>\n            <artifactId>nanohttpd</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.nanohttpd</groupId>\n            <artifactId>nanohttpd-nanolets</artifactId>\n        </dependency>\n        -->\n    </dependencies>\n</project>\n"
  },
  {
    "path": "httpserver/src/main/java/com/megaease/easeagent/httpserver/HttpRequest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.httpserver;\n\nimport lombok.Builder;\nimport lombok.Data;\nimport org.apache.kafka.common.header.Headers;\n\nimport java.io.InputStream;\n\n@Data\n@Builder\npublic class HttpRequest {\n    Headers headers;\n    String method;\n    String uri;\n    String remoteIp;\n    String remoteHostName;\n    InputStream input;\n}\n"
  },
  {
    "path": "httpserver/src/main/java/com/megaease/easeagent/httpserver/HttpResponse.java",
    "content": "/*\n *   Copyright (c) 2017, MegaEase\n *   All rights reserved.\n *\n *   Licensed under the Apache License, Version 2.0 (the \"License\");\n *   you may not use this file except in compliance with the License.\n *   You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n *   Unless required by applicable law or agreed to in writing, software\n *   distributed under the License is distributed on an \"AS IS\" BASIS,\n *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *   See the License for the specific language governing permissions and\n *   limitations under the License.\n */\n\npackage com.megaease.easeagent.httpserver;\n\nimport lombok.Builder;\nimport lombok.Data;\n\n@Data\n@Builder\npublic class HttpResponse {\n    private int statusCode;\n    private String data;\n}\n"
  },
  {
    "path": "httpserver/src/main/java/com/megaease/easeagent/httpserver/IHttpHandler.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.httpserver;\n\nimport java.util.Map;\n\npublic interface IHttpHandler {\n    String getPath();\n\n    HttpResponse process(HttpRequest request, Map<String, String> uriParams);\n\n    default int priority() {\n        return 100;\n    }\n}\n"
  },
  {
    "path": "httpserver/src/main/java/com/megaease/easeagent/httpserver/IHttpServer.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.httpserver;\n\nimport java.util.List;\n\npublic interface IHttpServer {\n    void start(int port);\n\n    void addHttpRoutes(List<IHttpHandler> agentHttpHandlers);\n\n    void stop();\n}\n"
  },
  {
    "path": "httpserver/src/main/java/com/megaease/easeagent/httpserver/jdk/AgentHttpServerV2.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.httpserver.jdk;\n\nimport com.megaease.easeagent.httpserver.IHttpHandler;\nimport com.megaease.easeagent.httpserver.IHttpServer;\nimport com.megaease.easeagent.plugin.async.AgentThreadFactory;\nimport com.sun.net.httpserver.HttpServer;\n\nimport java.io.IOException;\nimport java.net.InetSocketAddress;\nimport java.util.List;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\n\npublic class AgentHttpServerV2 implements IHttpServer {\n    RootContextHandler httpRootHandler;\n    HttpServer server;\n\n    @Override\n    public void start(int port) {\n        try {\n            this.server = HttpServer.create(new InetSocketAddress(\"localhost\", port), 0);\n            this.httpRootHandler = new RootContextHandler();\n            this.server.createContext(\"/\", this.httpRootHandler);\n\n            ExecutorService threadPoolExecutor = Executors.newFixedThreadPool(1, new AgentThreadFactory());\n            this.server.setExecutor(threadPoolExecutor);\n\n            this.server.start();\n        } catch (IOException ex) {\n        }\n    }\n\n    @Override\n    public void addHttpRoutes(List<IHttpHandler> agentHttpHandlers) {\n        agentHttpHandlers.forEach(\n            handler -> {\n                this.httpRootHandler.addRoute(handler);\n            }\n        );\n    }\n\n    @Override\n    public void stop() {\n        this.server.stop(0);\n    }\n}\n"
  },
  {
    "path": "httpserver/src/main/java/com/megaease/easeagent/httpserver/jdk/RootContextHandler.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.httpserver.jdk;\n\nimport com.megaease.easeagent.httpserver.IHttpHandler;\nimport com.sun.net.httpserver.HttpExchange;\nimport com.sun.net.httpserver.HttpHandler;\n\nimport java.io.IOException;\nimport java.net.InetSocketAddress;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.Iterator;\nimport java.util.PriorityQueue;\nimport java.util.concurrent.CopyOnWriteArrayList;\n\npublic class RootContextHandler implements HttpHandler {\n    CopyOnWriteArrayList<IHttpHandler> handlers = new CopyOnWriteArrayList<>();\n    DefaultRoutes routes = new DefaultRoutes();\n\n    @Override\n    public void handle(HttpExchange exchange) throws IOException {\n        String uri = exchange.getRequestURI().getPath();\n        String method = exchange.getRequestMethod();\n        InetSocketAddress inetRemote = exchange.getRemoteAddress();\n        exchange.getRequestBody();\n        exchange.getRequestHeaders();\n    }\n\n    public void addRoute(IHttpHandler handler) {\n        this.routes.addRoute(handler);\n    }\n\n    public static class DefaultRoutes {\n        protected final Collection<UriResource> mappings;\n\n        public DefaultRoutes() {\n            this.mappings = newMappingCollection();\n        }\n\n        public void addRoute(String url, int priority, Class<?> handler, Object... initParameter) {\n            if (url != null) {\n                if (handler != null) {\n                    mappings.add(new UriResource(url, priority + mappings.size(), handler, initParameter));\n                }\n            }\n        }\n\n        public void addRoute(IHttpHandler handler) {\n            this.addRoute(handler.getPath(), handler.priority(), handler.getClass());\n        }\n\n        public void removeRoute(String url) {\n            String uriToDelete = UriResource.normalizeUri(url);\n            Iterator<UriResource> iter = mappings.iterator();\n            while (iter.hasNext()) {\n                UriResource uriResource = iter.next();\n                if (uriToDelete.equals(uriResource.getUri())) {\n                    iter.remove();\n                    break;\n                }\n            }\n        }\n\n        public Collection<UriResource> getPrioritizedRoutes() {\n            return Collections.unmodifiableCollection(mappings);\n        }\n\n        protected Collection<UriResource> newMappingCollection() {\n            return new PriorityQueue<UriResource>();\n        }\n    }\n}\n"
  },
  {
    "path": "httpserver/src/main/java/com/megaease/easeagent/httpserver/jdk/UriResource.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.httpserver.jdk;\n\nimport com.megaease.easeagent.plugin.api.logging.Logger;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\n\nimport java.util.*;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n@SuppressWarnings(\"all\")\npublic class UriResource implements Comparable<UriResource> {\n    Logger LOG = EaseAgent.getLogger(UriResource.class);\n\n    private static final Pattern PARAM_PATTERN = Pattern.compile(\"(?<=(^|/)):[a-zA-Z0-9_-]+(?=(/|$))\");\n\n    private static final String PARAM_MATCHER = \"([A-Za-z0-9\\\\-\\\\._~:/?#\\\\[\\\\]@!\\\\$&'\\\\(\\\\)\\\\*\\\\+,;=\\\\s]+)\";\n\n    private static final Map<String, String> EMPTY = Collections.unmodifiableMap(new HashMap<>());\n\n    private final String uri;\n\n    private final Pattern uriPattern;\n\n    private int priority;\n\n    private final Class<?> handler;\n\n    private final Object[] initParameter;\n\n    private final List<String> uriParams = new ArrayList<>();\n\n    public UriResource(String uri, int priority, Class<?> handler, Object... initParameter) {\n        this(uri, handler, initParameter);\n        this.priority = priority + uriParams.size() * 1000;\n    }\n\n    public UriResource(String uri, Class<?> handler, Object... initParameter) {\n        this.handler = handler;\n        this.initParameter = initParameter;\n        if (uri != null) {\n            this.uri = normalizeUri(uri);\n            parse();\n            this.uriPattern = createUriPattern();\n        } else {\n            this.uriPattern = null;\n            this.uri = null;\n        }\n    }\n\n    private void parse() {\n    }\n\n    private Pattern createUriPattern() {\n        String patternUri = uri;\n        Matcher matcher = PARAM_PATTERN.matcher(patternUri);\n        int start = 0;\n        while (matcher.find(start)) {\n            uriParams.add(patternUri.substring(matcher.start() + 1, matcher.end()));\n            patternUri = new StringBuilder(patternUri.substring(0, matcher.start()))//\n                .append(PARAM_MATCHER)//\n                .append(patternUri.substring(matcher.end())).toString();\n            start = matcher.start() + PARAM_MATCHER.length();\n            matcher = PARAM_PATTERN.matcher(patternUri);\n        }\n        return Pattern.compile(patternUri);\n    }\n\n    @Override\n    public String toString() {\n        return new StringBuilder(\"UrlResource{uri='\").append((uri == null ? \"/\" : uri))//\n            .append(\"', urlParts=\").append(uriParams)//\n            .append('}')//\n            .toString();\n    }\n\n    public String getUri() {\n        return uri;\n    }\n\n    public <T> T initParameter(Class<T> paramClazz) {\n        return initParameter(0, paramClazz);\n    }\n\n    public <T> T initParameter(int parameterIndex, Class<T> paramClazz) {\n        if (initParameter.length > parameterIndex) {\n            return paramClazz.cast(initParameter[parameterIndex]);\n        }\n        LOG.error(\"init parameter index not available \" + parameterIndex);\n        return null;\n    }\n\n    public Map<String, String> match(String url) {\n        Matcher matcher = uriPattern.matcher(url);\n        if (matcher.matches()) {\n            if (uriParams.size() > 0) {\n                Map<String, String> result = new HashMap<String, String>();\n                for (int i = 1; i <= matcher.groupCount(); i++) {\n                    result.put(uriParams.get(i - 1), matcher.group(i));\n                }\n                return result;\n            } else {\n                return EMPTY;\n            }\n        }\n        return null;\n    }\n\n    @Override\n    public int compareTo(UriResource that) {\n        if (that == null) {\n            return 1;\n        } else if (this.priority > that.priority) {\n            return 1;\n        } else if (this.priority < that.priority) {\n            return -1;\n        } else {\n            return 0;\n        }\n    }\n\n    public void setPriority(int priority) {\n        this.priority = priority;\n    }\n\n\n    public static String normalizeUri(String value) {\n        if (value == null) {\n            return value;\n        }\n        if (value.startsWith(\"/\")) {\n            value = value.substring(1);\n        }\n        if (value.endsWith(\"/\")) {\n            value = value.substring(0, value.length() - 1);\n        }\n        return value;\n    }\n}\n"
  },
  {
    "path": "httpserver/src/main/java/com/megaease/easeagent/httpserver/nano/AgentHttpHandler.java",
    "content": "/*\n *   Copyright (c) 2017, MegaEase\n *   All rights reserved.\n *\n *   Licensed under the Apache License, Version 2.0 (the \"License\");\n *   you may not use this file except in compliance with the License.\n *   You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n *   Unless required by applicable law or agreed to in writing, software\n *   distributed under the License is distributed on an \"AS IS\" BASIS,\n *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *   See the License for the specific language governing permissions and\n *   limitations under the License.\n */\n\npackage com.megaease.easeagent.httpserver.nano;\n\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.IHTTPSession;\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.request.Method;\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.response.IStatus;\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.response.Response;\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.response.Status;\nimport com.megaease.easeagent.httpserver.nanohttpd.router.RouterNanoHTTPD;\nimport lombok.SneakyThrows;\n\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.*;\n\npublic abstract class AgentHttpHandler extends RouterNanoHTTPD.DefaultHandler {\n\n    public abstract String getPath();\n\n    protected String text;\n    protected Set<Method> methods = new HashSet<>(Arrays.asList(Method.PUT, Method.POST));\n\n    @Override\n    public String getText() {\n        return this.text;\n    }\n\n    @SneakyThrows\n    protected String buildRequestBody(IHTTPSession session) {\n        Map<String, String> files = new HashMap<>();\n        Method method = session.getMethod();\n        if (!methods.contains(method)) {\n            return null;\n        }\n        session.parseBody(files);\n        String content = files.get(\"content\");\n        if (content != null) {\n            Path path = Paths.get(content);\n            byte[] bytes = Files.readAllBytes(path);\n            return new String(bytes, StandardCharsets.UTF_8);\n        }\n        return files.get(\"postData\");\n    }\n\n    @Override\n    public IStatus getStatus() {\n        return Status.OK;\n    }\n\n    @Override\n    public String getMimeType() {\n        return null;\n    }\n\n    public abstract Response process(RouterNanoHTTPD.UriResource uriResource, Map<String, String> urlParams, IHTTPSession session);\n\n    @Override\n    public Response get(RouterNanoHTTPD.UriResource uriResource, Map<String, String> urlParams, IHTTPSession session) {\n        return this.process(uriResource, urlParams, session);\n    }\n}\n"
  },
  {
    "path": "httpserver/src/main/java/com/megaease/easeagent/httpserver/nano/AgentHttpHandlerProvider.java",
    "content": "/*\n *   Copyright (c) 2017, MegaEase\n *   All rights reserved.\n *\n *   Licensed under the Apache License, Version 2.0 (the \"License\");\n *   you may not use this file except in compliance with the License.\n *   You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n *   Unless required by applicable law or agreed to in writing, software\n *   distributed under the License is distributed on an \"AS IS\" BASIS,\n *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *   See the License for the specific language governing permissions and\n *   limitations under the License.\n */\n\npackage com.megaease.easeagent.httpserver.nano;\n\nimport java.util.List;\n\npublic interface AgentHttpHandlerProvider {\n\n    List<AgentHttpHandler> getAgentHttpHandlers();\n}\n"
  },
  {
    "path": "httpserver/src/main/java/com/megaease/easeagent/httpserver/nano/AgentHttpServer.java",
    "content": "/*\n *   Copyright (c) 2017, MegaEase\n *   All rights reserved.\n *\n *   Licensed under the Apache License, Version 2.0 (the \"License\");\n *   you may not use this file except in compliance with the License.\n *   You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n *   Unless required by applicable law or agreed to in writing, software\n *   distributed under the License is distributed on an \"AS IS\" BASIS,\n *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *   See the License for the specific language governing permissions and\n *   limitations under the License.\n */\n\npackage com.megaease.easeagent.httpserver.nano;\n\nimport com.megaease.easeagent.httpserver.nanohttpd.router.RouterNanoHTTPD;\nimport lombok.SneakyThrows;\n\nimport java.util.List;\n\npublic class AgentHttpServer extends RouterNanoHTTPD {\n\n    public static String JSON_TYPE = \"application/json\";\n\n    public AgentHttpServer(int port) {\n        super(port);\n        this.addMappings();\n        Runtime.getRuntime().addShutdownHook(new Thread(this::stop));\n    }\n\n    public void addHttpRoutes(List<AgentHttpHandler> agentHttpHandlers) {\n        for (AgentHttpHandler agentHttpHandler : agentHttpHandlers) {\n            this.addRoute(agentHttpHandler.getPath(), agentHttpHandler.getClass());\n        }\n    }\n\n    public void addHttpRoute(AgentHttpHandler handler) {\n        this.addRoute(handler.getPath(), handler.getClass());\n    }\n\n    @SneakyThrows\n    public void startServer() {\n        this.start(5000, true);\n    }\n}\n"
  },
  {
    "path": "httpserver/src/main/java/com/megaease/easeagent/httpserver/nanohttpd/protocols/http/ClientHandler.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.httpserver.nanohttpd.protocols.http;\n\n/*\n * #%L\n * NanoHttpd-Core\n * %%\n * Copyright (C) 2012 - 2016 nanohttpd\n * %%\n * Redistribution and use in source and binary forms, with or without modification,\n * are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice, this\n *    list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n *    this list of conditions and the following disclaimer in the documentation\n *    and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the nanohttpd nor the names of its contributors\n *    may be used to endorse or promote products derived from this software without\n *    specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,\n * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE\n * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED\n * OF THE POSSIBILITY OF SUCH DAMAGE.\n * #L%\n */\n\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.tempfiles.ITempFileManager;\n\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.net.Socket;\nimport java.net.SocketException;\nimport java.net.SocketTimeoutException;\nimport java.util.logging.Level;\n\n/**\n * The runnable that will be used for every new client connection.\n */\npublic class ClientHandler implements Runnable {\n\n    private final NanoHTTPD httpd;\n\n    private final InputStream inputStream;\n\n    private final Socket acceptSocket;\n\n    public ClientHandler(NanoHTTPD httpd, InputStream inputStream, Socket acceptSocket) {\n        this.httpd = httpd;\n        this.inputStream = inputStream;\n        this.acceptSocket = acceptSocket;\n    }\n\n    public void close() {\n        NanoHTTPD.safeClose(this.inputStream);\n        NanoHTTPD.safeClose(this.acceptSocket);\n    }\n\n    @Override\n    public void run() {\n        OutputStream outputStream = null;\n        try {\n            outputStream = this.acceptSocket.getOutputStream();\n            ITempFileManager tempFileManager = httpd.getTempFileManagerFactory().create();\n            HTTPSession session = new HTTPSession(httpd, tempFileManager, this.inputStream, outputStream, this.acceptSocket.getInetAddress());\n            while (!this.acceptSocket.isClosed()) {\n                session.execute();\n            }\n        } catch (Exception e) {\n            // When the socket is closed by the client,\n            // we throw our own SocketException\n            // to break the \"keep alive\" loop above. If\n            // the exception was anything other\n            // than the expected SocketException OR a\n            // SocketTimeoutException, print the\n            // stacktrace\n            if (!(e instanceof SocketException && \"NanoHttpd Shutdown\".equals(e.getMessage())) && !(e instanceof SocketTimeoutException)) {\n                NanoHTTPD.LOG.log(Level.SEVERE, \"Communication with the client broken, or an bug in the handler code\", e);\n            }\n        } finally {\n            NanoHTTPD.safeClose(outputStream);\n            NanoHTTPD.safeClose(this.inputStream);\n            NanoHTTPD.safeClose(this.acceptSocket);\n            httpd.asyncRunner.closed(this);\n        }\n    }\n}\n"
  },
  {
    "path": "httpserver/src/main/java/com/megaease/easeagent/httpserver/nanohttpd/protocols/http/HTTPSession.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.httpserver.nanohttpd.protocols.http;\n\n/*\n * #%L\n * NanoHttpd-Core\n * %%\n * Copyright (C) 2012 - 2016 nanohttpd\n * %%\n * Redistribution and use in source and binary forms, with or without modification,\n * are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice, this\n *    list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n *    this list of conditions and the following disclaimer in the documentation\n *    and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the nanohttpd nor the names of its contributors\n *    may be used to endorse or promote products derived from this software without\n *    specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,\n * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE\n * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED\n * OF THE POSSIBILITY OF SUCH DAMAGE.\n * #L%\n */\n\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.content.ContentType;\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.content.CookieHandler;\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.request.Method;\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.response.Response;\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.response.Status;\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.tempfiles.ITempFile;\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.tempfiles.ITempFileManager;\n\nimport javax.net.ssl.SSLException;\nimport java.io.*;\nimport java.net.InetAddress;\nimport java.net.SocketException;\nimport java.net.SocketTimeoutException;\nimport java.nio.ByteBuffer;\nimport java.nio.channels.FileChannel;\nimport java.nio.charset.Charset;\nimport java.util.*;\nimport java.util.logging.Level;\nimport java.util.regex.Matcher;\n\npublic class HTTPSession implements IHTTPSession {\n\n    public static final String POST_DATA = \"postData\";\n\n    private static final int REQUEST_BUFFER_LEN = 512;\n\n    private static final int MEMORY_STORE_LIMIT = 1024;\n\n    public static final int BUFSIZE = 8192;\n\n    public static final int MAX_HEADER_SIZE = 1024;\n\n    private final NanoHTTPD httpd;\n\n    private final ITempFileManager tempFileManager;\n\n    private final OutputStream outputStream;\n\n    private final BufferedInputStream inputStream;\n\n    private int splitbyte;\n\n    private int rlen;\n\n    private String uri;\n\n    private Method method;\n\n    private Map<String, List<String>> parms;\n\n    private Map<String, String> headers;\n\n    private CookieHandler cookies;\n\n    private String queryParameterString;\n\n    private String remoteIp;\n\n    private String protocolVersion;\n\n    public HTTPSession(NanoHTTPD httpd, ITempFileManager tempFileManager, InputStream inputStream, OutputStream outputStream) {\n        this.httpd = httpd;\n        this.tempFileManager = tempFileManager;\n        this.inputStream = new BufferedInputStream(inputStream, HTTPSession.BUFSIZE);\n        this.outputStream = outputStream;\n    }\n\n    public HTTPSession(NanoHTTPD httpd, ITempFileManager tempFileManager, InputStream inputStream, OutputStream outputStream, InetAddress inetAddress) {\n        this.httpd = httpd;\n        this.tempFileManager = tempFileManager;\n        this.inputStream = new BufferedInputStream(inputStream, HTTPSession.BUFSIZE);\n        this.outputStream = outputStream;\n        this.remoteIp = inetAddress.isLoopbackAddress() || inetAddress.isAnyLocalAddress() ? \"127.0.0.1\" : inetAddress.getHostAddress().toString();\n        this.headers = new HashMap<String, String>();\n    }\n\n    /**\n     * Decodes the sent headers and loads the data into Key/value pairs\n     */\n    private void decodeHeader(BufferedReader in, Map<String, String> pre, Map<String, List<String>> parms, Map<String, String> headers) throws NanoHTTPD.ResponseException {\n        try {\n            // Read the request line\n            String inLine = in.readLine();\n            if (inLine == null) {\n                return;\n            }\n\n            StringTokenizer st = new StringTokenizer(inLine);\n            if (!st.hasMoreTokens()) {\n                throw new NanoHTTPD.ResponseException(Status.BAD_REQUEST, \"BAD REQUEST: Syntax error. Usage: GET /example/file.html\");\n            }\n\n            pre.put(\"method\", st.nextToken());\n\n            if (!st.hasMoreTokens()) {\n                throw new NanoHTTPD.ResponseException(Status.BAD_REQUEST, \"BAD REQUEST: Missing URI. Usage: GET /example/file.html\");\n            }\n\n            String uri = st.nextToken();\n\n            // Decode parameters from the URI\n            int qmi = uri.indexOf('?');\n            if (qmi >= 0) {\n                decodeParms(uri.substring(qmi + 1), parms);\n                uri = NanoHTTPD.decodePercent(uri.substring(0, qmi));\n            } else {\n                uri = NanoHTTPD.decodePercent(uri);\n            }\n\n            // If there's another token, its protocol version,\n            // followed by HTTP headers.\n            // NOTE: this now forces header names lower case since they are\n            // case insensitive and vary by client.\n            if (st.hasMoreTokens()) {\n                protocolVersion = st.nextToken();\n            } else {\n                protocolVersion = \"HTTP/1.1\";\n                NanoHTTPD.LOG.log(Level.FINE, \"no protocol version specified, strange. Assuming HTTP/1.1.\");\n            }\n            String line = in.readLine();\n            while (line != null && !line.trim().isEmpty()) {\n                int p = line.indexOf(':');\n                if (p >= 0) {\n                    headers.put(line.substring(0, p).trim().toLowerCase(Locale.US), line.substring(p + 1).trim());\n                }\n                line = in.readLine();\n            }\n\n            pre.put(\"uri\", uri);\n        } catch (IOException ioe) {\n            throw new NanoHTTPD.ResponseException(Status.INTERNAL_ERROR, \"SERVER INTERNAL ERROR: IOException: \" + ioe.getMessage(), ioe);\n        }\n    }\n\n    /**\n     * Decodes the Multipart Body data and put it into Key/Value pairs.\n     */\n    private void decodeMultipartFormData(ContentType contentType, ByteBuffer fbuf, Map<String, List<String>> parms, Map<String, String> files) throws NanoHTTPD.ResponseException {\n        int pcount = 0;\n        try {\n            int[] boundaryIdxs = getBoundaryPositions(fbuf, contentType.getBoundary().getBytes());\n            if (boundaryIdxs.length < 2) {\n                throw new NanoHTTPD.ResponseException(Status.BAD_REQUEST, \"BAD REQUEST: Content type is multipart/form-data but contains less than two boundary strings.\");\n            }\n\n            byte[] partHeaderBuff = new byte[MAX_HEADER_SIZE];\n            for (int boundaryIdx = 0; boundaryIdx < boundaryIdxs.length - 1; boundaryIdx++) {\n                fbuf.position(boundaryIdxs[boundaryIdx]);\n                int len = (fbuf.remaining() < MAX_HEADER_SIZE) ? fbuf.remaining() : MAX_HEADER_SIZE;\n                fbuf.get(partHeaderBuff, 0, len);\n                BufferedReader in =\n                        new BufferedReader(new InputStreamReader(new ByteArrayInputStream(partHeaderBuff, 0, len), Charset.forName(contentType.getEncoding())), len);\n\n                int headerLines = 0;\n                // First line is boundary string\n                String mpline = in.readLine();\n                headerLines++;\n                if (mpline == null || !mpline.contains(contentType.getBoundary())) {\n                    throw new NanoHTTPD.ResponseException(Status.BAD_REQUEST, \"BAD REQUEST: Content type is multipart/form-data but chunk does not start with boundary.\");\n                }\n\n                String partName = null, fileName = null, partContentType = null;\n                // Parse the reset of the header lines\n                mpline = in.readLine();\n                headerLines++;\n                while (mpline != null && mpline.trim().length() > 0) {\n                    Matcher matcher = NanoHTTPD.CONTENT_DISPOSITION_PATTERN.matcher(mpline);\n                    if (matcher.matches()) {\n                        String attributeString = matcher.group(2);\n                        matcher = NanoHTTPD.CONTENT_DISPOSITION_ATTRIBUTE_PATTERN.matcher(attributeString);\n                        while (matcher.find()) {\n                            String key = matcher.group(1);\n                            if (\"name\".equalsIgnoreCase(key)) {\n                                partName = matcher.group(2);\n                            } else if (\"filename\".equalsIgnoreCase(key)) {\n                                fileName = matcher.group(2);\n                                // add these two line to support multiple\n                                // files uploaded using the same field Id\n                                if (!fileName.isEmpty()) {\n                                    if (pcount > 0)\n                                        partName = partName + String.valueOf(pcount++);\n                                    else\n                                        pcount++;\n                                }\n                            }\n                        }\n                    }\n                    matcher = NanoHTTPD.CONTENT_TYPE_PATTERN.matcher(mpline);\n                    if (matcher.matches()) {\n                        partContentType = matcher.group(2).trim();\n                    }\n                    mpline = in.readLine();\n                    headerLines++;\n                }\n                int partHeaderLength = 0;\n                while (headerLines-- > 0) {\n                    partHeaderLength = scipOverNewLine(partHeaderBuff, partHeaderLength);\n                }\n                // Read the part data\n                if (partHeaderLength >= len - 4) {\n                    throw new NanoHTTPD.ResponseException(Status.INTERNAL_ERROR, \"Multipart header size exceeds MAX_HEADER_SIZE.\");\n                }\n                int partDataStart = boundaryIdxs[boundaryIdx] + partHeaderLength;\n                int partDataEnd = boundaryIdxs[boundaryIdx + 1] - 4;\n\n                fbuf.position(partDataStart);\n\n                List<String> values = parms.get(partName);\n                if (values == null) {\n                    values = new ArrayList<String>();\n                    parms.put(partName, values);\n                }\n\n                if (partContentType == null) {\n                    // Read the part into a string\n                    byte[] data_bytes = new byte[partDataEnd - partDataStart];\n                    fbuf.get(data_bytes);\n\n                    values.add(new String(data_bytes, contentType.getEncoding()));\n                } else {\n                    // Read it into a file\n                    String path = saveTmpFile(fbuf, partDataStart, partDataEnd - partDataStart, fileName);\n                    if (!files.containsKey(partName)) {\n                        files.put(partName, path);\n                    } else {\n                        int count = 2;\n                        while (files.containsKey(partName + count)) {\n                            count++;\n                        }\n                        files.put(partName + count, path);\n                    }\n                    values.add(fileName);\n                }\n            }\n        } catch (NanoHTTPD.ResponseException re) {\n            throw re;\n        } catch (Exception e) {\n            throw new NanoHTTPD.ResponseException(Status.INTERNAL_ERROR, e.toString());\n        }\n    }\n\n    private int scipOverNewLine(byte[] partHeaderBuff, int index) {\n        while (partHeaderBuff[index] != '\\n') {\n            index++;\n        }\n        return ++index;\n    }\n\n    /**\n     * Decodes parameters in percent-encoded URI-format ( e.g.\n     * \"name=Jack%20Daniels&pass=Single%20Malt\" ) and adds them to given Map.\n     */\n    private void decodeParms(String parms, Map<String, List<String>> p) {\n        if (parms == null) {\n            this.queryParameterString = \"\";\n            return;\n        }\n\n        this.queryParameterString = parms;\n        StringTokenizer st = new StringTokenizer(parms, \"&\");\n        while (st.hasMoreTokens()) {\n            String e = st.nextToken();\n            int sep = e.indexOf('=');\n            String key = null;\n            String value = null;\n\n            if (sep >= 0) {\n                key = NanoHTTPD.decodePercent(e.substring(0, sep)).trim();\n                value = NanoHTTPD.decodePercent(e.substring(sep + 1));\n            } else {\n                key = NanoHTTPD.decodePercent(e).trim();\n                value = \"\";\n            }\n\n            List<String> values = p.get(key);\n            if (values == null) {\n                values = new ArrayList<String>();\n                p.put(key, values);\n            }\n\n            values.add(value);\n        }\n    }\n\n    @Override\n    public void execute() throws IOException {\n        Response r = null;\n        try {\n            // Read the first 8192 bytes.\n            // The full header should fit in here.\n            // Apache's default header limit is 8KB.\n            // Do NOT assume that a single read will get the entire header\n            // at once!\n            byte[] buf = new byte[HTTPSession.BUFSIZE];\n            this.splitbyte = 0;\n            this.rlen = 0;\n\n            int read = -1;\n            this.inputStream.mark(HTTPSession.BUFSIZE);\n            try {\n                read = this.inputStream.read(buf, 0, HTTPSession.BUFSIZE);\n            } catch (SSLException e) {\n                throw e;\n            } catch (IOException e) {\n                NanoHTTPD.safeClose(this.inputStream);\n                NanoHTTPD.safeClose(this.outputStream);\n                throw new SocketException(\"NanoHttpd Shutdown\");\n            }\n            if (read == -1) {\n                // socket was been closed\n                NanoHTTPD.safeClose(this.inputStream);\n                NanoHTTPD.safeClose(this.outputStream);\n                throw new SocketException(\"NanoHttpd Shutdown\");\n            }\n            while (read > 0) {\n                this.rlen += read;\n                this.splitbyte = findHeaderEnd(buf, this.rlen);\n                if (this.splitbyte > 0) {\n                    break;\n                }\n                read = this.inputStream.read(buf, this.rlen, HTTPSession.BUFSIZE - this.rlen);\n            }\n\n            if (this.splitbyte < this.rlen) {\n                this.inputStream.reset();\n                this.inputStream.skip(this.splitbyte);\n            }\n\n            this.parms = new HashMap<String, List<String>>();\n            if (null == this.headers) {\n                this.headers = new HashMap<String, String>();\n            } else {\n                this.headers.clear();\n            }\n\n            // Create a BufferedReader for parsing the header.\n            BufferedReader hin = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(buf, 0, this.rlen)));\n\n            // Decode the header into parms and header java properties\n            Map<String, String> pre = new HashMap<String, String>();\n            decodeHeader(hin, pre, this.parms, this.headers);\n\n            if (null != this.remoteIp) {\n                this.headers.put(\"remote-addr\", this.remoteIp);\n                this.headers.put(\"http-client-ip\", this.remoteIp);\n            }\n\n            this.method = Method.lookup(pre.get(\"method\"));\n            if (this.method == null) {\n                throw new NanoHTTPD.ResponseException(Status.BAD_REQUEST, \"BAD REQUEST: Syntax error. HTTP verb \" + pre.get(\"method\") + \" unhandled.\");\n            }\n\n            this.uri = pre.get(\"uri\");\n\n            this.cookies = new CookieHandler(this.headers);\n\n            String connection = this.headers.get(\"connection\");\n            boolean keepAlive = \"HTTP/1.1\".equals(protocolVersion) && (connection == null || !connection.matches(\"(?i).*close.*\"));\n\n            // Ok, now do the serve()\n\n            // TODO: long body_size = getBodySize();\n            // TODO: long pos_before_serve = this.inputStream.totalRead()\n            // (requires implementation for totalRead())\n            r = httpd.handle(this);\n            // TODO: this.inputStream.skip(body_size -\n            // (this.inputStream.totalRead() - pos_before_serve))\n\n            if (r == null) {\n                throw new NanoHTTPD.ResponseException(Status.INTERNAL_ERROR, \"SERVER INTERNAL ERROR: Serve() returned a null response.\");\n            } else {\n                String acceptEncoding = this.headers.get(\"accept-encoding\");\n                this.cookies.unloadQueue(r);\n                r.setRequestMethod(this.method);\n                if (acceptEncoding == null || !acceptEncoding.contains(\"gzip\")) {\n                    r.setUseGzip(false);\n                }\n                r.setKeepAlive(keepAlive);\n                r.send(this.outputStream);\n            }\n            if (!keepAlive || r.isCloseConnection()) {\n                throw new SocketException(\"NanoHttpd Shutdown\");\n            }\n        } catch (SocketException e) {\n            // throw it out to close socket object (finalAccept)\n            throw e;\n        } catch (SocketTimeoutException ste) {\n            // treat socket timeouts the same way we treat socket exceptions\n            // i.e. close the stream & finalAccept object by throwing the\n            // exception up the call stack.\n            throw ste;\n        } catch (SSLException ssle) {\n            Response resp = Response.newFixedLengthResponse(Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT, \"SSL PROTOCOL FAILURE: \" + ssle.getMessage());\n            resp.send(this.outputStream);\n            NanoHTTPD.safeClose(this.outputStream);\n        } catch (IOException ioe) {\n            Response resp = Response.newFixedLengthResponse(Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT, \"SERVER INTERNAL ERROR: IOException: \" + ioe.getMessage());\n            resp.send(this.outputStream);\n            NanoHTTPD.safeClose(this.outputStream);\n        } catch (NanoHTTPD.ResponseException re) {\n            Response resp = Response.newFixedLengthResponse(re.getStatus(), NanoHTTPD.MIME_PLAINTEXT, re.getMessage());\n            resp.send(this.outputStream);\n            NanoHTTPD.safeClose(this.outputStream);\n        } finally {\n            NanoHTTPD.safeClose(r);\n            this.tempFileManager.clear();\n        }\n    }\n\n    /**\n     * Find byte index separating header from body. It must be the last byte of\n     * the first two sequential new lines.\n     */\n    private int findHeaderEnd(final byte[] buf, int rlen) {\n        int splitbyte = 0;\n        while (splitbyte + 1 < rlen) {\n\n            // RFC2616\n            if (buf[splitbyte] == '\\r' && buf[splitbyte + 1] == '\\n' && splitbyte + 3 < rlen && buf[splitbyte + 2] == '\\r' && buf[splitbyte + 3] == '\\n') {\n                return splitbyte + 4;\n            }\n\n            // tolerance\n            if (buf[splitbyte] == '\\n' && buf[splitbyte + 1] == '\\n') {\n                return splitbyte + 2;\n            }\n            splitbyte++;\n        }\n        return 0;\n    }\n\n    /**\n     * Find the byte positions where multipart boundaries start. This reads a\n     * large block at a time and uses a temporary buffer to optimize (memory\n     * mapped) file access.\n     */\n    private int[] getBoundaryPositions(ByteBuffer b, byte[] boundary) {\n        int[] res = new int[0];\n        if (b.remaining() < boundary.length) {\n            return res;\n        }\n\n        int search_window_pos = 0;\n        byte[] search_window = new byte[4 * 1024 + boundary.length];\n\n        int first_fill = (b.remaining() < search_window.length) ? b.remaining() : search_window.length;\n        b.get(search_window, 0, first_fill);\n        int new_bytes = first_fill - boundary.length;\n\n        do {\n            // Search the search_window\n            for (int j = 0; j < new_bytes; j++) {\n                for (int i = 0; i < boundary.length; i++) {\n                    if (search_window[j + i] != boundary[i])\n                        break;\n                    if (i == boundary.length - 1) {\n                        // Match found, add it to results\n                        int[] new_res = new int[res.length + 1];\n                        System.arraycopy(res, 0, new_res, 0, res.length);\n                        new_res[res.length] = search_window_pos + j;\n                        res = new_res;\n                    }\n                }\n            }\n            search_window_pos += new_bytes;\n\n            // Copy the end of the buffer to the start\n            System.arraycopy(search_window, search_window.length - boundary.length, search_window, 0, boundary.length);\n\n            // Refill search_window\n            new_bytes = search_window.length - boundary.length;\n            new_bytes = (b.remaining() < new_bytes) ? b.remaining() : new_bytes;\n            b.get(search_window, boundary.length, new_bytes);\n        } while (new_bytes > 0);\n        return res;\n    }\n\n    @Override\n    public CookieHandler getCookies() {\n        return this.cookies;\n    }\n\n    @Override\n    public final Map<String, String> getHeaders() {\n        return this.headers;\n    }\n\n    @Override\n    public final InputStream getInputStream() {\n        return this.inputStream;\n    }\n\n    @Override\n    public final Method getMethod() {\n        return this.method;\n    }\n\n    /**\n     * @deprecated use {@link #getParameters()} instead.\n     */\n    @Override\n    @Deprecated\n    public final Map<String, String> getParams() {\n        Map<String, String> result = new HashMap<String, String>();\n        for (String key : this.parms.keySet()) {\n            result.put(key, this.parms.get(key).get(0));\n        }\n\n        return result;\n    }\n\n    @Override\n    public final Map<String, List<String>> getParameters() {\n        return this.parms;\n    }\n\n    @Override\n    public String getQueryParameterString() {\n        return this.queryParameterString;\n    }\n\n    private RandomAccessFile getTmpBucket() {\n        try {\n            ITempFile tempFile = this.tempFileManager.createTempFile(null);\n            return new RandomAccessFile(tempFile.getName(), \"rw\");\n        } catch (Exception e) {\n            throw new Error(e); // we won't recover, so throw an error\n        }\n    }\n\n    @Override\n    public final String getUri() {\n        return this.uri;\n    }\n\n    /**\n     * Deduce body length in bytes. Either from \"content-length\" header or read\n     * bytes.\n     */\n    public long getBodySize() {\n        if (this.headers.containsKey(\"content-length\")) {\n            return Long.parseLong(this.headers.get(\"content-length\"));\n        } else if (this.splitbyte < this.rlen) {\n            return this.rlen - this.splitbyte;\n        }\n        return 0;\n    }\n\n    @Override\n    public void parseBody(Map<String, String> files) throws IOException, NanoHTTPD.ResponseException {\n        RandomAccessFile randomAccessFile = null;\n        try {\n            long size = getBodySize();\n            ByteArrayOutputStream baos = null;\n            DataOutput requestDataOutput = null;\n\n            // Store the request in memory or a file, depending on size\n            if (size < MEMORY_STORE_LIMIT) {\n                baos = new ByteArrayOutputStream();\n                requestDataOutput = new DataOutputStream(baos);\n            } else {\n                randomAccessFile = getTmpBucket();\n                requestDataOutput = randomAccessFile;\n            }\n\n            // Read all the body and write it to request_data_output\n            byte[] buf = new byte[REQUEST_BUFFER_LEN];\n            while (this.rlen >= 0 && size > 0) {\n                this.rlen = this.inputStream.read(buf, 0, (int) Math.min(size, REQUEST_BUFFER_LEN));\n                size -= this.rlen;\n                if (this.rlen > 0) {\n                    requestDataOutput.write(buf, 0, this.rlen);\n                }\n            }\n\n            ByteBuffer fbuf = null;\n            if (baos != null) {\n                fbuf = ByteBuffer.wrap(baos.toByteArray(), 0, baos.size());\n            } else {\n                fbuf = randomAccessFile.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, randomAccessFile.length());\n                randomAccessFile.seek(0);\n            }\n\n            // If the method is POST, there may be parameters\n            // in data section, too, read it:\n            if (Method.POST.equals(this.method)) {\n                ContentType contentType = new ContentType(this.headers.get(\"content-type\"));\n                if (contentType.isMultipart()) {\n                    String boundary = contentType.getBoundary();\n                    if (boundary == null) {\n                        throw new NanoHTTPD.ResponseException(Status.BAD_REQUEST, \"BAD REQUEST: Content type is multipart/form-data but boundary missing. Usage: GET /example/file.html\");\n                    }\n                    decodeMultipartFormData(contentType, fbuf, this.parms, files);\n                } else {\n                    byte[] postBytes = new byte[fbuf.remaining()];\n                    fbuf.get(postBytes);\n                    String postLine = new String(postBytes, contentType.getEncoding()).trim();\n                    // Handle application/x-www-form-urlencoded\n                    if (\"application/x-www-form-urlencoded\".equalsIgnoreCase(contentType.getContentType())) {\n                        decodeParms(postLine, this.parms);\n                    } else if (postLine.length() != 0) {\n                        // Special case for raw POST data => create a\n                        // special files entry \"postData\" with raw content\n                        // data\n                        files.put(POST_DATA, postLine);\n                    }\n                }\n            } else if (Method.PUT.equals(this.method)) {\n                files.put(\"content\", saveTmpFile(fbuf, 0, fbuf.limit(), null));\n            }\n        } finally {\n            NanoHTTPD.safeClose(randomAccessFile);\n        }\n    }\n\n    /**\n     * Retrieves the content of a sent file and saves it to a temporary file.\n     * The full path to the saved file is returned.\n     */\n    private String saveTmpFile(ByteBuffer b, int offset, int len, String filename_hint) {\n        String path = \"\";\n        if (len > 0) {\n            FileOutputStream fileOutputStream = null;\n            try {\n                ITempFile tempFile = this.tempFileManager.createTempFile(filename_hint);\n                ByteBuffer src = b.duplicate();\n                fileOutputStream = new FileOutputStream(tempFile.getName());\n                FileChannel dest = fileOutputStream.getChannel();\n                src.position(offset).limit(offset + len);\n                dest.write(src.slice());\n                path = tempFile.getName();\n            } catch (Exception e) { // Catch exception if any\n                throw new Error(e); // we won't recover, so throw an error\n            } finally {\n                NanoHTTPD.safeClose(fileOutputStream);\n            }\n        }\n        return path;\n    }\n\n    @Override\n    public String getRemoteIpAddress() {\n        return this.remoteIp;\n    }\n}\n"
  },
  {
    "path": "httpserver/src/main/java/com/megaease/easeagent/httpserver/nanohttpd/protocols/http/IHTTPSession.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.httpserver.nanohttpd.protocols.http;\n\n/*\n * #%L\n * NanoHttpd-Core\n * %%\n * Copyright (C) 2012 - 2016 nanohttpd\n * %%\n * Redistribution and use in source and binary forms, with or without modification,\n * are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice, this\n *    list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n *    this list of conditions and the following disclaimer in the documentation\n *    and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the nanohttpd nor the names of its contributors\n *    may be used to endorse or promote products derived from this software without\n *    specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,\n * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE\n * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED\n * OF THE POSSIBILITY OF SUCH DAMAGE.\n * #L%\n */\n\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.content.CookieHandler;\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.request.Method;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * Handles one session, i.e. parses the HTTP request and returns the response.\n */\npublic interface IHTTPSession {\n\n    void execute() throws IOException;\n\n    CookieHandler getCookies();\n\n    Map<String, String> getHeaders();\n\n    InputStream getInputStream();\n\n    Method getMethod();\n\n    /**\n     * This method will only return the first value for a given parameter. You\n     * will want to use getParameters if you expect multiple values for a given\n     * key.\n     *\n     * @deprecated use {@link #getParameters()} instead.\n     */\n    @Deprecated\n    Map<String, String> getParams();\n\n    Map<String, List<String>> getParameters();\n\n    String getQueryParameterString();\n\n    /**\n     * @return the path part of the URL.\n     */\n    String getUri();\n\n    /**\n     * Adds the files in the request body to the files map.\n     *\n     * @param files\n     *            map to modify\n     */\n    void parseBody(Map<String, String> files) throws IOException, NanoHTTPD.ResponseException;\n\n    /**\n     * Get the remote ip address of the requester.\n     *\n     * @return the IP address.\n     */\n    String getRemoteIpAddress();\n}\n"
  },
  {
    "path": "httpserver/src/main/java/com/megaease/easeagent/httpserver/nanohttpd/protocols/http/NanoHTTPD.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.httpserver.nanohttpd.protocols.http;\n\n/*\n * #%L\n * NanoHttpd-Core\n * %%\n * Copyright (C) 2012 - 2016 nanohttpd\n * %%\n * Redistribution and use in source and binary forms, with or without modification,\n * are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice, this\n *    list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n *    this list of conditions and the following disclaimer in the documentation\n *    and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the nanohttpd nor the names of its contributors\n *    may be used to endorse or promote products derived from this software without\n *    specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,\n * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE\n * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED\n * OF THE POSSIBILITY OF SUCH DAMAGE.\n * #L%\n */\n\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.response.Response;\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.response.Status;\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.sockets.DefaultServerSocketFactory;\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.sockets.SecureServerSocketFactory;\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.tempfiles.DefaultTempFileManagerFactory;\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.tempfiles.ITempFileManager;\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.threading.DefaultAsyncRunner;\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.threading.IAsyncRunner;\nimport com.megaease.easeagent.httpserver.nanohttpd.util.IFactory;\nimport com.megaease.easeagent.httpserver.nanohttpd.util.IFactoryThrowing;\nimport com.megaease.easeagent.httpserver.nanohttpd.util.IHandler;\n\nimport javax.net.ssl.*;\nimport java.io.Closeable;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.UnsupportedEncodingException;\nimport java.net.ServerSocket;\nimport java.net.Socket;\nimport java.net.URL;\nimport java.net.URLDecoder;\nimport java.security.KeyStore;\nimport java.util.*;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\nimport java.util.regex.Pattern;\n\n/**\n * A simple, tiny, nicely embeddable HTTP server in Java\n * <p/>\n * <p/>\n * NanoHTTPD\n * <p>\n * Copyright (c) 2012-2013 by Paul S. Hawke, 2001,2005-2013 by Jarno Elonen,\n * 2010 by Konstantinos Togias\n * </p>\n * <p/>\n * <p/>\n * <b>Features + limitations: </b>\n * <ul>\n * <p/>\n * <li>Only one Java file</li>\n * <li>Java 5 compatible</li>\n * <li>Released as open source, Modified BSD licence</li>\n * <li>No fixed config files, logging, authorization etc. (Implement yourself if\n * you need them.)</li>\n * <li>Supports parameter parsing of GET and POST methods (+ rudimentary PUT\n * support in 1.25)</li>\n * <li>Supports both dynamic content and file serving</li>\n * <li>Supports file upload (since version 1.2, 2010)</li>\n * <li>Supports partial content (streaming)</li>\n * <li>Supports ETags</li>\n * <li>Never caches anything</li>\n * <li>Doesn't limit bandwidth, request time or simultaneous connections</li>\n * <li>Default code serves files and shows all HTTP parameters and headers</li>\n * <li>File server supports directory listing, index.html and index.htm</li>\n * <li>File server supports partial content (streaming)</li>\n * <li>File server supports ETags</li>\n * <li>File server does the 301 redirection trick for directories without '/'</li>\n * <li>File server supports simple skipping for files (continue download)</li>\n * <li>File server serves also very long files without memory overhead</li>\n * <li>Contains a built-in list of most common MIME types</li>\n * <li>All header names are converted to lower case so they don't vary between\n * browsers/clients</li>\n * <p/>\n * </ul>\n * <p/>\n * <p/>\n * <b>How to use: </b>\n * <ul>\n * <p/>\n * <li>Subclass and implement serve() and embed to your own program</li>\n * <p/>\n * </ul>\n * <p/>\n * See the separate \"LICENSE.md\" file for the distribution license (Modified BSD\n * licence)\n */\npublic abstract class NanoHTTPD {\n\n    public static final String CONTENT_DISPOSITION_REGEX = \"([ |\\t]*Content-Disposition[ |\\t]*:)(.*)\";\n\n    public static final Pattern CONTENT_DISPOSITION_PATTERN = Pattern.compile(CONTENT_DISPOSITION_REGEX, Pattern.CASE_INSENSITIVE);\n\n    public static final String CONTENT_TYPE_REGEX = \"([ |\\t]*content-type[ |\\t]*:)(.*)\";\n\n    public static final Pattern CONTENT_TYPE_PATTERN = Pattern.compile(CONTENT_TYPE_REGEX, Pattern.CASE_INSENSITIVE);\n\n    public static final String CONTENT_DISPOSITION_ATTRIBUTE_REGEX = \"[ |\\t]*([a-zA-Z]*)[ |\\t]*=[ |\\t]*['|\\\"]([^\\\"^']*)['|\\\"]\";\n\n    public static final Pattern CONTENT_DISPOSITION_ATTRIBUTE_PATTERN = Pattern.compile(CONTENT_DISPOSITION_ATTRIBUTE_REGEX);\n\n    public static final class ResponseException extends Exception {\n\n        private static final long serialVersionUID = 6569838532917408380L;\n\n        private final Status status;\n\n        public ResponseException(Status status, String message) {\n            super(message);\n            this.status = status;\n        }\n\n        public ResponseException(Status status, String message, Exception e) {\n            super(message, e);\n            this.status = status;\n        }\n\n        public Status getStatus() {\n            return this.status;\n        }\n    }\n\n    /**\n     * Maximum time to wait on Socket.getInputStream().read() (in milliseconds)\n     * This is required as the Keep-Alive HTTP connections would otherwise block\n     * the socket reading thread forever (or as long the browser is open).\n     */\n    public static final int SOCKET_READ_TIMEOUT = 5000;\n\n    /**\n     * Common MIME type for dynamic content: plain text\n     */\n    public static final String MIME_PLAINTEXT = \"text/plain\";\n\n    /**\n     * Common MIME type for dynamic content: html\n     */\n    public static final String MIME_HTML = \"text/html\";\n\n    /**\n     * Pseudo-Parameter to use to store the actual query string in the\n     * parameters map for later re-processing.\n     */\n    private static final String QUERY_STRING_PARAMETER = \"NanoHttpd.QUERY_STRING\";\n\n    /**\n     * logger to log to.\n     */\n    public static final Logger LOG = Logger.getLogger(NanoHTTPD.class.getName());\n\n    /**\n     * Hashtable mapping (String)FILENAME_EXTENSION -> (String)MIME_TYPE\n     */\n    protected static Map<String, String> MIME_TYPES;\n\n    public static Map<String, String> mimeTypes() {\n        if (MIME_TYPES == null) {\n            MIME_TYPES = new HashMap<String, String>();\n            loadMimeTypes(MIME_TYPES, \"META-INF/nanohttpd/default-mimetypes.properties\");\n            loadMimeTypes(MIME_TYPES, \"META-INF/nanohttpd/mimetypes.properties\");\n            if (MIME_TYPES.isEmpty()) {\n                LOG.log(Level.WARNING, \"no mime types found in the classpath! please provide mimetypes.properties\");\n            }\n        }\n        return MIME_TYPES;\n    }\n\n    @SuppressWarnings({\n        \"unchecked\",\n        \"rawtypes\"\n    })\n    private static void loadMimeTypes(Map<String, String> result, String resourceName) {\n        try {\n            Enumeration<URL> resources = NanoHTTPD.class.getClassLoader().getResources(resourceName);\n            while (resources.hasMoreElements()) {\n                URL url = (URL) resources.nextElement();\n                Properties properties = new Properties();\n                InputStream stream = null;\n                try {\n                    stream = url.openStream();\n                    properties.load(stream);\n                } catch (IOException e) {\n                    LOG.log(Level.SEVERE, \"could not load mimetypes from \" + url, e);\n                } finally {\n                    safeClose(stream);\n                }\n                result.putAll((Map) properties);\n            }\n        } catch (IOException e) {\n            LOG.log(Level.INFO, \"no mime types available at \" + resourceName);\n        }\n    };\n\n    /**\n     * Creates an SSLSocketFactory for HTTPS. Pass a loaded KeyStore and an\n     * array of loaded KeyManagers. These objects must properly\n     * loaded/initialized by the caller.\n     */\n    public static SSLServerSocketFactory makeSSLSocketFactory(KeyStore loadedKeyStore, KeyManager[] keyManagers) throws IOException {\n        SSLServerSocketFactory res = null;\n        try {\n            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());\n            trustManagerFactory.init(loadedKeyStore);\n            SSLContext ctx = SSLContext.getInstance(\"TLS\");\n            ctx.init(keyManagers, trustManagerFactory.getTrustManagers(), null);\n            res = ctx.getServerSocketFactory();\n        } catch (Exception e) {\n            throw new IOException(e.getMessage());\n        }\n        return res;\n    }\n\n    /**\n     * Creates an SSLSocketFactory for HTTPS. Pass a loaded KeyStore and a\n     * loaded KeyManagerFactory. These objects must properly loaded/initialized\n     * by the caller.\n     */\n    public static SSLServerSocketFactory makeSSLSocketFactory(KeyStore loadedKeyStore, KeyManagerFactory loadedKeyFactory) throws IOException {\n        try {\n            return makeSSLSocketFactory(loadedKeyStore, loadedKeyFactory.getKeyManagers());\n        } catch (Exception e) {\n            throw new IOException(e.getMessage());\n        }\n    }\n\n    /**\n     * Creates an SSLSocketFactory for HTTPS. Pass a KeyStore resource with your\n     * certificate and passphrase\n     */\n    public static SSLServerSocketFactory makeSSLSocketFactory(String keyAndTrustStoreClasspathPath, char[] passphrase) throws IOException {\n        try {\n            KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());\n            InputStream keystoreStream = NanoHTTPD.class.getResourceAsStream(keyAndTrustStoreClasspathPath);\n\n            if (keystoreStream == null) {\n                throw new IOException(\"Unable to load keystore from classpath: \" + keyAndTrustStoreClasspathPath);\n            }\n\n            keystore.load(keystoreStream, passphrase);\n            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());\n            keyManagerFactory.init(keystore, passphrase);\n            return makeSSLSocketFactory(keystore, keyManagerFactory);\n        } catch (Exception e) {\n            throw new IOException(e.getMessage());\n        }\n    }\n\n    /**\n     * Get MIME type from file name extension, if possible\n     *\n     * @param uri\n     *            the string representing a file\n     * @return the connected mime/type\n     */\n    public static String getMimeTypeForFile(String uri) {\n        int dot = uri.lastIndexOf('.');\n        String mime = null;\n        if (dot >= 0) {\n            mime = mimeTypes().get(uri.substring(dot + 1).toLowerCase());\n        }\n        return mime == null ? \"application/octet-stream\" : mime;\n    }\n\n    public static final void safeClose(Object closeable) {\n        try {\n            if (closeable != null) {\n                if (closeable instanceof Closeable) {\n                    ((Closeable) closeable).close();\n                } else if (closeable instanceof Socket) {\n                    ((Socket) closeable).close();\n                } else if (closeable instanceof ServerSocket) {\n                    ((ServerSocket) closeable).close();\n                } else {\n                    throw new IllegalArgumentException(\"Unknown object to close\");\n                }\n            }\n        } catch (IOException e) {\n            NanoHTTPD.LOG.log(Level.SEVERE, \"Could not close\", e);\n        }\n    }\n\n    public final String hostname;\n\n    public final int myPort;\n\n    private volatile ServerSocket myServerSocket;\n\n    public ServerSocket getMyServerSocket() {\n        return myServerSocket;\n    }\n\n    private IFactoryThrowing<ServerSocket, IOException> serverSocketFactory = new DefaultServerSocketFactory();\n\n    private Thread myThread;\n\n    private IHandler<IHTTPSession, Response> httpHandler;\n\n    protected List<IHandler<IHTTPSession, Response>> interceptors = new ArrayList<IHandler<IHTTPSession, Response>>(4);\n\n    /**\n     * Pluggable strategy for asynchronously executing requests.\n     */\n    protected IAsyncRunner asyncRunner;\n\n    /**\n     * Pluggable strategy for creating and cleaning up temporary files.\n     */\n    private IFactory<ITempFileManager> tempFileManagerFactory;\n\n    /**\n     * Constructs an HTTP server on given port.\n     */\n    public NanoHTTPD(int port) {\n        this(null, port);\n    }\n\n    // -------------------------------------------------------------------------------\n    // //\n    //\n    // Threading Strategy.\n    //\n    // -------------------------------------------------------------------------------\n    // //\n\n    /**\n     * Constructs an HTTP server on given hostname and port.\n     */\n    public NanoHTTPD(String hostname, int port) {\n        this.hostname = hostname;\n        this.myPort = port;\n        setTempFileManagerFactory(new DefaultTempFileManagerFactory());\n        setAsyncRunner(new DefaultAsyncRunner());\n\n        // creates a default handler that redirects to deprecated serve();\n        this.httpHandler = new IHandler<IHTTPSession, Response>() {\n\n            @Override\n            public Response handle(IHTTPSession input) {\n                return NanoHTTPD.this.serve(input);\n            }\n        };\n    }\n\n    public void setHTTPHandler(IHandler<IHTTPSession, Response> handler) {\n        this.httpHandler = handler;\n    }\n\n    public void addHTTPInterceptor(IHandler<IHTTPSession, Response> interceptor) {\n        interceptors.add(interceptor);\n    }\n\n    /**\n     * Forcibly closes all connections that are open.\n     */\n    public synchronized void closeAllConnections() {\n        stop();\n    }\n\n    /**\n     * create a instance of the client handler, subclasses can return a subclass\n     * of the ClientHandler.\n     *\n     * @param finalAccept\n     *            the socket the client is connected to\n     * @param inputStream\n     *            the input stream\n     * @return the client handler\n     */\n    protected ClientHandler createClientHandler(final Socket finalAccept, final InputStream inputStream) {\n        return new ClientHandler(this, inputStream, finalAccept);\n    }\n\n    /**\n     * Instantiate the server runnable, can be overwritten by subclasses to\n     * provide a subclass of the ServerRunnable.\n     *\n     * @param timeout\n     *            the socket timeout to use.\n     * @return the server runnable.\n     */\n    protected ServerRunnable createServerRunnable(final int timeout) {\n        return new ServerRunnable(this, timeout);\n    }\n\n    /**\n     * Decode parameters from a URL, handing the case where a single parameter\n     * name might have been supplied several times, by return lists of values.\n     * In general these lists will contain a single element.\n     *\n     * @param params\n     *            original <b>NanoHTTPD</b> parameters values, as passed to the\n     *            <code>serve()</code> method.\n     * @return a map of <code>String</code> (parameter name) to\n     *         <code>List&lt;String&gt;</code> (a list of the values supplied).\n     */\n    protected static Map<String, List<String>> decodeParameters(Map<String, String> params) {\n        return decodeParameters(params.get(NanoHTTPD.QUERY_STRING_PARAMETER));\n    }\n\n    // -------------------------------------------------------------------------------\n    // //\n\n    /**\n     * Decode parameters from a URL, handing the case where a single parameter\n     * name might have been supplied several times, by return lists of values.\n     * In general these lists will contain a single element.\n     *\n     * @param queryString\n     *            a query string pulled from the URL.\n     * @return a map of <code>String</code> (parameter name) to\n     *         <code>List&lt;String&gt;</code> (a list of the values supplied).\n     */\n    protected static Map<String, List<String>> decodeParameters(String queryString) {\n        Map<String, List<String>> params = new HashMap<String, List<String>>();\n        if (queryString != null) {\n            StringTokenizer st = new StringTokenizer(queryString, \"&\");\n            while (st.hasMoreTokens()) {\n                String e = st.nextToken();\n                int sep = e.indexOf('=');\n                String propertyName = sep >= 0 ? decodePercent(e.substring(0, sep)).trim() : decodePercent(e).trim();\n                if (!params.containsKey(propertyName)) {\n                    params.put(propertyName, new ArrayList<String>());\n                }\n                String propertyValue = sep >= 0 ? decodePercent(e.substring(sep + 1)) : null;\n                if (propertyValue != null) {\n                    params.get(propertyName).add(propertyValue);\n                }\n            }\n        }\n        return params;\n    }\n\n    /**\n     * Decode percent encoded <code>String</code> values.\n     *\n     * @param str\n     *            the percent encoded <code>String</code>\n     * @return expanded form of the input, for example \"foo%20bar\" becomes\n     *         \"foo bar\"\n     */\n    public static String decodePercent(String str) {\n        String decoded = null;\n        try {\n            decoded = URLDecoder.decode(str, \"UTF8\");\n        } catch (UnsupportedEncodingException ignored) {\n            NanoHTTPD.LOG.log(Level.WARNING, \"Encoding not supported, ignored\", ignored);\n        }\n        return decoded;\n    }\n\n    public final int getListeningPort() {\n        return this.myServerSocket == null ? -1 : this.myServerSocket.getLocalPort();\n    }\n\n    public final boolean isAlive() {\n        return wasStarted() && !this.myServerSocket.isClosed() && this.myThread.isAlive();\n    }\n\n    public IFactoryThrowing<ServerSocket, IOException> getServerSocketFactory() {\n        return serverSocketFactory;\n    }\n\n    public void setServerSocketFactory(IFactoryThrowing<ServerSocket, IOException> serverSocketFactory) {\n        this.serverSocketFactory = serverSocketFactory;\n    }\n\n    public String getHostname() {\n        return hostname;\n    }\n\n    public IFactory<ITempFileManager> getTempFileManagerFactory() {\n        return tempFileManagerFactory;\n    }\n\n    /**\n     * Call before start() to serve over HTTPS instead of HTTP\n     */\n    public void makeSecure(SSLServerSocketFactory sslServerSocketFactory, String[] sslProtocols) {\n        this.serverSocketFactory = new SecureServerSocketFactory(sslServerSocketFactory, sslProtocols);\n    }\n\n    /**\n     * This is the \"master\" method that delegates requests to handlers and makes\n     * sure there is a response to every request. You are not supposed to call\n     * or override this method in any circumstances. But no one will stop you if\n     * you do. I'm a Javadoc, not Code Police.\n     *\n     * @param session\n     *            the incoming session\n     * @return a response to the incoming session\n     */\n    public Response handle(IHTTPSession session) {\n        for (IHandler<IHTTPSession, Response> interceptor : interceptors) {\n            Response response = interceptor.handle(session);\n            if (response != null)\n                return response;\n        }\n        return httpHandler.handle(session);\n    }\n\n    /**\n     * Override this to customize the server.\n     * <p/>\n     * <p/>\n     * (By default, this returns a 404 \"Not Found\" plain text error response.)\n     *\n     * @param session\n     *            The HTTP session\n     * @return HTTP response, see class Response for details\n     */\n    @Deprecated\n    protected Response serve(IHTTPSession session) {\n        return Response.newFixedLengthResponse(Status.NOT_FOUND, NanoHTTPD.MIME_PLAINTEXT, \"Not Found\");\n    }\n\n    /**\n     * Pluggable strategy for asynchronously executing requests.\n     *\n     * @param asyncRunner\n     *            new strategy for handling threads.\n     */\n    public void setAsyncRunner(IAsyncRunner asyncRunner) {\n        this.asyncRunner = asyncRunner;\n    }\n\n    /**\n     * Pluggable strategy for creating and cleaning up temporary files.\n     *\n     * @param tempFileManagerFactory\n     *            new strategy for handling temp files.\n     */\n    public void setTempFileManagerFactory(IFactory<ITempFileManager> tempFileManagerFactory) {\n        this.tempFileManagerFactory = tempFileManagerFactory;\n    }\n\n    /**\n     * Start the server.\n     *\n     * @throws IOException\n     *             if the socket is in use.\n     */\n    public void start() throws IOException {\n        start(NanoHTTPD.SOCKET_READ_TIMEOUT);\n    }\n\n    /**\n     * Starts the server (in setDaemon(true) mode).\n     */\n    public void start(final int timeout) throws IOException {\n        start(timeout, true);\n    }\n\n    /**\n     * Start the server.\n     *\n     * @param timeout\n     *            timeout to use for socket connections.\n     * @param daemon\n     *            start the thread daemon or not.\n     * @throws IOException\n     *             if the socket is in use.\n     */\n    public void start(final int timeout, boolean daemon) throws IOException {\n        this.myServerSocket = this.getServerSocketFactory().create();\n        this.myServerSocket.setReuseAddress(true);\n\n        ServerRunnable serverRunnable = createServerRunnable(timeout);\n        this.myThread = new Thread(serverRunnable);\n        this.myThread.setDaemon(daemon);\n        this.myThread.setName(\"NanoHttpd Main Listener\");\n        this.myThread.start();\n        while (!serverRunnable.hasBinded() && serverRunnable.getBindException() == null) {\n            try {\n                Thread.sleep(10L);\n            } catch (Throwable e) {\n                // on android this may not be allowed, that's why we\n                // catch throwable the wait should be very short because we are\n                // just waiting for the bind of the socket\n            }\n        }\n        if (serverRunnable.getBindException() != null) {\n            throw serverRunnable.getBindException();\n        }\n    }\n\n    /**\n     * Stop the server.\n     */\n    public void stop() {\n        try {\n            safeClose(this.myServerSocket);\n            this.asyncRunner.closeAll();\n            if (this.myThread != null) {\n                this.myThread.join();\n            }\n        } catch (Exception e) {\n            NanoHTTPD.LOG.log(Level.SEVERE, \"Could not stop all connections\", e);\n        }\n    }\n\n    public final boolean wasStarted() {\n        return this.myServerSocket != null && this.myThread != null;\n    }\n}\n"
  },
  {
    "path": "httpserver/src/main/java/com/megaease/easeagent/httpserver/nanohttpd/protocols/http/ServerRunnable.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.httpserver.nanohttpd.protocols.http;\n\n/*\n * #%L\n * NanoHttpd-Core\n * %%\n * Copyright (C) 2012 - 2016 nanohttpd\n * %%\n * Redistribution and use in source and binary forms, with or without modification,\n * are permitted provided that the following conditions are met:\n * \n * 1. Redistributions of source code must retain the above copyright notice, this\n *    list of conditions and the following disclaimer.\n * \n * 2. Redistributions in binary form must reproduce the above copyright notice,\n *    this list of conditions and the following disclaimer in the documentation\n *    and/or other materials provided with the distribution.\n * \n * 3. Neither the name of the nanohttpd nor the names of its contributors\n *    may be used to endorse or promote products derived from this software without\n *    specific prior written permission.\n * \n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,\n * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE\n * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED\n * OF THE POSSIBILITY OF SUCH DAMAGE.\n * #L%\n */\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.InetSocketAddress;\nimport java.net.Socket;\nimport java.util.logging.Level;\n\n/**\n * The runnable that will be used for the main listening thread.\n */\npublic class ServerRunnable implements Runnable {\n\n    private NanoHTTPD httpd;\n\n    private final int timeout;\n\n    private IOException bindException;\n\n    private boolean hasBinded = false;\n\n    public ServerRunnable(NanoHTTPD httpd, int timeout) {\n        this.httpd = httpd;\n        this.timeout = timeout;\n    }\n\n    @Override\n    public void run() {\n        try {\n            httpd.getMyServerSocket().bind(httpd.hostname != null ? new InetSocketAddress(httpd.hostname, httpd.myPort) : new InetSocketAddress(httpd.myPort));\n            hasBinded = true;\n        } catch (IOException e) {\n            this.bindException = e;\n            return;\n        }\n        do {\n            try {\n                final Socket finalAccept = httpd.getMyServerSocket().accept();\n                if (this.timeout > 0) {\n                    finalAccept.setSoTimeout(this.timeout);\n                }\n                final InputStream inputStream = finalAccept.getInputStream();\n                httpd.asyncRunner.exec(httpd.createClientHandler(finalAccept, inputStream));\n            } catch (IOException e) {\n                NanoHTTPD.LOG.log(Level.FINE, \"Communication with the client broken\", e);\n            }\n        } while (!httpd.getMyServerSocket().isClosed());\n    }\n\n    public IOException getBindException() {\n        return bindException;\n    }\n\n    public boolean hasBinded() {\n        return hasBinded;\n    }\n}\n"
  },
  {
    "path": "httpserver/src/main/java/com/megaease/easeagent/httpserver/nanohttpd/protocols/http/content/ContentType.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.httpserver.nanohttpd.protocols.http.content;\n\n/*\n * #%L\n * NanoHttpd-Core\n * %%\n * Copyright (C) 2012 - 2016 nanohttpd\n * %%\n * Redistribution and use in source and binary forms, with or without modification,\n * are permitted provided that the following conditions are met:\n * \n * 1. Redistributions of source code must retain the above copyright notice, this\n *    list of conditions and the following disclaimer.\n * \n * 2. Redistributions in binary form must reproduce the above copyright notice,\n *    this list of conditions and the following disclaimer in the documentation\n *    and/or other materials provided with the distribution.\n * \n * 3. Neither the name of the nanohttpd nor the names of its contributors\n *    may be used to endorse or promote products derived from this software without\n *    specific prior written permission.\n * \n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,\n * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE\n * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED\n * OF THE POSSIBILITY OF SUCH DAMAGE.\n * #L%\n */\n\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\npublic class ContentType {\n\n    private static final String ASCII_ENCODING = \"US-ASCII\";\n\n    private static final String MULTIPART_FORM_DATA_HEADER = \"multipart/form-data\";\n\n    private static final String CONTENT_REGEX = \"[ |\\t]*([^/^ ^;^,]+/[^ ^;^,]+)\";\n\n    private static final Pattern MIME_PATTERN = Pattern.compile(CONTENT_REGEX, Pattern.CASE_INSENSITIVE);\n\n    private static final String CHARSET_REGEX = \"[ |\\t]*(charset)[ |\\t]*=[ |\\t]*['|\\\"]?([^\\\"^'^;^,]*)['|\\\"]?\";\n\n    private static final Pattern CHARSET_PATTERN = Pattern.compile(CHARSET_REGEX, Pattern.CASE_INSENSITIVE);\n\n    private static final String BOUNDARY_REGEX = \"[ |\\t]*(boundary)[ |\\t]*=[ |\\t]*['|\\\"]?([^\\\"^'^;^,]*)['|\\\"]?\";\n\n    private static final Pattern BOUNDARY_PATTERN = Pattern.compile(BOUNDARY_REGEX, Pattern.CASE_INSENSITIVE);\n\n    private final String contentTypeHeader;\n\n    private final String contentType;\n\n    private final String encoding;\n\n    private final String boundary;\n\n    public ContentType(String contentTypeHeader) {\n        this.contentTypeHeader = contentTypeHeader;\n        if (contentTypeHeader != null) {\n            contentType = getDetailFromContentHeader(contentTypeHeader, MIME_PATTERN, \"\", 1);\n            encoding = getDetailFromContentHeader(contentTypeHeader, CHARSET_PATTERN, null, 2);\n        } else {\n            contentType = \"\";\n            encoding = \"UTF-8\";\n        }\n        if (MULTIPART_FORM_DATA_HEADER.equalsIgnoreCase(contentType)) {\n            boundary = getDetailFromContentHeader(contentTypeHeader, BOUNDARY_PATTERN, null, 2);\n        } else {\n            boundary = null;\n        }\n    }\n\n    private String getDetailFromContentHeader(String contentTypeHeader, Pattern pattern, String defaultValue, int group) {\n        Matcher matcher = pattern.matcher(contentTypeHeader);\n        return matcher.find() ? matcher.group(group) : defaultValue;\n    }\n\n    public String getContentTypeHeader() {\n        return contentTypeHeader;\n    }\n\n    public String getContentType() {\n        return contentType;\n    }\n\n    public String getEncoding() {\n        return encoding == null ? ASCII_ENCODING : encoding;\n    }\n\n    public String getBoundary() {\n        return boundary;\n    }\n\n    public boolean isMultipart() {\n        return MULTIPART_FORM_DATA_HEADER.equalsIgnoreCase(contentType);\n    }\n\n    public ContentType tryUTF8() {\n        if (encoding == null) {\n            return new ContentType(this.contentTypeHeader + \"; charset=UTF-8\");\n        }\n        return this;\n    }\n}\n"
  },
  {
    "path": "httpserver/src/main/java/com/megaease/easeagent/httpserver/nanohttpd/protocols/http/content/Cookie.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.httpserver.nanohttpd.protocols.http.content;\n\n/*\n * #%L\n * NanoHttpd-Core\n * %%\n * Copyright (C) 2012 - 2016 nanohttpd\n * %%\n * Redistribution and use in source and binary forms, with or without modification,\n * are permitted provided that the following conditions are met:\n * \n * 1. Redistributions of source code must retain the above copyright notice, this\n *    list of conditions and the following disclaimer.\n * \n * 2. Redistributions in binary form must reproduce the above copyright notice,\n *    this list of conditions and the following disclaimer in the documentation\n *    and/or other materials provided with the distribution.\n * \n * 3. Neither the name of the nanohttpd nor the names of its contributors\n *    may be used to endorse or promote products derived from this software without\n *    specific prior written permission.\n * \n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,\n * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE\n * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED\n * OF THE POSSIBILITY OF SUCH DAMAGE.\n * #L%\n */\n\nimport java.text.SimpleDateFormat;\nimport java.util.Calendar;\nimport java.util.Locale;\nimport java.util.TimeZone;\n\n/**\n * A simple cookie representation. This is old code and is flawed in many ways.\n * \n * @author LordFokas\n */\npublic class Cookie {\n\n    public static String getHTTPTime(int days) {\n        Calendar calendar = Calendar.getInstance();\n        SimpleDateFormat dateFormat = new SimpleDateFormat(\"EEE, dd MMM yyyy HH:mm:ss z\", Locale.US);\n        dateFormat.setTimeZone(TimeZone.getTimeZone(\"GMT\"));\n        calendar.add(Calendar.DAY_OF_MONTH, days);\n        return dateFormat.format(calendar.getTime());\n    }\n\n    private final String n, v, e;\n\n    public Cookie(String name, String value) {\n        this(name, value, 30);\n    }\n\n    public Cookie(String name, String value, int numDays) {\n        this.n = name;\n        this.v = value;\n        this.e = getHTTPTime(numDays);\n    }\n\n    public Cookie(String name, String value, String expires) {\n        this.n = name;\n        this.v = value;\n        this.e = expires;\n    }\n\n    public String getHTTPHeader() {\n        String fmt = \"%s=%s; expires=%s\";\n        return String.format(fmt, this.n, this.v, this.e);\n    }\n}\n"
  },
  {
    "path": "httpserver/src/main/java/com/megaease/easeagent/httpserver/nanohttpd/protocols/http/content/CookieHandler.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.httpserver.nanohttpd.protocols.http.content;\n\n/*\n * #%L\n * NanoHttpd-Core\n * %%\n * Copyright (C) 2012 - 2016 nanohttpd\n * %%\n * Redistribution and use in source and binary forms, with or without modification,\n * are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice, this\n *    list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n *    this list of conditions and the following disclaimer in the documentation\n *    and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the nanohttpd nor the names of its contributors\n *    may be used to endorse or promote products derived from this software without\n *    specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,\n * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE\n * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED\n * OF THE POSSIBILITY OF SUCH DAMAGE.\n * #L%\n */\n\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.response.Response;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.Iterator;\nimport java.util.Map;\n\n/**\n * Provides rudimentary support for cookies. Doesn't support 'path', 'secure'\n * nor 'httpOnly'. Feel free to improve it and/or add unsupported features. This\n * is old code and it's flawed in many ways.\n *\n * @author LordFokas\n */\npublic class CookieHandler implements Iterable<String> {\n\n    private final HashMap<String, String> cookies = new HashMap<String, String>();\n\n    private final ArrayList<Cookie> queue = new ArrayList<Cookie>();\n\n    public CookieHandler(Map<String, String> httpHeaders) {\n        String raw = httpHeaders.get(\"cookie\");\n        if (raw != null) {\n            String[] tokens = raw.split(\";\");\n            for (String token : tokens) {\n                String[] data = token.trim().split(\"=\");\n                if (data.length == 2) {\n                    this.cookies.put(data[0], data[1]);\n                }\n            }\n        }\n    }\n\n    /**\n     * Set a cookie with an expiration date from a month ago, effectively\n     * deleting it on the client side.\n     *\n     * @param name\n     *            The cookie name.\n     */\n    public void delete(String name) {\n        set(name, \"-delete-\", -30);\n    }\n\n    @Override\n    public Iterator<String> iterator() {\n        return this.cookies.keySet().iterator();\n    }\n\n    /**\n     * Read a cookie from the HTTP Headers.\n     *\n     * @param name\n     *            The cookie's name.\n     * @return The cookie's value if it exists, null otherwise.\n     */\n    public String read(String name) {\n        return this.cookies.get(name);\n    }\n\n    public void set(Cookie cookie) {\n        this.queue.add(cookie);\n    }\n\n    /**\n     * Sets a cookie.\n     *\n     * @param name\n     *            The cookie's name.\n     * @param value\n     *            The cookie's value.\n     * @param expires\n     *            How many days until the cookie expires.\n     */\n    public void set(String name, String value, int expires) {\n        this.queue.add(new Cookie(name, value, Cookie.getHTTPTime(expires)));\n    }\n\n    /**\n     * Internally used by the webserver to add all queued cookies into the\n     * Response's HTTP Headers.\n     *\n     * @param response\n     *            The Response object to which headers the queued cookies will\n     *            be added.\n     */\n    public void unloadQueue(Response response) {\n        for (Cookie cookie : this.queue) {\n            response.addCookieHeader(cookie.getHTTPHeader());\n        }\n    }\n}\n"
  },
  {
    "path": "httpserver/src/main/java/com/megaease/easeagent/httpserver/nanohttpd/protocols/http/request/Method.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.httpserver.nanohttpd.protocols.http.request;\n\n/*\n * #%L\n * NanoHttpd-Core\n * %%\n * Copyright (C) 2012 - 2016 nanohttpd\n * %%\n * Redistribution and use in source and binary forms, with or without modification,\n * are permitted provided that the following conditions are met:\n * \n * 1. Redistributions of source code must retain the above copyright notice, this\n *    list of conditions and the following disclaimer.\n * \n * 2. Redistributions in binary form must reproduce the above copyright notice,\n *    this list of conditions and the following disclaimer in the documentation\n *    and/or other materials provided with the distribution.\n * \n * 3. Neither the name of the nanohttpd nor the names of its contributors\n *    may be used to endorse or promote products derived from this software without\n *    specific prior written permission.\n * \n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,\n * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE\n * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED\n * OF THE POSSIBILITY OF SUCH DAMAGE.\n * #L%\n */\n\n/**\n * HTTP Request methods, with the ability to decode a <code>String</code> back\n * to its enum value.\n */\npublic enum Method {\n    GET,\n    PUT,\n    POST,\n    DELETE,\n    HEAD,\n    OPTIONS,\n    TRACE,\n    CONNECT,\n    PATCH,\n    PROPFIND,\n    PROPPATCH,\n    MKCOL,\n    MOVE,\n    COPY,\n    LOCK,\n    UNLOCK,\n    NOTIFY,\n    SUBSCRIBE;\n\n    public static Method lookup(String method) {\n        if (method == null)\n            return null;\n\n        try {\n            return valueOf(method);\n        } catch (IllegalArgumentException e) {\n            // TODO: Log it?\n            return null;\n        }\n    }\n}\n"
  },
  {
    "path": "httpserver/src/main/java/com/megaease/easeagent/httpserver/nanohttpd/protocols/http/response/ChunkedOutputStream.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.httpserver.nanohttpd.protocols.http.response;\n\n/*\n * #%L\n * NanoHttpd-Core\n * %%\n * Copyright (C) 2012 - 2016 nanohttpd\n * %%\n * Redistribution and use in source and binary forms, with or without modification,\n * are permitted provided that the following conditions are met:\n * \n * 1. Redistributions of source code must retain the above copyright notice, this\n *    list of conditions and the following disclaimer.\n * \n * 2. Redistributions in binary form must reproduce the above copyright notice,\n *    this list of conditions and the following disclaimer in the documentation\n *    and/or other materials provided with the distribution.\n * \n * 3. Neither the name of the nanohttpd nor the names of its contributors\n *    may be used to endorse or promote products derived from this software without\n *    specific prior written permission.\n * \n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,\n * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE\n * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED\n * OF THE POSSIBILITY OF SUCH DAMAGE.\n * #L%\n */\n\nimport java.io.FilterOutputStream;\nimport java.io.IOException;\nimport java.io.OutputStream;\n\n/**\n * Output stream that will automatically send every write to the wrapped\n * OutputStream according to chunked transfer:\n * http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1\n */\npublic class ChunkedOutputStream extends FilterOutputStream {\n\n    public ChunkedOutputStream(OutputStream out) {\n        super(out);\n    }\n\n    @Override\n    public void write(int b) throws IOException {\n        byte[] data = {\n            (byte) b\n        };\n        write(data, 0, 1);\n    }\n\n    @Override\n    public void write(byte[] b) throws IOException {\n        write(b, 0, b.length);\n    }\n\n    @Override\n    public void write(byte[] b, int off, int len) throws IOException {\n        if (len == 0)\n            return;\n        out.write(String.format(\"%x\\r\\n\", len).getBytes());\n        out.write(b, off, len);\n        out.write(\"\\r\\n\".getBytes());\n    }\n\n    public void finish() throws IOException {\n        out.write(\"0\\r\\n\\r\\n\".getBytes());\n    }\n}\n"
  },
  {
    "path": "httpserver/src/main/java/com/megaease/easeagent/httpserver/nanohttpd/protocols/http/response/IStatus.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.httpserver.nanohttpd.protocols.http.response;\n\n/*\n * #%L\n * NanoHttpd-Core\n * %%\n * Copyright (C) 2012 - 2016 nanohttpd\n * %%\n * Redistribution and use in source and binary forms, with or without modification,\n * are permitted provided that the following conditions are met:\n * \n * 1. Redistributions of source code must retain the above copyright notice, this\n *    list of conditions and the following disclaimer.\n * \n * 2. Redistributions in binary form must reproduce the above copyright notice,\n *    this list of conditions and the following disclaimer in the documentation\n *    and/or other materials provided with the distribution.\n * \n * 3. Neither the name of the nanohttpd nor the names of its contributors\n *    may be used to endorse or promote products derived from this software without\n *    specific prior written permission.\n * \n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,\n * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE\n * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED\n * OF THE POSSIBILITY OF SUCH DAMAGE.\n * #L%\n */\n\npublic interface IStatus {\n\n    String getDescription();\n\n    int getRequestStatus();\n}\n"
  },
  {
    "path": "httpserver/src/main/java/com/megaease/easeagent/httpserver/nanohttpd/protocols/http/response/Response.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.httpserver.nanohttpd.protocols.http.response;\n\n/*\n * #%L\n * NanoHttpd-Core\n * %%\n * Copyright (C) 2012 - 2016 nanohttpd\n * %%\n * Redistribution and use in source and binary forms, with or without modification,\n * are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice, this\n *    list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n *    this list of conditions and the following disclaimer in the documentation\n *    and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the nanohttpd nor the names of its contributors\n *    may be used to endorse or promote products derived from this software without\n *    specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,\n * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE\n * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED\n * OF THE POSSIBILITY OF SUCH DAMAGE.\n * #L%\n */\n\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.NanoHTTPD;\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.content.ContentType;\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.request.Method;\n\nimport java.io.*;\nimport java.nio.charset.Charset;\nimport java.nio.charset.CharsetEncoder;\nimport java.text.SimpleDateFormat;\nimport java.util.*;\nimport java.util.Map.Entry;\nimport java.util.logging.Level;\nimport java.util.zip.GZIPOutputStream;\n\n/**\n * HTTP response. Return one of these from serve().\n */\npublic class Response implements Closeable {\n\n    /**\n     * HTTP status code after processing, e.g. \"200 OK\", Status.OK\n     */\n    private IStatus status;\n\n    /**\n     * MIME type of content, e.g. \"text/html\"\n     */\n    private String mimeType;\n\n    /**\n     * Data of the response, may be null.\n     */\n    private InputStream data;\n\n    private long contentLength;\n\n    /**\n     * Headers for the HTTP response. Use addHeader() to add lines. the\n     * lowercase map is automatically kept up to date.\n     */\n    @SuppressWarnings(\"serial\")\n    private final Map<String, String> header = new HashMap<String, String>() {\n\n        public String put(String key, String value) {\n            lowerCaseHeader.put(key == null ? key : key.toLowerCase(), value);\n            return super.put(key, value);\n        };\n    };\n\n    /**\n     * copy of the header map with all the keys lowercase for faster searching.\n     */\n    private final Map<String, String> lowerCaseHeader = new HashMap<String, String>();\n\n    /**\n     * The request method that spawned this response.\n     */\n    private Method requestMethod;\n\n    /**\n     * Use chunkedTransfer\n     */\n    private boolean chunkedTransfer;\n\n    private boolean keepAlive;\n\n    private List<String> cookieHeaders;\n\n    private GzipUsage gzipUsage = GzipUsage.DEFAULT;\n\n    private static enum GzipUsage {\n        DEFAULT,\n        ALWAYS,\n        NEVER;\n    }\n\n    /**\n     * Creates a fixed length response if totalBytes>=0, otherwise chunked.\n     */\n    @SuppressWarnings({\n        \"rawtypes\",\n        \"unchecked\"\n    })\n    protected Response(IStatus status, String mimeType, InputStream data, long totalBytes) {\n        this.status = status;\n        this.mimeType = mimeType;\n        if (data == null) {\n            this.data = new ByteArrayInputStream(new byte[0]);\n            this.contentLength = 0L;\n        } else {\n            this.data = data;\n            this.contentLength = totalBytes;\n        }\n        this.chunkedTransfer = this.contentLength < 0;\n        this.keepAlive = true;\n        this.cookieHeaders = new ArrayList(10);\n    }\n\n    @Override\n    public void close() throws IOException {\n        if (this.data != null) {\n            this.data.close();\n        }\n    }\n\n    /**\n     * Adds a cookie header to the list. Should not be called manually, this is\n     * an internal utility.\n     */\n    public void addCookieHeader(String cookie) {\n        cookieHeaders.add(cookie);\n    }\n\n    /**\n     * Should not be called manually. This is an internally utility for JUnit\n     * test purposes.\n     *\n     * @return All unloaded cookie headers.\n     */\n    public List<String> getCookieHeaders() {\n        return cookieHeaders;\n    }\n\n    /**\n     * Adds given line to the header.\n     */\n    public void addHeader(String name, String value) {\n        this.header.put(name, value);\n    }\n\n    /**\n     * Indicate to close the connection after the Response has been sent.\n     *\n     * @param close\n     *            {@code true} to hint connection closing, {@code false} to let\n     *            connection be closed by client.\n     */\n    public void closeConnection(boolean close) {\n        if (close)\n            this.header.put(\"connection\", \"close\");\n        else\n            this.header.remove(\"connection\");\n    }\n\n    /**\n     * @return {@code true} if connection is to be closed after this Response\n     *         has been sent.\n     */\n    public boolean isCloseConnection() {\n        return \"close\".equals(getHeader(\"connection\"));\n    }\n\n    public InputStream getData() {\n        return this.data;\n    }\n\n    public String getHeader(String name) {\n        return this.lowerCaseHeader.get(name.toLowerCase());\n    }\n\n    public String getMimeType() {\n        return this.mimeType;\n    }\n\n    public Method getRequestMethod() {\n        return this.requestMethod;\n    }\n\n    public IStatus getStatus() {\n        return this.status;\n    }\n\n    public void setKeepAlive(boolean useKeepAlive) {\n        this.keepAlive = useKeepAlive;\n    }\n\n    /**\n     * Sends given response to the socket.\n     */\n    public void send(OutputStream outputStream) {\n        SimpleDateFormat gmtFrmt = new SimpleDateFormat(\"E, d MMM yyyy HH:mm:ss 'GMT'\", Locale.US);\n        gmtFrmt.setTimeZone(TimeZone.getTimeZone(\"GMT\"));\n\n        try {\n            if (this.status == null) {\n                throw new Error(\"sendResponse(): Status can't be null.\");\n            }\n            PrintWriter pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(outputStream, new ContentType(this.mimeType).getEncoding())), false);\n            pw.append(\"HTTP/1.1 \").append(this.status.getDescription()).append(\" \\r\\n\");\n            if (this.mimeType != null) {\n                printHeader(pw, \"Content-Type\", this.mimeType);\n            }\n            if (getHeader(\"date\") == null) {\n                printHeader(pw, \"Date\", gmtFrmt.format(new Date()));\n            }\n            for (Entry<String, String> entry : this.header.entrySet()) {\n                printHeader(pw, entry.getKey(), entry.getValue());\n            }\n            for (String cookieHeader : this.cookieHeaders) {\n                printHeader(pw, \"Set-Cookie\", cookieHeader);\n            }\n            if (getHeader(\"connection\") == null) {\n                printHeader(pw, \"Connection\", (this.keepAlive ? \"keep-alive\" : \"close\"));\n            }\n            if (getHeader(\"content-length\") != null) {\n                setUseGzip(false);\n            }\n            if (useGzipWhenAccepted()) {\n                printHeader(pw, \"Content-Encoding\", \"gzip\");\n                setChunkedTransfer(true);\n            }\n            long pending = this.data != null ? this.contentLength : 0;\n            if (this.requestMethod != Method.HEAD && this.chunkedTransfer) {\n                printHeader(pw, \"Transfer-Encoding\", \"chunked\");\n            } else if (!useGzipWhenAccepted()) {\n                pending = sendContentLengthHeaderIfNotAlreadyPresent(pw, pending);\n            }\n            pw.append(\"\\r\\n\");\n            pw.flush();\n            sendBodyWithCorrectTransferAndEncoding(outputStream, pending);\n            outputStream.flush();\n            NanoHTTPD.safeClose(this.data);\n        } catch (IOException ioe) {\n            NanoHTTPD.LOG.log(Level.SEVERE, \"Could not send response to the client\", ioe);\n        }\n    }\n\n    @SuppressWarnings(\"static-method\")\n    protected void printHeader(PrintWriter pw, String key, String value) {\n        pw.append(key).append(\": \").append(value).append(\"\\r\\n\");\n    }\n\n    protected long sendContentLengthHeaderIfNotAlreadyPresent(PrintWriter pw, long defaultSize) {\n        String contentLengthString = getHeader(\"content-length\");\n        long size = defaultSize;\n        if (contentLengthString != null) {\n            try {\n                size = Long.parseLong(contentLengthString);\n            } catch (NumberFormatException ex) {\n                NanoHTTPD.LOG.severe(\"content-length was no number \" + contentLengthString);\n            }\n        }else{\n        \tpw.print(\"Content-Length: \" + size + \"\\r\\n\");\n        }\n        return size;\n    }\n\n    private void sendBodyWithCorrectTransferAndEncoding(OutputStream outputStream, long pending) throws IOException {\n        if (this.requestMethod != Method.HEAD && this.chunkedTransfer) {\n            ChunkedOutputStream chunkedOutputStream = new ChunkedOutputStream(outputStream);\n            sendBodyWithCorrectEncoding(chunkedOutputStream, -1);\n            try {\n                chunkedOutputStream.finish();\n            } catch (Exception e) {\n                if(this.data != null) {\n                    this.data.close();\n                }\n            }\n        } else {\n            sendBodyWithCorrectEncoding(outputStream, pending);\n        }\n    }\n\n    private void sendBodyWithCorrectEncoding(OutputStream outputStream, long pending) throws IOException {\n        if (useGzipWhenAccepted()) {\n            GZIPOutputStream gzipOutputStream = null;\n            try {\n                gzipOutputStream = new GZIPOutputStream(outputStream);\n            } catch (Exception e) {\n                if(this.data != null) {\n                    this.data.close();\n                }\n            }\n            if (gzipOutputStream != null) {\n                sendBody(gzipOutputStream, -1);\n                gzipOutputStream.finish();\n            }\n        } else {\n            sendBody(outputStream, pending);\n        }\n    }\n\n    /**\n     * Sends the body to the specified OutputStream. The pending parameter\n     * limits the maximum amounts of bytes sent unless it is -1, in which case\n     * everything is sent.\n     *\n     * @param outputStream\n     *            the OutputStream to send data to\n     * @param pending\n     *            -1 to send everything, otherwise sets a max limit to the\n     *            number of bytes sent\n     * @throws IOException\n     *             if something goes wrong while sending the data.\n     */\n    private void sendBody(OutputStream outputStream, long pending) throws IOException {\n        long BUFFER_SIZE = 16 * 1024;\n        byte[] buff = new byte[(int) BUFFER_SIZE];\n        boolean sendEverything = pending == -1;\n        while (pending > 0 || sendEverything) {\n            long bytesToRead = sendEverything ? BUFFER_SIZE : Math.min(pending, BUFFER_SIZE);\n            int read = this.data.read(buff, 0, (int) bytesToRead);\n            if (read <= 0) {\n                break;\n            }\n            try {\n                outputStream.write(buff, 0, read);\n            } catch (Exception e) {\n                if(this.data != null) {\n                    this.data.close();\n                }\n            }\n            if (!sendEverything) {\n                pending -= read;\n            }\n        }\n    }\n\n    public void setChunkedTransfer(boolean chunkedTransfer) {\n        this.chunkedTransfer = chunkedTransfer;\n    }\n\n    public void setData(InputStream data) {\n        this.data = data;\n    }\n\n    public void setMimeType(String mimeType) {\n        this.mimeType = mimeType;\n    }\n\n    public void setRequestMethod(Method requestMethod) {\n        this.requestMethod = requestMethod;\n    }\n\n    public void setStatus(IStatus status) {\n        this.status = status;\n    }\n\n    /**\n     * Create a response with unknown length (using HTTP 1.1 chunking).\n     */\n    public static Response newChunkedResponse(IStatus status, String mimeType, InputStream data) {\n        return new Response(status, mimeType, data, -1);\n    }\n\n    public static Response newFixedLengthResponse(IStatus status, String mimeType, byte[] data) {\n        return newFixedLengthResponse(status, mimeType, new ByteArrayInputStream(data), data.length);\n    }\n\n    /**\n     * Create a response with known length.\n     */\n    public static Response newFixedLengthResponse(IStatus status, String mimeType, InputStream data, long totalBytes) {\n        return new Response(status, mimeType, data, totalBytes);\n    }\n\n    /**\n     * Create a text response with known length.\n     */\n    public static Response newFixedLengthResponse(IStatus status, String mimeType, String txt) {\n        ContentType contentType = new ContentType(mimeType);\n        if (txt == null) {\n            return newFixedLengthResponse(status, mimeType, new ByteArrayInputStream(new byte[0]), 0);\n        } else {\n            byte[] bytes;\n            try {\n                CharsetEncoder newEncoder = Charset.forName(contentType.getEncoding()).newEncoder();\n                if (!newEncoder.canEncode(txt)) {\n                    contentType = contentType.tryUTF8();\n                }\n                bytes = txt.getBytes(contentType.getEncoding());\n            } catch (UnsupportedEncodingException e) {\n                NanoHTTPD.LOG.log(Level.SEVERE, \"encoding problem, responding nothing\", e);\n                bytes = new byte[0];\n            }\n            return newFixedLengthResponse(status, contentType.getContentTypeHeader(), new ByteArrayInputStream(bytes), bytes.length);\n        }\n    }\n\n    /**\n     * Create a text response with known length.\n     */\n    public static Response newFixedLengthResponse(String msg) {\n        return newFixedLengthResponse(Status.OK, NanoHTTPD.MIME_HTML, msg);\n    }\n\n    public Response setUseGzip(boolean useGzip) {\n        gzipUsage = useGzip ? GzipUsage.ALWAYS : GzipUsage.NEVER;\n        return this;\n    }\n\n    // If a Gzip usage has been enforced, use it.\n    // Else decide whether or not to use Gzip.\n    public boolean useGzipWhenAccepted() {\n        if (gzipUsage == GzipUsage.DEFAULT)\n            return getMimeType() != null && (getMimeType().toLowerCase().contains(\"text/\") || getMimeType().toLowerCase().contains(\"/json\"));\n        else\n            return gzipUsage == GzipUsage.ALWAYS;\n    }\n}\n"
  },
  {
    "path": "httpserver/src/main/java/com/megaease/easeagent/httpserver/nanohttpd/protocols/http/response/Status.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.httpserver.nanohttpd.protocols.http.response;\n\n/*\n * #%L\n * NanoHttpd-Core\n * %%\n * Copyright (C) 2012 - 2016 nanohttpd\n * %%\n * Redistribution and use in source and binary forms, with or without modification,\n * are permitted provided that the following conditions are met:\n * \n * 1. Redistributions of source code must retain the above copyright notice, this\n *    list of conditions and the following disclaimer.\n * \n * 2. Redistributions in binary form must reproduce the above copyright notice,\n *    this list of conditions and the following disclaimer in the documentation\n *    and/or other materials provided with the distribution.\n * \n * 3. Neither the name of the nanohttpd nor the names of its contributors\n *    may be used to endorse or promote products derived from this software without\n *    specific prior written permission.\n * \n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,\n * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE\n * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED\n * OF THE POSSIBILITY OF SUCH DAMAGE.\n * #L%\n */\n\n/**\n * Some HTTP response status codes\n */\npublic enum Status implements IStatus {\n    SWITCH_PROTOCOL(101, \"Switching Protocols\"),\n\n    OK(200, \"OK\"),\n    CREATED(201, \"Created\"),\n    ACCEPTED(202, \"Accepted\"),\n    NO_CONTENT(204, \"No Content\"),\n    PARTIAL_CONTENT(206, \"Partial Content\"),\n    MULTI_STATUS(207, \"Multi-Status\"),\n\n    REDIRECT(301, \"Moved Permanently\"),\n    /**\n     * Many user agents mishandle 302 in ways that violate the RFC1945 spec\n     * (i.e., redirect a POST to a GET). 303 and 307 were added in RFC2616 to\n     * address this. You should prefer 303 and 307 unless the calling user agent\n     * does not support 303 and 307 functionality\n     */\n    @Deprecated\n    FOUND(302, \"Found\"),\n    REDIRECT_SEE_OTHER(303, \"See Other\"),\n    NOT_MODIFIED(304, \"Not Modified\"),\n    TEMPORARY_REDIRECT(307, \"Temporary Redirect\"),\n\n    BAD_REQUEST(400, \"Bad Request\"),\n    UNAUTHORIZED(401, \"Unauthorized\"),\n    FORBIDDEN(403, \"Forbidden\"),\n    NOT_FOUND(404, \"Not Found\"),\n    METHOD_NOT_ALLOWED(405, \"Method Not Allowed\"),\n    NOT_ACCEPTABLE(406, \"Not Acceptable\"),\n    REQUEST_TIMEOUT(408, \"Request Timeout\"),\n    CONFLICT(409, \"Conflict\"),\n    GONE(410, \"Gone\"),\n    LENGTH_REQUIRED(411, \"Length Required\"),\n    PRECONDITION_FAILED(412, \"Precondition Failed\"),\n    PAYLOAD_TOO_LARGE(413, \"Payload Too Large\"),\n    UNSUPPORTED_MEDIA_TYPE(415, \"Unsupported Media Type\"),\n    RANGE_NOT_SATISFIABLE(416, \"Requested Range Not Satisfiable\"),\n    EXPECTATION_FAILED(417, \"Expectation Failed\"),\n    TOO_MANY_REQUESTS(429, \"Too Many Requests\"),\n\n    INTERNAL_ERROR(500, \"Internal Server Error\"),\n    NOT_IMPLEMENTED(501, \"Not Implemented\"),\n    SERVICE_UNAVAILABLE(503, \"Service Unavailable\"),\n    UNSUPPORTED_HTTP_VERSION(505, \"HTTP Version Not Supported\");\n\n    private final int requestStatus;\n\n    private final String description;\n\n    Status(int requestStatus, String description) {\n        this.requestStatus = requestStatus;\n        this.description = description;\n    }\n\n    public static Status lookup(int requestStatus) {\n        for (Status status : Status.values()) {\n            if (status.getRequestStatus() == requestStatus) {\n                return status;\n            }\n        }\n        return null;\n    }\n\n    @Override\n    public String getDescription() {\n        return \"\" + this.requestStatus + \" \" + this.description;\n    }\n\n    @Override\n    public int getRequestStatus() {\n        return this.requestStatus;\n    }\n}\n"
  },
  {
    "path": "httpserver/src/main/java/com/megaease/easeagent/httpserver/nanohttpd/protocols/http/sockets/DefaultServerSocketFactory.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.httpserver.nanohttpd.protocols.http.sockets;\n\n/*\n * #%L\n * NanoHttpd-Core\n * %%\n * Copyright (C) 2012 - 2016 nanohttpd\n * %%\n * Redistribution and use in source and binary forms, with or without modification,\n * are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice, this\n *    list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n *    this list of conditions and the following disclaimer in the documentation\n *    and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the nanohttpd nor the names of its contributors\n *    may be used to endorse or promote products derived from this software without\n *    specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,\n * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE\n * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED\n * OF THE POSSIBILITY OF SUCH DAMAGE.\n * #L%\n */\n\nimport com.megaease.easeagent.httpserver.nanohttpd.util.IFactoryThrowing;\n\nimport java.io.IOException;\nimport java.net.ServerSocket;\n\n/**\n * Creates a normal ServerSocket for TCP connections\n */\npublic class DefaultServerSocketFactory implements IFactoryThrowing<ServerSocket, IOException> {\n\n    @Override\n    public ServerSocket create() throws IOException {\n        return new ServerSocket();\n    }\n\n}\n"
  },
  {
    "path": "httpserver/src/main/java/com/megaease/easeagent/httpserver/nanohttpd/protocols/http/sockets/SecureServerSocketFactory.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.httpserver.nanohttpd.protocols.http.sockets;\n\n/*\n * #%L\n * NanoHttpd-Core\n * %%\n * Copyright (C) 2012 - 2016 nanohttpd\n * %%\n * Redistribution and use in source and binary forms, with or without modification,\n * are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice, this\n *    list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n *    this list of conditions and the following disclaimer in the documentation\n *    and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the nanohttpd nor the names of its contributors\n *    may be used to endorse or promote products derived from this software without\n *    specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,\n * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE\n * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED\n * OF THE POSSIBILITY OF SUCH DAMAGE.\n * #L%\n */\n\nimport com.megaease.easeagent.httpserver.nanohttpd.util.IFactoryThrowing;\n\nimport javax.net.ssl.SSLServerSocket;\nimport javax.net.ssl.SSLServerSocketFactory;\nimport java.io.IOException;\nimport java.net.ServerSocket;\n\n/**\n * Creates a new SSLServerSocket\n */\npublic class SecureServerSocketFactory implements IFactoryThrowing<ServerSocket, IOException> {\n\n    private SSLServerSocketFactory sslServerSocketFactory;\n\n    private String[] sslProtocols;\n\n    public SecureServerSocketFactory(SSLServerSocketFactory sslServerSocketFactory, String[] sslProtocols) {\n        this.sslServerSocketFactory = sslServerSocketFactory;\n        this.sslProtocols = sslProtocols;\n    }\n\n    @Override\n    public ServerSocket create() throws IOException {\n        SSLServerSocket ss = null;\n        ss = (SSLServerSocket) this.sslServerSocketFactory.createServerSocket();\n        if (this.sslProtocols != null) {\n            ss.setEnabledProtocols(this.sslProtocols);\n        } else {\n            ss.setEnabledProtocols(ss.getSupportedProtocols());\n        }\n        ss.setUseClientMode(false);\n        ss.setWantClientAuth(false);\n        ss.setNeedClientAuth(false);\n        return ss;\n    }\n\n}\n"
  },
  {
    "path": "httpserver/src/main/java/com/megaease/easeagent/httpserver/nanohttpd/protocols/http/tempfiles/DefaultTempFile.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.httpserver.nanohttpd.protocols.http.tempfiles;\n\n/*\n * #%L\n * NanoHttpd-Core\n * %%\n * Copyright (C) 2012 - 2016 nanohttpd\n * %%\n * Redistribution and use in source and binary forms, with or without modification,\n * are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice, this\n *    list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n *    this list of conditions and the following disclaimer in the documentation\n *    and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the nanohttpd nor the names of its contributors\n *    may be used to endorse or promote products derived from this software without\n *    specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,\n * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE\n * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED\n * OF THE POSSIBILITY OF SUCH DAMAGE.\n * #L%\n */\n\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.NanoHTTPD;\n\nimport java.io.File;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.OutputStream;\n\n/**\n * Default strategy for creating and cleaning up temporary files.\n * <p/>\n * <p>\n * By default, files are created by <code>File.createTempFile()</code> in the\n * directory specified.\n * </p>\n */\npublic class DefaultTempFile implements ITempFile {\n\n    private final File file;\n\n    private final OutputStream fstream;\n\n    public DefaultTempFile(File tempdir) throws IOException {\n        this.file = File.createTempFile(\"NanoHTTPD-\", \"\", tempdir);\n        this.fstream = new FileOutputStream(this.file);\n    }\n\n    @Override\n    public void delete() throws Exception {\n        NanoHTTPD.safeClose(this.fstream);\n        if (!this.file.delete()) {\n            throw new Exception(\"could not delete temporary file: \" + this.file.getAbsolutePath());\n        }\n    }\n\n    @Override\n    public String getName() {\n        return this.file.getAbsolutePath();\n    }\n\n    @Override\n    public OutputStream open() throws Exception {\n        return this.fstream;\n    }\n}\n"
  },
  {
    "path": "httpserver/src/main/java/com/megaease/easeagent/httpserver/nanohttpd/protocols/http/tempfiles/DefaultTempFileManager.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.httpserver.nanohttpd.protocols.http.tempfiles;\n\n/*\n * #%L\n * NanoHttpd-Core\n * %%\n * Copyright (C) 2012 - 2016 nanohttpd\n * %%\n * Redistribution and use in source and binary forms, with or without modification,\n * are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice, this\n *    list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n *    this list of conditions and the following disclaimer in the documentation\n *    and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the nanohttpd nor the names of its contributors\n *    may be used to endorse or promote products derived from this software without\n *    specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,\n * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE\n * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED\n * OF THE POSSIBILITY OF SUCH DAMAGE.\n * #L%\n */\n\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.NanoHTTPD;\n\nimport java.io.File;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.logging.Level;\n\n/**\n * Default strategy for creating and cleaning up temporary files.\n * <p/>\n * <p>\n * This class stores its files in the standard location (that is, wherever\n * <code>java.io.tmpdir</code> points to). Files are added to an internal list,\n * and deleted when no longer needed (that is, when <code>clear()</code> is\n * invoked at the end of processing a request).\n * </p>\n */\npublic class DefaultTempFileManager implements ITempFileManager {\n\n    private final File tmpdir;\n\n    private final List<ITempFile> tempFiles;\n\n    public DefaultTempFileManager() {\n        this.tmpdir = new File(System.getProperty(\"java.io.tmpdir\"));\n        if (!tmpdir.exists()) {\n            tmpdir.mkdirs();\n        }\n        this.tempFiles = new ArrayList<ITempFile>();\n    }\n\n    @Override\n    public void clear() {\n        for (ITempFile file : this.tempFiles) {\n            try {\n                file.delete();\n            } catch (Exception ignored) {\n                NanoHTTPD.LOG.log(Level.WARNING, \"could not delete file \", ignored);\n            }\n        }\n        this.tempFiles.clear();\n    }\n\n    @Override\n    public ITempFile createTempFile(String filename_hint) throws Exception {\n        DefaultTempFile tempFile = new DefaultTempFile(this.tmpdir);\n        this.tempFiles.add(tempFile);\n        return tempFile;\n    }\n}\n"
  },
  {
    "path": "httpserver/src/main/java/com/megaease/easeagent/httpserver/nanohttpd/protocols/http/tempfiles/DefaultTempFileManagerFactory.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.httpserver.nanohttpd.protocols.http.tempfiles;\n\n/*\n * #%L\n * NanoHttpd-Core\n * %%\n * Copyright (C) 2012 - 2016 nanohttpd\n * %%\n * Redistribution and use in source and binary forms, with or without modification,\n * are permitted provided that the following conditions are met:\n * \n * 1. Redistributions of source code must retain the above copyright notice, this\n *    list of conditions and the following disclaimer.\n * \n * 2. Redistributions in binary form must reproduce the above copyright notice,\n *    this list of conditions and the following disclaimer in the documentation\n *    and/or other materials provided with the distribution.\n * \n * 3. Neither the name of the nanohttpd nor the names of its contributors\n *    may be used to endorse or promote products derived from this software without\n *    specific prior written permission.\n * \n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,\n * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE\n * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED\n * OF THE POSSIBILITY OF SUCH DAMAGE.\n * #L%\n */\n\nimport com.megaease.easeagent.httpserver.nanohttpd.util.IFactory;\n\n/**\n * Default strategy for creating and cleaning up temporary files.\n */\npublic class DefaultTempFileManagerFactory implements IFactory<ITempFileManager> {\n\n    @Override\n    public ITempFileManager create() {\n        return new DefaultTempFileManager();\n    }\n}\n"
  },
  {
    "path": "httpserver/src/main/java/com/megaease/easeagent/httpserver/nanohttpd/protocols/http/tempfiles/ITempFile.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.httpserver.nanohttpd.protocols.http.tempfiles;\n\n/*\n * #%L\n * NanoHttpd-Core\n * %%\n * Copyright (C) 2012 - 2016 nanohttpd\n * %%\n * Redistribution and use in source and binary forms, with or without modification,\n * are permitted provided that the following conditions are met:\n * \n * 1. Redistributions of source code must retain the above copyright notice, this\n *    list of conditions and the following disclaimer.\n * \n * 2. Redistributions in binary form must reproduce the above copyright notice,\n *    this list of conditions and the following disclaimer in the documentation\n *    and/or other materials provided with the distribution.\n * \n * 3. Neither the name of the nanohttpd nor the names of its contributors\n *    may be used to endorse or promote products derived from this software without\n *    specific prior written permission.\n * \n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,\n * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE\n * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED\n * OF THE POSSIBILITY OF SUCH DAMAGE.\n * #L%\n */\n\nimport java.io.OutputStream;\n\n/**\n * A temp file.\n * <p/>\n * <p>\n * Temp files are responsible for managing the actual temporary storage and\n * cleaning themselves up when no longer needed.\n * </p>\n */\npublic interface ITempFile {\n\n    public void delete() throws Exception;\n\n    public String getName();\n\n    public OutputStream open() throws Exception;\n}\n"
  },
  {
    "path": "httpserver/src/main/java/com/megaease/easeagent/httpserver/nanohttpd/protocols/http/tempfiles/ITempFileManager.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.httpserver.nanohttpd.protocols.http.tempfiles;\n\n/*\n * #%L\n * NanoHttpd-Core\n * %%\n * Copyright (C) 2012 - 2016 nanohttpd\n * %%\n * Redistribution and use in source and binary forms, with or without modification,\n * are permitted provided that the following conditions are met:\n * \n * 1. Redistributions of source code must retain the above copyright notice, this\n *    list of conditions and the following disclaimer.\n * \n * 2. Redistributions in binary form must reproduce the above copyright notice,\n *    this list of conditions and the following disclaimer in the documentation\n *    and/or other materials provided with the distribution.\n * \n * 3. Neither the name of the nanohttpd nor the names of its contributors\n *    may be used to endorse or promote products derived from this software without\n *    specific prior written permission.\n * \n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,\n * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE\n * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED\n * OF THE POSSIBILITY OF SUCH DAMAGE.\n * #L%\n */\n\n/**\n * Temp file manager.\n * <p/>\n * <p>\n * Temp file managers are created 1-to-1 with incoming requests, to create and\n * cleanup temporary files created as a result of handling the request.\n * </p>\n */\npublic interface ITempFileManager {\n\n    void clear();\n\n    public ITempFile createTempFile(String filename_hint) throws Exception;\n}\n"
  },
  {
    "path": "httpserver/src/main/java/com/megaease/easeagent/httpserver/nanohttpd/protocols/http/threading/DefaultAsyncRunner.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.httpserver.nanohttpd.protocols.http.threading;\n\n/*\n * #%L\n * NanoHttpd-Core\n * %%\n * Copyright (C) 2012 - 2016 nanohttpd\n * %%\n * Redistribution and use in source and binary forms, with or without modification,\n * are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice, this\n *    list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n *    this list of conditions and the following disclaimer in the documentation\n *    and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the nanohttpd nor the names of its contributors\n *    may be used to endorse or promote products derived from this software without\n *    specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,\n * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE\n * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED\n * OF THE POSSIBILITY OF SUCH DAMAGE.\n * #L%\n */\n\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.ClientHandler;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * Default threading strategy for NanoHTTPD.\n * <p/>\n * <p>\n * By default, the server spawns a new Thread for every incoming request. These\n * are set to <i>daemon</i> status, and named according to the request number.\n * The name is useful when profiling the application.\n * </p>\n */\npublic class DefaultAsyncRunner implements IAsyncRunner {\n\n    protected long requestCount;\n\n    private final List<ClientHandler> running = Collections.synchronizedList(new ArrayList<ClientHandler>());\n\n    /**\n     * @return a list with currently running clients.\n     */\n    public List<ClientHandler> getRunning() {\n        return running;\n    }\n\n    @Override\n    public void closeAll() {\n        // copy of the list for concurrency\n        for (ClientHandler clientHandler : new ArrayList<ClientHandler>(this.running)) {\n            clientHandler.close();\n        }\n    }\n\n    @Override\n    public void closed(ClientHandler clientHandler) {\n        this.running.remove(clientHandler);\n    }\n\n    @Override\n    public void exec(ClientHandler clientHandler) {\n        ++this.requestCount;\n        this.running.add(clientHandler);\n        createThread(clientHandler).start();\n    }\n\n    protected Thread createThread(ClientHandler clientHandler) {\n        Thread t = new Thread(clientHandler);\n        t.setDaemon(true);\n        t.setName(\"NanoHttpd Request Processor (#\" + this.requestCount + \")\");\n        return t;\n    }\n}\n"
  },
  {
    "path": "httpserver/src/main/java/com/megaease/easeagent/httpserver/nanohttpd/protocols/http/threading/IAsyncRunner.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.httpserver.nanohttpd.protocols.http.threading;\n\n/*\n * #%L\n * NanoHttpd-Core\n * %%\n * Copyright (C) 2012 - 2016 nanohttpd\n * %%\n * Redistribution and use in source and binary forms, with or without modification,\n * are permitted provided that the following conditions are met:\n * \n * 1. Redistributions of source code must retain the above copyright notice, this\n *    list of conditions and the following disclaimer.\n * \n * 2. Redistributions in binary form must reproduce the above copyright notice,\n *    this list of conditions and the following disclaimer in the documentation\n *    and/or other materials provided with the distribution.\n * \n * 3. Neither the name of the nanohttpd nor the names of its contributors\n *    may be used to endorse or promote products derived from this software without\n *    specific prior written permission.\n * \n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,\n * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE\n * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED\n * OF THE POSSIBILITY OF SUCH DAMAGE.\n * #L%\n */\n\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.ClientHandler;\n\n/**\n * Pluggable strategy for asynchronously executing requests.\n */\npublic interface IAsyncRunner {\n\n    void closeAll();\n\n    void closed(ClientHandler clientHandler);\n\n    void exec(ClientHandler code);\n}\n"
  },
  {
    "path": "httpserver/src/main/java/com/megaease/easeagent/httpserver/nanohttpd/router/RouterNanoHTTPD.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.httpserver.nanohttpd.router;\n\n/*\n * #%L\n * NanoHttpd-Samples\n * %%\n * Copyright (C) 2012 - 2015 nanohttpd\n * %%\n * Redistribution and use in source and binary forms, with or without modification,\n * are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice, this\n *    list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n *    this list of conditions and the following disclaimer in the documentation\n *    and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the nanohttpd nor the names of its contributors\n *    may be used to endorse or promote products derived from this software without\n *    specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,\n * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE\n * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED\n * OF THE POSSIBILITY OF SUCH DAMAGE.\n * #L%\n */\n\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.IHTTPSession;\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.NanoHTTPD;\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.response.IStatus;\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.response.Response;\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.response.Status;\n\nimport java.io.*;\nimport java.util.*;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n/**\n * @author vnnv\n * @author ritchieGitHub\n */\npublic class RouterNanoHTTPD extends NanoHTTPD {\n\n    /**\n     * logger to log to.\n     */\n    private static final Logger LOG = Logger.getLogger(RouterNanoHTTPD.class.getName());\n\n    public interface UriResponder {\n\n        public Response get(UriResource uriResource, Map<String, String> urlParams, IHTTPSession session);\n\n        public Response put(UriResource uriResource, Map<String, String> urlParams, IHTTPSession session);\n\n        public Response post(UriResource uriResource, Map<String, String> urlParams, IHTTPSession session);\n\n        public Response delete(UriResource uriResource, Map<String, String> urlParams, IHTTPSession session);\n\n        public Response other(String method, UriResource uriResource, Map<String, String> urlParams, IHTTPSession session);\n    }\n\n    /**\n     * General nanolet to inherit from if you provide stream data, only chucked\n     * responses will be generated.\n     */\n    public static abstract class DefaultStreamHandler implements UriResponder {\n\n        public abstract String getMimeType();\n\n        public abstract IStatus getStatus();\n\n        public abstract InputStream getData();\n\n        public Response get(UriResource uriResource, Map<String, String> urlParams, IHTTPSession session) {\n            return Response.newChunkedResponse(getStatus(), getMimeType(), getData());\n        }\n\n        public Response post(UriResource uriResource, Map<String, String> urlParams, IHTTPSession session) {\n            return get(uriResource, urlParams, session);\n        }\n\n        public Response put(UriResource uriResource, Map<String, String> urlParams, IHTTPSession session) {\n            return get(uriResource, urlParams, session);\n        }\n\n        public Response delete(UriResource uriResource, Map<String, String> urlParams, IHTTPSession session) {\n            return get(uriResource, urlParams, session);\n        }\n\n        public Response other(String method, UriResource uriResource, Map<String, String> urlParams, IHTTPSession session) {\n            return get(uriResource, urlParams, session);\n        }\n    }\n\n    /**\n     * General nanolet to inherit from if you provide text or html data, only\n     * fixed size responses will be generated.\n     */\n    public static abstract class DefaultHandler extends DefaultStreamHandler {\n\n        public abstract String getText();\n\n        public abstract IStatus getStatus();\n\n        public Response get(UriResource uriResource, Map<String, String> urlParams, IHTTPSession session) {\n            return Response.newFixedLengthResponse(getStatus(), getMimeType(), getText());\n        }\n\n        @Override\n        public InputStream getData() {\n            throw new IllegalStateException(\"this method should not be called in a text based nanolet\");\n        }\n    }\n\n    /**\n     * General nanolet to print debug info's as a html page.\n     */\n    public static class GeneralHandler extends DefaultHandler {\n\n        @Override\n        public String getText() {\n            throw new IllegalStateException(\"this method should not be called\");\n        }\n\n        @Override\n        public String getMimeType() {\n            return \"text/html\";\n        }\n\n        @Override\n        public IStatus getStatus() {\n            return Status.OK;\n        }\n\n\n        /**\n         * Clean XSS.\n         *\n         * @param value the value to be cleaned\n         * @return the cleaned value\n         */\n        private String cleanXSS(String value) {\n\t\t    // You'll need to remove the spaces from the html entities below\n\t\t    value = value.replaceAll(\"<\", \"& lt;\").replaceAll(\">\", \"& gt;\");\n            value = value.replaceAll(\"\\\\(\", \"& #40;\").replaceAll(\"\\\\)\", \"& #41;\");\n            value = value.replaceAll(\"'\", \"& #39;\");\n            value = value.replaceAll(\"eval\\\\((.*)\\\\)\", \"\");\n            value = value.replaceAll(\"[\\\\\\\"\\\\\\'][\\\\s]*javascript:(.*)[\\\\\\\"\\\\\\']\", \"\\\"\\\"\");\n            value = value.replaceAll(\"script\", \"\");\n\n            return value;\n\t    }\n\n        public Response get(UriResource uriResource, Map<String, String> urlParams, IHTTPSession session) {\n            StringBuilder text = new StringBuilder(\"<html><body>\");\n\n            text.append(\"<h1>Url: \");\n            text.append(cleanXSS(session.getUri()));\n            text.append(\"</h1><br>\");\n\n            Map<String, String> queryParams = session.getParams();\n            if (queryParams.size() > 0) {\n                for (Map.Entry<String, String> entry : queryParams.entrySet()) {\n                    String key = entry.getKey();\n                    String value = entry.getValue();\n\n                    key = cleanXSS(key);\n                    value = cleanXSS(value);\n\n                    text.append(\"<p>Param '\");\n                    text.append(key);\n                    text.append(\"' = \");\n                    text.append(value);\n                    text.append(\"</p>\");\n                }\n            } else {\n                text.append(\"<p>no params in url</p><br>\");\n            }\n\n            return Response.newFixedLengthResponse(getStatus(), getMimeType(), text.toString());\n        }\n    }\n\n    /**\n     * General nanolet to print debug info's as a html page.\n     */\n    public static class StaticPageHandler extends DefaultHandler {\n\n        private static String[] getPathArray(String uri) {\n            String array[] = uri.split(\"/\");\n            ArrayList<String> pathArray = new ArrayList<String>();\n\n            for (String s : array) {\n                if (s.length() > 0)\n                    pathArray.add(s);\n            }\n\n            return pathArray.toArray(new String[]{});\n\n        }\n\n        @Override\n        public String getText() {\n            throw new IllegalStateException(\"this method should not be called\");\n        }\n\n        @Override\n        public String getMimeType() {\n            throw new IllegalStateException(\"this method should not be called\");\n        }\n\n        @Override\n        public IStatus getStatus() {\n            return Status.OK;\n        }\n\n        public Response get(UriResource uriResource, Map<String, String> urlParams, IHTTPSession session) {\n            String baseUri = uriResource.getUri();\n            String realUri = normalizeUri(session.getUri());\n            for (int index = 0; index < Math.min(baseUri.length(), realUri.length()); index++) {\n                if (baseUri.charAt(index) != realUri.charAt(index)) {\n                    realUri = normalizeUri(realUri.substring(index));\n                    break;\n                }\n            }\n            File fileOrdirectory = uriResource.initParameter(File.class);\n            for (String pathPart : getPathArray(realUri)) {\n                fileOrdirectory = new File(fileOrdirectory, pathPart);\n            }\n            if (fileOrdirectory.isDirectory()) {\n                fileOrdirectory = new File(fileOrdirectory, \"index.html\");\n                if (!fileOrdirectory.exists()) {\n                    fileOrdirectory = new File(fileOrdirectory.getParentFile(), \"index.htm\");\n                }\n            }\n            if (!fileOrdirectory.exists() || !fileOrdirectory.isFile()) {\n                return new Error404UriHandler().get(uriResource, urlParams, session);\n            } else {\n                try {\n                    return Response.newChunkedResponse(getStatus(), getMimeTypeForFile(fileOrdirectory.getName()), fileToInputStream(fileOrdirectory));\n                } catch (IOException ioe) {\n                    return Response.newFixedLengthResponse(Status.REQUEST_TIMEOUT, \"text/plain\", (String) null);\n                }\n            }\n        }\n\n        protected BufferedInputStream fileToInputStream(File fileOrdirectory) throws IOException {\n            return new BufferedInputStream(new FileInputStream(fileOrdirectory));\n        }\n    }\n\n    /**\n     * Handling error 404 - unrecognized urls\n     */\n    public static class Error404UriHandler extends DefaultHandler {\n\n        public String getText() {\n            return \"<html><body><h3>Error 404: the requested page doesn't exist.</h3></body></html>\";\n        }\n\n        @Override\n        public String getMimeType() {\n            return \"text/html\";\n        }\n\n        @Override\n        public IStatus getStatus() {\n            return Status.NOT_FOUND;\n        }\n    }\n\n    /**\n     * Handling index\n     */\n    public static class IndexHandler extends DefaultHandler {\n\n        public String getText() {\n            return \"<html><body><h2>Hello world!</h3></body></html>\";\n        }\n\n        @Override\n        public String getMimeType() {\n            return \"text/html\";\n        }\n\n        @Override\n        public IStatus getStatus() {\n            return Status.OK;\n        }\n\n    }\n\n    public static class NotImplementedHandler extends DefaultHandler {\n\n        public String getText() {\n            return \"<html><body><h2>The uri is mapped in the router, but no handler is specified. <br> Status: Not implemented!</h3></body></html>\";\n        }\n\n        @Override\n        public String getMimeType() {\n            return \"text/html\";\n        }\n\n        @Override\n        public IStatus getStatus() {\n            return Status.OK;\n        }\n    }\n\n    public static String normalizeUri(String value) {\n        if (value == null) {\n            return value;\n        }\n        if (value.startsWith(\"/\")) {\n            value = value.substring(1);\n        }\n        if (value.endsWith(\"/\")) {\n            value = value.substring(0, value.length() - 1);\n        }\n        return value;\n\n    }\n\n    public static class UriResource implements Comparable<UriResource> {\n\n        private static final Pattern PARAM_PATTERN = Pattern.compile(\"(?<=(^|/)):[a-zA-Z0-9_-]+(?=(/|$))\");\n\n        private static final String PARAM_MATCHER = \"([A-Za-z0-9\\\\-\\\\._~:/?#\\\\[\\\\]@!\\\\$&'\\\\(\\\\)\\\\*\\\\+,;=\\\\s]+)\";\n\n        private static final Map<String, String> EMPTY = Collections.unmodifiableMap(new HashMap<String, String>());\n\n        private final String uri;\n\n        private final Pattern uriPattern;\n\n        private int priority;\n\n        private final Class<?> handler;\n\n        private final Object[] initParameter;\n\n        private final List<String> uriParams = new ArrayList<String>();\n\n        public UriResource(String uri, int priority, Class<?> handler, Object... initParameter) {\n            this(uri, handler, initParameter);\n            this.priority = priority + uriParams.size() * 1000;\n        }\n\n        public UriResource(String uri, Class<?> handler, Object... initParameter) {\n            this.handler = handler;\n            this.initParameter = initParameter;\n            if (uri != null) {\n                this.uri = normalizeUri(uri);\n                parse();\n                this.uriPattern = createUriPattern();\n            } else {\n                this.uriPattern = null;\n                this.uri = null;\n            }\n        }\n\n        private void parse() {\n        }\n\n        private Pattern createUriPattern() {\n            String patternUri = uri;\n            Matcher matcher = PARAM_PATTERN.matcher(patternUri);\n            int start = 0;\n            while (matcher.find(start)) {\n                uriParams.add(patternUri.substring(matcher.start() + 1, matcher.end()));\n                patternUri = new StringBuilder(patternUri.substring(0, matcher.start()))//\n                        .append(PARAM_MATCHER)//\n                        .append(patternUri.substring(matcher.end())).toString();\n                start = matcher.start() + PARAM_MATCHER.length();\n                matcher = PARAM_PATTERN.matcher(patternUri);\n            }\n            return Pattern.compile(patternUri);\n        }\n\n        public Response process(Map<String, String> urlParams, IHTTPSession session) {\n            String error = \"General error!\";\n            if (handler != null) {\n                try {\n                    Object object = handler.newInstance();\n                    if (object instanceof UriResponder) {\n                        UriResponder responder = (UriResponder) object;\n                        switch (session.getMethod()) {\n                            case GET:\n                                return responder.get(this, urlParams, session);\n                            case POST:\n                                return responder.post(this, urlParams, session);\n                            case PUT:\n                                return responder.put(this, urlParams, session);\n                            case DELETE:\n                                return responder.delete(this, urlParams, session);\n                            default:\n                                return responder.other(session.getMethod().toString(), this, urlParams, session);\n                        }\n                    } else {\n                        return Response.newFixedLengthResponse(Status.OK, \"text/plain\", //\n                                new StringBuilder(\"Return: \")//\n                                        .append(handler.getCanonicalName())//\n                                        .append(\".toString() -> \")//\n                                        .append(object)//\n                                        .toString());\n                    }\n                } catch (Exception e) {\n                    error = \"Error: \" + e.getClass().getName() + \" : \" + e.getMessage();\n                    LOG.log(Level.SEVERE, error, e);\n                }\n            }\n            return Response.newFixedLengthResponse(Status.INTERNAL_ERROR, \"text/plain\", error);\n        }\n\n        @Override\n        public String toString() {\n            return new StringBuilder(\"UrlResource{uri='\").append((uri == null ? \"/\" : uri))//\n                    .append(\"', urlParts=\").append(uriParams)//\n                    .append('}')//\n                    .toString();\n        }\n\n        public String getUri() {\n            return uri;\n        }\n\n        public <T> T initParameter(Class<T> paramClazz) {\n            return initParameter(0, paramClazz);\n        }\n\n        public <T> T initParameter(int parameterIndex, Class<T> paramClazz) {\n            if (initParameter.length > parameterIndex) {\n                return paramClazz.cast(initParameter[parameterIndex]);\n            }\n            LOG.severe(\"init parameter index not available \" + parameterIndex);\n            return null;\n        }\n\n        public Map<String, String> match(String url) {\n            Matcher matcher = uriPattern.matcher(url);\n            if (matcher.matches()) {\n                if (uriParams.size() > 0) {\n                    Map<String, String> result = new HashMap<String, String>();\n                    for (int i = 1; i <= matcher.groupCount(); i++) {\n                        result.put(uriParams.get(i - 1), matcher.group(i));\n                    }\n                    return result;\n                } else {\n                    return EMPTY;\n                }\n            }\n            return null;\n        }\n\n        @Override\n        public int compareTo(UriResource that) {\n            if (that == null) {\n                return 1;\n            } else if (this.priority > that.priority) {\n                return 1;\n            } else if (this.priority < that.priority) {\n                return -1;\n            } else {\n                return 0;\n            }\n        }\n\n        public void setPriority(int priority) {\n            this.priority = priority;\n        }\n\n    }\n\n    public static interface IRoutePrioritizer {\n\n        void addRoute(String url, int priority, Class<?> handler, Object... initParameter);\n\n        void removeRoute(String url);\n\n        Collection<UriResource> getPrioritizedRoutes();\n\n        void setNotImplemented(Class<?> notImplemented);\n    }\n\n    public static abstract class BaseRoutePrioritizer implements IRoutePrioritizer {\n\n        protected Class<?> notImplemented;\n\n        protected final Collection<UriResource> mappings;\n\n        public BaseRoutePrioritizer() {\n            this.mappings = newMappingCollection();\n            this.notImplemented = NotImplementedHandler.class;\n        }\n\n        @Override\n        public void addRoute(String url, int priority, Class<?> handler, Object... initParameter) {\n            if (url != null) {\n                if (handler != null) {\n                    mappings.add(new UriResource(url, priority + mappings.size(), handler, initParameter));\n                } else {\n                    mappings.add(new UriResource(url, priority + mappings.size(), notImplemented));\n                }\n            }\n        }\n\n        public void removeRoute(String url) {\n            String uriToDelete = normalizeUri(url);\n            Iterator<UriResource> iter = mappings.iterator();\n            while (iter.hasNext()) {\n                UriResource uriResource = iter.next();\n                if (uriToDelete.equals(uriResource.getUri())) {\n                    iter.remove();\n                    break;\n                }\n            }\n        }\n\n        @Override\n        public Collection<UriResource> getPrioritizedRoutes() {\n            return Collections.unmodifiableCollection(mappings);\n        }\n\n        @Override\n        public void setNotImplemented(Class<?> handler) {\n            notImplemented = handler;\n        }\n\n        protected abstract Collection<UriResource> newMappingCollection();\n    }\n\n    public static class ProvidedPriorityRoutePrioritizer extends BaseRoutePrioritizer {\n\n        @Override\n        public void addRoute(String url, int priority, Class<?> handler, Object... initParameter) {\n            if (url != null) {\n                UriResource resource = null;\n                if (handler != null) {\n                    resource = new UriResource(url, handler, initParameter);\n                } else {\n                    resource = new UriResource(url, handler, notImplemented);\n                }\n\n                resource.setPriority(priority);\n                mappings.add(resource);\n            }\n        }\n\n        @Override\n        protected Collection<UriResource> newMappingCollection() {\n            return new PriorityQueue<UriResource>();\n        }\n\n    }\n\n    public static class DefaultRoutePrioritizer extends BaseRoutePrioritizer {\n\n        protected Collection<UriResource> newMappingCollection() {\n            return new PriorityQueue<UriResource>();\n        }\n    }\n\n    public static class InsertionOrderRoutePrioritizer extends BaseRoutePrioritizer {\n\n        protected Collection<UriResource> newMappingCollection() {\n            return new ArrayList<UriResource>();\n        }\n    }\n\n    public static class UriRouter {\n\n        private UriResource error404Url;\n\n        private IRoutePrioritizer routePrioritizer;\n\n        public UriRouter() {\n            this.routePrioritizer = new DefaultRoutePrioritizer();\n        }\n\n        /**\n         * Search in the mappings if the given url matches some of the rules If\n         * there are more than one marches returns the rule with less parameters\n         * e.g. mapping 1 = /user/:id mapping 2 = /user/help if the incoming uri\n         * is www.example.com/user/help - mapping 2 is returned if the incoming\n         * uri is www.example.com/user/3232 - mapping 1 is returned\n         */\n        public Response process(IHTTPSession session) {\n            String work = normalizeUri(session.getUri());\n            Map<String, String> params = null;\n            UriResource uriResource = error404Url;\n            for (UriResource u : routePrioritizer.getPrioritizedRoutes()) {\n                params = u.match(work);\n                if (params != null) {\n                    uriResource = u;\n                    break;\n                }\n            }\n            return uriResource.process(params, session);\n        }\n\n        private void addRoute(String url, int priority, Class<?> handler, Object... initParameter) {\n            routePrioritizer.addRoute(url, priority, handler, initParameter);\n        }\n\n        private void removeRoute(String url) {\n            routePrioritizer.removeRoute(url);\n        }\n\n        public void setNotFoundHandler(Class<?> handler) {\n            error404Url = new UriResource(null, 100, handler);\n        }\n\n        public void setNotImplemented(Class<?> handler) {\n            routePrioritizer.setNotImplemented(handler);\n        }\n\n        public void setRoutePrioritizer(IRoutePrioritizer routePrioritizer) {\n            this.routePrioritizer = routePrioritizer;\n        }\n\n    }\n\n    private UriRouter router;\n\n    public RouterNanoHTTPD(int port) {\n        super(port);\n        router = new UriRouter();\n    }\n\n    public RouterNanoHTTPD(String hostname, int port) {\n        super(hostname, port);\n        router = new UriRouter();\n    }\n\n    /**\n     * default routings, they are over writable.\n     *\n     * <pre>\n     * router.setNotFoundHandler(GeneralHandler.class);\n     * </pre>\n     */\n\n    public void addMappings() {\n        router.setNotImplemented(NotImplementedHandler.class);\n        router.setNotFoundHandler(Error404UriHandler.class);\n        router.addRoute(\"/\", Integer.MAX_VALUE / 2, IndexHandler.class);\n        router.addRoute(\"/index.html\", Integer.MAX_VALUE / 2, IndexHandler.class);\n    }\n\n    public void addRoute(String url, Class<?> handler, Object... initParameter) {\n        router.addRoute(url, 100, handler, initParameter);\n    }\n\n    public <T extends UriResponder> void setNotImplementedHandler(Class<T> handler) {\n        router.setNotImplemented(handler);\n    }\n\n    public <T extends UriResponder> void setNotFoundHandler(Class<T> handler) {\n        router.setNotFoundHandler(handler);\n    }\n\n    public void removeRoute(String url) {\n        router.removeRoute(url);\n    }\n\n    public void setRoutePrioritizer(IRoutePrioritizer routePrioritizer) {\n        router.setRoutePrioritizer(routePrioritizer);\n    }\n\n    @Override\n    public Response serve(IHTTPSession session) {\n        // Try to find match\n        return router.process(session);\n    }\n}\n"
  },
  {
    "path": "httpserver/src/main/java/com/megaease/easeagent/httpserver/nanohttpd/util/IFactory.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.httpserver.nanohttpd.util;\n\n/*\n * #%L\n * NanoHttpd-Core\n * %%\n * Copyright (C) 2012 - 2016 nanohttpd\n * %%\n * Redistribution and use in source and binary forms, with or without modification,\n * are permitted provided that the following conditions are met:\n * \n * 1. Redistributions of source code must retain the above copyright notice, this\n *    list of conditions and the following disclaimer.\n * \n * 2. Redistributions in binary form must reproduce the above copyright notice,\n *    this list of conditions and the following disclaimer in the documentation\n *    and/or other materials provided with the distribution.\n * \n * 3. Neither the name of the nanohttpd nor the names of its contributors\n *    may be used to endorse or promote products derived from this software without\n *    specific prior written permission.\n * \n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,\n * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE\n * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED\n * OF THE POSSIBILITY OF SUCH DAMAGE.\n * #L%\n */\n\n/**\n * Represents a simple factory\n * \n * @author LordFokas\n * @param <T>\n *            The Type of object to create\n */\npublic interface IFactory<T> {\n\n    T create();\n}\n"
  },
  {
    "path": "httpserver/src/main/java/com/megaease/easeagent/httpserver/nanohttpd/util/IFactoryThrowing.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.httpserver.nanohttpd.util;\n\n/*\n * #%L\n * NanoHttpd-Core\n * %%\n * Copyright (C) 2012 - 2016 nanohttpd\n * %%\n * Redistribution and use in source and binary forms, with or without modification,\n * are permitted provided that the following conditions are met:\n * \n * 1. Redistributions of source code must retain the above copyright notice, this\n *    list of conditions and the following disclaimer.\n * \n * 2. Redistributions in binary form must reproduce the above copyright notice,\n *    this list of conditions and the following disclaimer in the documentation\n *    and/or other materials provided with the distribution.\n * \n * 3. Neither the name of the nanohttpd nor the names of its contributors\n *    may be used to endorse or promote products derived from this software without\n *    specific prior written permission.\n * \n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,\n * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE\n * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED\n * OF THE POSSIBILITY OF SUCH DAMAGE.\n * #L%\n */\n\n/**\n * Represents a factory that can throw an exception instead of actually creating\n * an object\n * \n * @author LordFokas\n * @param <T>\n *            The Type of object to create\n * @param <E>\n *            The base Type of exceptions that can be thrown\n */\npublic interface IFactoryThrowing<T, E extends Throwable> {\n\n    T create() throws E;\n}\n"
  },
  {
    "path": "httpserver/src/main/java/com/megaease/easeagent/httpserver/nanohttpd/util/IHandler.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.httpserver.nanohttpd.util;\n\n/*\n * #%L\n * NanoHttpd-Core\n * %%\n * Copyright (C) 2012 - 2016 nanohttpd\n * %%\n * Redistribution and use in source and binary forms, with or without modification,\n * are permitted provided that the following conditions are met:\n * \n * 1. Redistributions of source code must retain the above copyright notice, this\n *    list of conditions and the following disclaimer.\n * \n * 2. Redistributions in binary form must reproduce the above copyright notice,\n *    this list of conditions and the following disclaimer in the documentation\n *    and/or other materials provided with the distribution.\n * \n * 3. Neither the name of the nanohttpd nor the names of its contributors\n *    may be used to endorse or promote products derived from this software without\n *    specific prior written permission.\n * \n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,\n * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE\n * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED\n * OF THE POSSIBILITY OF SUCH DAMAGE.\n * #L%\n */\n\n/**\n * Defines a generic handler that returns an object of type O when given an\n * object of type I.\n * \n * @author LordFokas\n * @param <I>\n *            The input type.\n * @param <O>\n *            The output type.\n */\npublic interface IHandler<I, O> {\n\n    public O handle(I input);\n}\n"
  },
  {
    "path": "httpserver/src/main/java/com/megaease/easeagent/httpserver/nanohttpd/util/ServerRunner.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.httpserver.nanohttpd.util;\n\n/*\n * #%L\n * NanoHttpd-Webserver\n * %%\n * Copyright (C) 2012 - 2015 nanohttpd\n * %%\n * Redistribution and use in source and binary forms, with or without modification,\n * are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice, this\n *    list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n *    this list of conditions and the following disclaimer in the documentation\n *    and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the nanohttpd nor the names of its contributors\n *    may be used to endorse or promote products derived from this software without\n *    specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,\n * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE\n * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED\n * OF THE POSSIBILITY OF SUCH DAMAGE.\n * #L%\n */\n\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.NanoHTTPD;\n\nimport java.io.IOException;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\n\npublic class ServerRunner {\n\n    /**\n     * logger to log to.\n     */\n    private static final Logger LOG = Logger.getLogger(ServerRunner.class.getName());\n\n    public static void executeInstance(NanoHTTPD server) {\n        try {\n            server.start(NanoHTTPD.SOCKET_READ_TIMEOUT, false);\n        } catch (IOException ioe) {\n            System.err.println(\"Couldn't start server:\\n\" + ioe);\n            System.exit(-1);\n        }\n\n        System.out.println(\"Server started, Hit Enter to stop.\\n\");\n\n        try {\n            System.in.read();\n        } catch (Throwable ignored) {\n        }\n\n        server.stop();\n        System.out.println(\"Server stopped.\\n\");\n    }\n\n    public static <T extends NanoHTTPD> void run(Class<T> serverClass) {\n        try {\n            executeInstance(serverClass.newInstance());\n        } catch (Exception e) {\n            ServerRunner.LOG.log(Level.SEVERE, \"Could not create server\", e);\n        }\n    }\n}\n"
  },
  {
    "path": "loader/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  Copyright (c) 2017, MegaEase\n  All rights reserved.\n\n  Licensed under the Apache License, Version 2.0 (the \"License\");\n  you may not use this file except in compliance with the License.\n  You may obtain a copy of the License at\n\n      http://www.apache.org/licenses/LICENSE-2.0\n\n  Unless required by applicable law or agreed to in writing, software\n  distributed under the License is distributed on an \"AS IS\" BASIS,\n  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  See the License for the specific language governing permissions and\n  limitations under the License.\n-->\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>easeagent</artifactId>\n        <groupId>com.megaease.easeagent</groupId>\n        <version>2.3.0</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>loader</artifactId>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.google.guava</groupId>\n            <artifactId>guava</artifactId>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-shade-plugin</artifactId>\n                <version>${version.maven-shade-plugin}</version>\n                <configuration>\n                    <minimizeJar>true</minimizeJar>\n                    <relocations>\n                        <relocation>\n                            <pattern>org.springframework</pattern>\n                            <shadedPattern>spring</shadedPattern>\n                        </relocation>\n                        <relocation>\n                            <pattern>com.google</pattern>\n                            <shadedPattern>com.megaease.easeagent.google</shadedPattern>\n                        </relocation>\n                    </relocations>\n                </configuration>\n                <executions>\n                    <execution>\n                        <phase>package</phase>\n                        <goals>\n                            <goal>shade</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n        </plugins>\n    </build>\n</project>\n"
  },
  {
    "path": "loader/src/main/java/com/megaease/easeagent/EaseAgentClassLoader.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent;\n\nimport java.lang.ref.WeakReference;\nimport java.net.URL;\nimport java.net.URLClassLoader;\nimport java.util.Objects;\nimport java.util.Set;\nimport java.util.concurrent.CopyOnWriteArraySet;\n\n/**\n * EaseAgent's exclusive classloader, used to isolate classes\n */\npublic class EaseAgentClassLoader extends URLClassLoader {\n    static {\n        ClassLoader.registerAsParallelCapable();\n    }\n\n    private final Set<WeakReference<ClassLoader>> externals = new CopyOnWriteArraySet<>();\n\n    public EaseAgentClassLoader(URL[] urls, ClassLoader parent) {\n        super(urls, parent);\n    }\n\n    @SuppressWarnings(\"unused\")\n    public void add(ClassLoader cl) {\n        if (cl != null && !Objects.equals(cl, this)) {\n            externals.add(new WeakReference<>(cl));\n        }\n    }\n\n    @Override\n    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {\n        try {\n            return super.loadClass(name, resolve);\n        } catch (ClassNotFoundException e) {\n            for (WeakReference<ClassLoader> external : externals) {\n                try {\n                    ClassLoader cl = external.get();\n                    if (cl == null) {\n                        continue;\n                    }\n                    final Class<?> aClass = cl.loadClass(name);\n                    if (resolve) {\n                        resolveClass(aClass);\n                    }\n                    return aClass;\n                } catch (ClassNotFoundException ignored) {\n                    // ignored\n                }\n            }\n\n            throw e;\n        }\n    }\n\n    @Override\n    public URL findResource(String name) {\n        URL url = super.findResource(name);\n        if (url == null) {\n            for (WeakReference<ClassLoader> external : externals) {\n                try {\n                    ClassLoader cl = external.get();\n                    url = cl.getResource(name);\n                    if (url != null) {\n                        return url;\n                    }\n                } catch (Exception ignored) {\n                    // ignored\n                }\n            }\n        }\n        return url;\n    }\n}\n"
  },
  {
    "path": "loader/src/main/java/com/megaease/easeagent/JarCache.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent;\n\nimport java.io.*;\nimport java.net.URL;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Random;\nimport java.util.jar.Attributes;\nimport java.util.jar.JarFile;\nimport java.util.jar.Manifest;\n\n/**\n * EaseAgent's jar reading cache.\n * Only one layer of sub-jar packages is read, and multi-layer nested reading is not supported.\n * easeagent has only one layer of sub-jar packages, so only one layer needs to be read.\n * It does not need to use spring-boot's loader, which reduces dependencies and facilitates spring-boot business upgrades\n */\npublic class JarCache {\n    private static final int EOF = -1;\n    private static final int BUFFER_SIZE = 4096;\n\n    private final JarFile jarFile;\n    private final Map<String, JarFile> childJars;\n    private final Map<String, URL> childUrls;\n\n    public JarCache(JarFile jarFile, Map<String, JarFile> childJars, Map<String, URL> childUrls) throws IOException {\n        this.jarFile = jarFile;\n        this.childJars = childJars;\n        this.childUrls = childUrls;\n    }\n\n    public ArrayList<URL> nestJarUrls(String prefix) {\n        ArrayList<URL> urls = new ArrayList<>();\n        for (Map.Entry<String, URL> entry : childUrls.entrySet()) {\n            if (entry.getKey().startsWith(prefix)) {\n                urls.add(entry.getValue());\n            }\n        }\n        return urls;\n    }\n\n    public ArrayList<JarFile> nestJarFiles(String prefix) {\n        ArrayList<JarFile> jarFiles = new ArrayList<>();\n        for (Map.Entry<String, JarFile> entry : childJars.entrySet()) {\n            if (entry.getKey().startsWith(prefix)) {\n                jarFiles.add(entry.getValue());\n            }\n        }\n        return jarFiles;\n    }\n\n    public Manifest getManifest() throws IOException {\n        return this.jarFile.getManifest();\n    }\n\n\n    static JarCache build(File file) throws IOException {\n        final JarFile jarFile = new JarFile(file);\n        String tmpDir = getTmpDir(jarFile);\n        Map<String, JarFile> childJars = new HashMap<>();\n        Map<String, URL> childUrls = new HashMap<>();\n        jarFile.stream().forEach(jarEntry -> {\n            String name = jarEntry.getName();\n            if (!jarEntry.isDirectory() && name.endsWith(\".jar\")) {\n                try (InputStream input = jarFile.getInputStream(jarEntry)) {\n                    File output = createTempJarFile(tmpDir, input, jarEntry.getName());\n                    JarFile childJarFile = new JarFile(output);\n                    childJars.put(name, childJarFile);\n                    childUrls.put(name, output.toURI().toURL());\n                } catch (IOException e) {\n                    throw new RuntimeException(e);\n                }\n            }\n        });\n        return new JarCache(jarFile, childJars, childUrls);\n    }\n\n    private static File createTempJarFile(String tmpDir, InputStream input, String outputName) throws IOException {\n        File dir;\n        String fName = (new File(outputName)).getName();\n        if (fName.length() < outputName.length()) {\n            String localDir = outputName.substring(0, outputName.length() - fName.length());\n            Path path = Paths.get(tmpDir + File.separatorChar + localDir);\n            dir = Files.createDirectories(path).toFile();\n        } else {\n            dir = new File(tmpDir);\n        }\n        File f = new File(dir, fName);\n        f.deleteOnExit();\n        try (FileOutputStream outputStream = new FileOutputStream(f)) {\n            copy(input, outputStream);\n        }\n\n        return f;\n    }\n\n    public static void copy(InputStream input, OutputStream output) throws IOException {\n        int n;\n        final byte[] buffer = new byte[BUFFER_SIZE];\n        while (EOF != (n = input.read(buffer))) {\n            output.write(buffer, 0, n);\n        }\n    }\n\n    public static String getTmpDir(JarFile jarFile) throws IOException {\n        String tmp = System.getProperty(\"java.io.tmpdir\");\n        Random random = new Random();\n        String dirName = \"easeagent-\" + getAttribute(jarFile, \"Easeagent-Version\") + \"-\" + Math.abs(random.nextLong());\n        if (tmp != null && tmp.endsWith(String.valueOf(File.separatorChar))) {\n            return tmp + dirName + File.separatorChar;\n        }\n        return tmp + File.separatorChar + dirName + File.separatorChar;\n    }\n\n    public static String getAttribute(JarFile jarFile, String key) throws IOException {\n        final Attributes attributes = jarFile.getManifest().getMainAttributes();\n        return attributes.getValue(key);\n    }\n\n}\n"
  },
  {
    "path": "loader/src/main/java/com/megaease/easeagent/Main.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent;\n\nimport com.google.common.base.Strings;\nimport lombok.SneakyThrows;\n\nimport java.io.File;\nimport java.lang.instrument.Instrumentation;\nimport java.lang.reflect.Field;\nimport java.net.*;\nimport java.security.CodeSource;\nimport java.security.ProtectionDomain;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.concurrent.Callable;\nimport java.util.jar.Attributes;\nimport java.util.jar.JarFile;\n\npublic class Main {\n    private static final ClassLoader BOOTSTRAP_CLASS_LOADER = null;\n    private static final String LIB = \"lib/\";\n    private static final String BOOTSTRAP = \"boot/\";\n    private static final String SLf4J2 = \"log4j2/\";\n    private static final String PLUGINS = \"plugins/\";\n    private static final String LOGGING_PROPERTY = \"Logging-Property\";\n    private static final String EASEAGENT_LOG_CONF = \"easeagent.log.conf\";\n    private static final String EASEAGENT_LOG_CONF_ENV_KEY = \"EASEAGENT_LOG_CONF\";\n    private static final String DEFAULT_AGENT_LOG_CONF = \"easeagent-log4j2.xml\";\n    private static ClassLoader loader;\n    private static JarCache JAR_CACHE;\n\n    public static void premain(final String args, final Instrumentation inst) throws Exception {\n        File jar = getArchiveFileContains();\n        JAR_CACHE = JarCache.build(jar);\n\n        // custom classloader\n        ArrayList<URL> urls = JAR_CACHE.nestJarUrls(LIB);\n        urls.addAll(JAR_CACHE.nestJarUrls(PLUGINS));\n        urls.addAll(JAR_CACHE.nestJarUrls(SLf4J2));\n        File p = new File(jar.getParent() + File.separator + \"plugins\");\n        if (p.exists()) {\n            urls.addAll(directoryPluginUrls(p));\n        }\n\n        loader = buildClassLoader(urls.toArray(new URL[0]));\n\n        // install bootstrap jar\n        final ArrayList<JarFile> bootUrls = JAR_CACHE.nestJarFiles(BOOTSTRAP);\n        bootUrls.forEach(url -> installBootstrapJar(url, inst));\n\n        final Attributes attributes = JAR_CACHE.getManifest().getMainAttributes();\n        final String loggingProperty = attributes.getValue(LOGGING_PROPERTY);\n        final String bootstrap = attributes.getValue(\"Bootstrap-Class\");\n        initEaseAgentSlf4j2Dir(JAR_CACHE, loader);\n\n        switchLoggingProperty(loader, loggingProperty, () -> {\n            initAgentSlf4jMDC(loader);\n            loader.loadClass(bootstrap)\n                .getMethod(\"premain\", String.class, Instrumentation.class, String.class)\n                .invoke(null, args, inst, jar.getPath());\n            return null;\n        });\n    }\n\n    private static void initAgentSlf4jMDC(ClassLoader loader) {\n        // init sfl4j MDC for inner agent\n        Class<?> mdcClass;\n        try {\n            mdcClass = loader.loadClass(\"org.slf4j.MDC\");\n            // just make a reference to mdcClass avoiding JIT remove\n            mdcClass.getMethod(\"remove\", String.class)\n                .invoke(null, \"EaseAgent\");\n        } catch (Exception ignored) {\n            // ignored\n        }\n    }\n\n    private static void installBootstrapJar(JarFile file, Instrumentation inst) {\n        inst.appendToBootstrapClassLoaderSearch(file);\n    }\n\n    private static void initEaseAgentSlf4j2Dir(JarCache archive, final ClassLoader bootstrapLoader) throws Exception {\n        final URL[] slf4j2Urls = archive.nestJarUrls(SLf4J2).toArray(new URL[0]);\n        final ClassLoader slf4j2Loader = new URLClassLoader(slf4j2Urls, null);\n        Class<?> classLoaderSupplier = bootstrapLoader.loadClass(\"com.megaease.easeagent.log4j2.FinalClassloaderSupplier\");\n        Field field = classLoaderSupplier.getDeclaredField(\"CLASSLOADER\");\n        field.set(null, slf4j2Loader);\n    }\n\n    /**\n     * Switching the system property temporary could fix the problem of conflict of logging configuration\n     * when host used the same logging library as agent.\n     */\n    private static void switchLoggingProperty(ClassLoader loader, String hostKey, Callable<Void> callable)\n        throws Exception {\n        final Thread t = Thread.currentThread();\n        final ClassLoader ccl = t.getContextClassLoader();\n\n        t.setContextClassLoader(loader);\n\n        // get config from system properties\n        final String host = System.getProperty(hostKey);\n        final String agent = getLogConfigPath();\n\n        // Redirect config of host to agent\n        System.setProperty(hostKey, agent);\n\n        try {\n            callable.call();\n        } finally {\n            t.setContextClassLoader(ccl);\n            // Recovery host configuration\n            if (host == null) {\n                System.getProperties().remove(hostKey);\n            } else {\n                System.setProperty(hostKey, host);\n            }\n        }\n    }\n\n    private static String getLogConfigPath() {\n        String logConfigPath = System.getProperty(EASEAGENT_LOG_CONF);\n        if (Strings.isNullOrEmpty(logConfigPath)) {\n            logConfigPath = System.getenv(EASEAGENT_LOG_CONF_ENV_KEY);\n\n        }\n        // if not set, use default\n        if (Strings.isNullOrEmpty(logConfigPath)) {\n            logConfigPath = DEFAULT_AGENT_LOG_CONF;\n\n        }\n        return logConfigPath;\n    }\n\n    private static ArrayList<URL> directoryPluginUrls(File directory) {\n        if (!directory.isDirectory()) {\n            return new ArrayList<>();\n        }\n\n        File[] files = directory.listFiles();\n        if (files == null) {\n            return new ArrayList<>();\n        }\n\n        final ArrayList<URL> urls = new ArrayList<>(files.length);\n\n        Arrays.stream(files).forEach(item -> {\n            if (!item.getName().endsWith(\"jar\")) {\n                return;\n            }\n            try {\n                URL pUrl = item.toURI().toURL();\n                urls.add(pUrl);\n            } catch (MalformedURLException e) {\n                e.printStackTrace();\n            }\n        });\n        return urls;\n    }\n\n    private static File getArchiveFileContains() throws URISyntaxException {\n        final ProtectionDomain protectionDomain = Main.class.getProtectionDomain();\n        final CodeSource codeSource = protectionDomain.getCodeSource();\n        final URI location = (codeSource == null ? null : codeSource.getLocation().toURI());\n        final String path = (location == null ? null : location.getSchemeSpecificPart());\n\n        if (path == null) {\n            throw new IllegalStateException(\"Unable to determine code source archive\");\n        }\n\n        final File root = new File(path);\n        if (!root.exists() || root.isDirectory()) {\n            throw new IllegalStateException(\"Unable to determine code source archive from \" + root);\n        }\n        return root;\n    }\n\n    static ClassLoader buildClassLoader(URL[] urls) {\n        return new EaseAgentClassLoader(urls, BOOTSTRAP_CLASS_LOADER);\n    }\n\n    @SneakyThrows\n    public static void main(String[] args) {\n        // ignored\n    }\n}\n"
  },
  {
    "path": "loader/src/main/java/com/megaease/easeagent/StringSequence.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent;\n\nimport java.util.Objects;\n\n/**\n * extract org.springframework.boot.loader\n */\n@SuppressWarnings({\"unused\", \"SameParameterValue\"})\npublic class StringSequence implements CharSequence {\n    private final String source;\n\n    private final int start;\n\n    private final int end;\n\n    private int hash;\n\n    public StringSequence(String source) {\n        this(source, 0, (source != null) ? source.length() : -1);\n    }\n\n    public StringSequence(String source, int start, int end) {\n        Objects.requireNonNull(source, \"Source must not be null\");\n        if (start < 0) {\n            throw new StringIndexOutOfBoundsException(start);\n        }\n        if (end > source.length()) {\n            throw new StringIndexOutOfBoundsException(end);\n        }\n        this.source = source;\n        this.start = start;\n        this.end = end;\n    }\n\n    StringSequence subSequence(int start) {\n        return subSequence(start, length());\n    }\n\n    @Override\n    public StringSequence subSequence(int start, int end) {\n        int subSequenceStart = this.start + start;\n        int subSequenceEnd = this.start + end;\n        if (subSequenceStart > this.end) {\n            throw new StringIndexOutOfBoundsException(start);\n        }\n        if (subSequenceEnd > this.end) {\n            throw new StringIndexOutOfBoundsException(end);\n        }\n        if (start == 0 && subSequenceEnd == this.end) {\n            return this;\n        }\n        return new StringSequence(this.source, subSequenceStart, subSequenceEnd);\n    }\n\n    /**\n     * Returns {@code true} if the sequence is empty. Public to be compatible with JDK 15.\n     *\n     * @return {@code true} if {@link #length()} is {@code 0}, otherwise {@code false}\n     */\n    public boolean isEmpty() {\n        return length() == 0;\n    }\n\n    @Override\n    public int length() {\n        return this.end - this.start;\n    }\n\n    @Override\n    public char charAt(int index) {\n        return this.source.charAt(this.start + index);\n    }\n\n    int indexOf(char ch) {\n        return this.source.indexOf(ch, this.start) - this.start;\n    }\n\n    int indexOf(String str) {\n        return this.source.indexOf(str, this.start) - this.start;\n    }\n\n    int indexOf(String str, int fromIndex) {\n        return this.source.indexOf(str, this.start + fromIndex) - this.start;\n    }\n\n    boolean startsWith(String prefix) {\n        return startsWith(prefix, 0);\n    }\n\n    boolean startsWith(String prefix, int offset) {\n        int prefixLength = prefix.length();\n        int length = length();\n        if (length - prefixLength - offset < 0) {\n            return false;\n        }\n        return this.source.startsWith(prefix, this.start + offset);\n    }\n\n    @Override\n    public boolean equals(Object obj) {\n        if (this == obj) {\n            return true;\n        }\n        if (!(obj instanceof CharSequence)) {\n            return false;\n        }\n        CharSequence other = (CharSequence) obj;\n        int n = length();\n        if (n != other.length()) {\n            return false;\n        }\n        int i = 0;\n        while (n-- != 0) {\n            if (charAt(i) != other.charAt(i)) {\n                return false;\n            }\n            i++;\n        }\n        return true;\n    }\n\n    @Override\n    public int hashCode() {\n        int hashVal = this.hash;\n        if (hashVal == 0 && length() > 0) {\n            for (int i = this.start; i < this.end; i++) {\n                hashVal = 31 * hashVal + this.source.charAt(i);\n            }\n            this.hash = hashVal;\n        }\n        return hashVal;\n    }\n\n    @Override\n    public String toString() {\n        return this.source.substring(this.start, this.end);\n    }\n}\n"
  },
  {
    "path": "loader/src/test/java/com/megaease/easeagent/MainTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent;\n\nimport org.junit.Test;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.net.URL;\nimport java.util.ArrayList;\n\nimport static org.junit.Assert.assertNotNull;\n\npublic class MainTest {\n\n    @Test\n    public void buildClassLoader() throws IOException, ClassNotFoundException {\n        File jar = new File(ClassLoader.getSystemResource(\"test-mock-load.jar\").getPath());\n        JarCache jarFileArchive = JarCache.build(jar);\n        ArrayList<URL> urls = jarFileArchive.nestJarUrls(\"mock/\");\n        ClassLoader loader = Main.buildClassLoader(urls.toArray(new URL[0]));\n        Class<?> logbackPlugin = loader.loadClass(\"com.megaease.easeagent.mock.utils.MockSystemEnv\");\n        assertNotNull(logbackPlugin);\n    }\n}\n"
  },
  {
    "path": "log4j2/README.md",
    "content": "# HTTPS Supporting\n\n## Import cert file to trust key store\n\n```\nkeytool -importcert -v -trustcacerts -alias some.host.name -file /path/to/cert/file -keystore /path/to/store/file\n```\n\n## Setup System properties\n\n```\njava -Djavax.net.ssl.trustStore=/path/to/store/file -Djavax.net.ssl.trustStorePassword=****** ...\n```\n\n> If `/path/to/store/file` is the JRE default store file, you can ignore properties above.\n\n## Reference\n\n* [Converting PEM-format keys to JKS format](https://docs.oracle.com/cd/E35976_01/server.740/es_admin/src/tadm_ssl_convert_pem_to_jks.html)"
  },
  {
    "path": "log4j2/log4j2-api/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright (c) 2021, MegaEase\n  ~ All rights reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~   http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>log4j2</artifactId>\n        <groupId>com.megaease.easeagent</groupId>\n        <version>2.3.0</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>log4j2-api</artifactId>\n\n    <dependencies>\n        <dependency>\n            <groupId>org.apache.logging.log4j</groupId>\n            <artifactId>log4j-slf4j-impl</artifactId>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.apache.logging.log4j</groupId>\n            <artifactId>log4j-core</artifactId>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.lmax</groupId>\n            <artifactId>disruptor</artifactId>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.github.dreamhead</groupId>\n            <artifactId>moco-core</artifactId>\n            <scope>test</scope>\n        </dependency>\n    </dependencies>\n\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-compiler-plugin</artifactId>\n                <version>3.8.1</version>\n                <configuration>\n                    <source>${version.java}</source>\n                    <target>${version.java}</target>\n                    <encoding>${encoding.file}</encoding>\n                </configuration>\n                <executions>\n                    <execution>\n                        <id>log4j-plugin-processor</id>\n                        <goals>\n                            <goal>compile</goal>\n                        </goals>\n                        <phase>process-classes</phase>\n                        <configuration>\n                            <proc>only</proc>\n                            <annotationProcessors>\n                                <annotationProcessor>\n                                    org.apache.logging.log4j.core.config.plugins.processor.PluginProcessor\n                                </annotationProcessor>\n                            </annotationProcessors>\n                        </configuration>\n                    </execution>\n                </executions>\n            </plugin>\n        </plugins>\n    </build>\n</project>\n"
  },
  {
    "path": "log4j2/log4j2-api/src/main/java/com/megaease/easeagent/log4j2/ClassLoaderUtils.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.log4j2;\n\nimport javax.annotation.Nonnull;\nimport java.io.IOException;\nimport java.net.JarURLConnection;\nimport java.net.URL;\nimport java.net.URLConnection;\nimport java.util.ArrayList;\nimport java.util.Enumeration;\nimport java.util.List;\nimport java.util.function.Function;\n\npublic class ClassLoaderUtils {\n    public static URL[] getAllUrls(ClassLoader classLoader) {\n        return getAllUrls(classLoader, null);\n    }\n\n    public static URL[] getAllUrls(ClassLoader classLoader, Function<URL, Boolean> filter) {\n        List<URL> list = new ArrayList<>();\n        Function<URL, Boolean> f = filter != null ? filter : url -> true;\n        try {\n            Enumeration<URL> enumeration = classLoader.getResources(\"META-INF\");\n            fillUrls(list, enumeration, f);\n            Enumeration<URL> enumeration2 = classLoader.getResources(\"\");\n            fillUrls(list, enumeration2, f);\n\n        } catch (IOException ignore) {\n            //ignore\n        }\n        return list.toArray(new URL[0]);\n    }\n\n\n    private static boolean filter(Function<URL, Boolean> filter, URL url) {\n        Boolean f = filter.apply(url);\n        return f != null && f;\n    }\n\n\n    private static void fillUrls(List<URL> list, Enumeration<URL> enumeration, @Nonnull Function<URL, Boolean> filter) throws IOException {\n        while (enumeration.hasMoreElements()) {\n            URL url = enumeration.nextElement();\n            URLConnection urlConnection = url.openConnection();\n            URL resultUrl = url;\n            if (urlConnection instanceof JarURLConnection) {\n                resultUrl = ((JarURLConnection) urlConnection).getJarFileURL();\n            }\n            if (list.contains(resultUrl)) {\n                continue;\n            }\n            if (filter(filter, resultUrl)) {\n                list.add(resultUrl);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "log4j2/log4j2-api/src/main/java/com/megaease/easeagent/log4j2/ClassloaderSupplier.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.log4j2;\n\nimport java.util.Iterator;\nimport java.util.ServiceLoader;\n\npublic interface ClassloaderSupplier {\n\n    ClassLoader get();\n\n    class ClassloaderSupplierImpl implements ClassloaderSupplier {\n\n        @Override\n        public ClassLoader get() {\n            FinalClassloaderSupplier supplier = new FinalClassloaderSupplier();\n            ClassLoader classLoader = supplier.get();\n            if (classLoader != null) {\n                return classLoader;\n            }\n            ServiceLoader<ClassloaderSupplier> loader = ServiceLoader.load(ClassloaderSupplier.class);\n            Iterator<ClassloaderSupplier> iterator = loader.iterator();\n            while (iterator.hasNext()) {\n                classLoader = iterator.next().get();\n                if (classLoader != null) {\n                    return classLoader;\n                }\n            }\n            return null;\n        }\n    }\n}\n"
  },
  {
    "path": "log4j2/log4j2-api/src/main/java/com/megaease/easeagent/log4j2/FinalClassloaderSupplier.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.log4j2;\n\nimport java.util.function.Supplier;\n\n@SuppressWarnings(\"all\")\npublic class FinalClassloaderSupplier implements Supplier<ClassLoader> {\n    public static volatile ClassLoader CLASSLOADER = null;\n\n\n    @Override\n    public ClassLoader get() {\n        return CLASSLOADER;\n    }\n}\n"
  },
  {
    "path": "log4j2/log4j2-api/src/main/java/com/megaease/easeagent/log4j2/Logger.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.megaease.easeagent.log4j2;\n\n/**\n * This interface extract from slf4j-api to avoid conflict with user's application's usage of slf4j\n * By warping log4j2, Logger is compatible with slf4j interface.\n */\npublic interface Logger {\n\n    /**\n     * Return the name of this <code>Logger</code> instance.\n     * @return name of this logger instance\n     */\n    public String getName();\n\n    /**\n     * Is the logger instance enabled for the TRACE level?\n     *\n     * @return True if this Logger is enabled for the TRACE level,\n     *         false otherwise.\n     */\n    public boolean isTraceEnabled();\n\n    /**\n     * Log a message at the TRACE level.\n     *\n     * @param msg the message string to be logged\n     */\n    public void trace(String msg);\n\n    /**\n     * Log a message at the TRACE level according to the specified format\n     * and argument.\n     * <p/>\n     * <p>This form avoids superfluous object creation when the logger\n     * is disabled for the TRACE level. </p>\n     *\n     * @param format the format string\n     * @param arg    the argument\n     */\n    public void trace(String format, Object arg);\n\n    /**\n     * Log a message at the TRACE level according to the specified format\n     * and arguments.\n     * <p/>\n     * <p>This form avoids superfluous object creation when the logger\n     * is disabled for the TRACE level. </p>\n     *\n     * @param format the format string\n     * @param arg1   the first argument\n     * @param arg2   the second argument\n     */\n    public void trace(String format, Object arg1, Object arg2);\n\n    /**\n     * Log a message at the TRACE level according to the specified format\n     * and arguments.\n     * <p/>\n     * <p>This form avoids superfluous string concatenation when the logger\n     * is disabled for the TRACE level. However, this variant incurs the hidden\n     * (and relatively small) cost of creating an <code>Object[]</code> before invoking the method,\n     * even if this logger is disabled for TRACE. The variants taking {@link #trace(String, Object) one} and\n     * {@link #trace(String, Object, Object) two} arguments exist solely in order to avoid this hidden cost.</p>\n     *\n     * @param format    the format string\n     * @param arguments a list of 3 or more arguments\n     */\n    public void trace(String format, Object... arguments);\n\n    /**\n     * Log an exception (throwable) at the TRACE level with an\n     * accompanying message.\n     *\n     * @param msg the message accompanying the exception\n     * @param t   the exception (throwable) to log\n     */\n    public void trace(String msg, Throwable t);\n\n    /**\n     * Is the logger instance enabled for the DEBUG level?\n     *\n     * @return True if this Logger is enabled for the DEBUG level,\n     *         false otherwise.\n     */\n    public boolean isDebugEnabled();\n\n    /**\n     * Log a message at the DEBUG level.\n     *\n     * @param msg the message string to be logged\n     */\n    public void debug(String msg);\n\n    /**\n     * Log a message at the DEBUG level according to the specified format\n     * and argument.\n     * <p/>\n     * <p>This form avoids superfluous object creation when the logger\n     * is disabled for the DEBUG level. </p>\n     *\n     * @param format the format string\n     * @param arg    the argument\n     */\n    public void debug(String format, Object arg);\n\n    /**\n     * Log a message at the DEBUG level according to the specified format\n     * and arguments.\n     * <p/>\n     * <p>This form avoids superfluous object creation when the logger\n     * is disabled for the DEBUG level. </p>\n     *\n     * @param format the format string\n     * @param arg1   the first argument\n     * @param arg2   the second argument\n     */\n    public void debug(String format, Object arg1, Object arg2);\n\n    /**\n     * Log a message at the DEBUG level according to the specified format\n     * and arguments.\n     * <p/>\n     * <p>This form avoids superfluous string concatenation when the logger\n     * is disabled for the DEBUG level. However, this variant incurs the hidden\n     * (and relatively small) cost of creating an <code>Object[]</code> before invoking the method,\n     * even if this logger is disabled for DEBUG. The variants taking\n     * {@link #debug(String, Object) one} and {@link #debug(String, Object, Object) two}\n     * arguments exist solely in order to avoid this hidden cost.</p>\n     *\n     * @param format    the format string\n     * @param arguments a list of 3 or more arguments\n     */\n    public void debug(String format, Object... arguments);\n\n    /**\n     * Log an exception (throwable) at the DEBUG level with an\n     * accompanying message.\n     *\n     * @param msg the message accompanying the exception\n     * @param t   the exception (throwable) to log\n     */\n    public void debug(String msg, Throwable t);\n\n    /**\n     * Is the logger instance enabled for the INFO level?\n     *\n     * @return True if this Logger is enabled for the INFO level,\n     *         false otherwise.\n     */\n    public boolean isInfoEnabled();\n\n    /**\n     * Log a message at the INFO level.\n     *\n     * @param msg the message string to be logged\n     */\n    public void info(String msg);\n\n    /**\n     * Log a message at the INFO level according to the specified format\n     * and argument.\n     * <p/>\n     * <p>This form avoids superfluous object creation when the logger\n     * is disabled for the INFO level. </p>\n     *\n     * @param format the format string\n     * @param arg    the argument\n     */\n    public void info(String format, Object arg);\n\n    /**\n     * Log a message at the INFO level according to the specified format\n     * and arguments.\n     * <p/>\n     * <p>This form avoids superfluous object creation when the logger\n     * is disabled for the INFO level. </p>\n     *\n     * @param format the format string\n     * @param arg1   the first argument\n     * @param arg2   the second argument\n     */\n    public void info(String format, Object arg1, Object arg2);\n\n    /**\n     * Log a message at the INFO level according to the specified format\n     * and arguments.\n     * <p/>\n     * <p>This form avoids superfluous string concatenation when the logger\n     * is disabled for the INFO level. However, this variant incurs the hidden\n     * (and relatively small) cost of creating an <code>Object[]</code> before invoking the method,\n     * even if this logger is disabled for INFO. The variants taking\n     * {@link #info(String, Object) one} and {@link #info(String, Object, Object) two}\n     * arguments exist solely in order to avoid this hidden cost.</p>\n     *\n     * @param format    the format string\n     * @param arguments a list of 3 or more arguments\n     */\n    public void info(String format, Object... arguments);\n\n    /**\n     * Log an exception (throwable) at the INFO level with an\n     * accompanying message.\n     *\n     * @param msg the message accompanying the exception\n     * @param t   the exception (throwable) to log\n     */\n    public void info(String msg, Throwable t);\n\n    /**\n     * Is the logger instance enabled for the WARN level?\n     *\n     * @return True if this Logger is enabled for the WARN level,\n     *         false otherwise.\n     */\n    public boolean isWarnEnabled();\n\n    /**\n     * Log a message at the WARN level.\n     *\n     * @param msg the message string to be logged\n     */\n    public void warn(String msg);\n\n    /**\n     * Log a message at the WARN level according to the specified format\n     * and argument.\n     * <p/>\n     * <p>This form avoids superfluous object creation when the logger\n     * is disabled for the WARN level. </p>\n     *\n     * @param format the format string\n     * @param arg    the argument\n     */\n    public void warn(String format, Object arg);\n\n    /**\n     * Log a message at the WARN level according to the specified format\n     * and arguments.\n     * <p/>\n     * <p>This form avoids superfluous string concatenation when the logger\n     * is disabled for the WARN level. However, this variant incurs the hidden\n     * (and relatively small) cost of creating an <code>Object[]</code> before invoking the method,\n     * even if this logger is disabled for WARN. The variants taking\n     * {@link #warn(String, Object) one} and {@link #warn(String, Object, Object) two}\n     * arguments exist solely in order to avoid this hidden cost.</p>\n     *\n     * @param format    the format string\n     * @param arguments a list of 3 or more arguments\n     */\n    public void warn(String format, Object... arguments);\n\n    /**\n     * Log a message at the WARN level according to the specified format\n     * and arguments.\n     * <p/>\n     * <p>This form avoids superfluous object creation when the logger\n     * is disabled for the WARN level. </p>\n     *\n     * @param format the format string\n     * @param arg1   the first argument\n     * @param arg2   the second argument\n     */\n    public void warn(String format, Object arg1, Object arg2);\n\n    /**\n     * Log an exception (throwable) at the WARN level with an\n     * accompanying message.\n     *\n     * @param msg the message accompanying the exception\n     * @param t   the exception (throwable) to log\n     */\n    public void warn(String msg, Throwable t);\n\n    /**\n     * Is the logger instance enabled for the ERROR level?\n     *\n     * @return True if this Logger is enabled for the ERROR level,\n     *         false otherwise.\n     */\n    public boolean isErrorEnabled();\n\n    /**\n     * Log a message at the ERROR level.\n     *\n     * @param msg the message string to be logged\n     */\n    public void error(String msg);\n\n    /**\n     * Log a message at the ERROR level according to the specified format\n     * and argument.\n     * <p/>\n     * <p>This form avoids superfluous object creation when the logger\n     * is disabled for the ERROR level. </p>\n     *\n     * @param format the format string\n     * @param arg    the argument\n     */\n    public void error(String format, Object arg);\n\n    /**\n     * Log a message at the ERROR level according to the specified format\n     * and arguments.\n     * <p/>\n     * <p>This form avoids superfluous object creation when the logger\n     * is disabled for the ERROR level. </p>\n     *\n     * @param format the format string\n     * @param arg1   the first argument\n     * @param arg2   the second argument\n     */\n    public void error(String format, Object arg1, Object arg2);\n\n    /**\n     * Log a message at the ERROR level according to the specified format\n     * and arguments.\n     * <p/>\n     * <p>This form avoids superfluous string concatenation when the logger\n     * is disabled for the ERROR level. However, this variant incurs the hidden\n     * (and relatively small) cost of creating an <code>Object[]</code> before invoking the method,\n     * even if this logger is disabled for ERROR. The variants taking\n     * {@link #error(String, Object) one} and {@link #error(String, Object, Object) two}\n     * arguments exist solely in order to avoid this hidden cost.</p>\n     *\n     * @param format    the format string\n     * @param arguments a list of 3 or more arguments\n     */\n    public void error(String format, Object... arguments);\n\n    /**\n     * Log an exception (throwable) at the ERROR level with an\n     * accompanying message.\n     *\n     * @param msg the message accompanying the exception\n     * @param t   the exception (throwable) to log\n     */\n    public void error(String msg, Throwable t);\n}\n"
  },
  {
    "path": "log4j2/log4j2-api/src/main/java/com/megaease/easeagent/log4j2/LoggerFactory.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.log4j2;\n\nimport com.megaease.easeagent.log4j2.api.AgentLogger;\nimport com.megaease.easeagent.log4j2.api.AgentLoggerFactory;\n\nimport java.util.function.Function;\nimport java.util.logging.Level;\n\npublic class LoggerFactory {\n    public static final java.util.logging.Logger LOGGER = java.util.logging.Logger.getLogger(LoggerFactory.class.getName());\n    protected static final AgentLoggerFactory<AgentLogger> FACTORY;\n\n    static {\n        AgentLoggerFactory<AgentLogger> factory = null;\n        try {\n            factory = AgentLoggerFactory.builder(\n                classLoaderSupplier(),\n                AgentLogger.LOGGER_SUPPLIER,\n                AgentLogger.class\n            ).build();\n        } catch (Exception e) {\n            LOGGER.log(Level.WARNING, String.format(\"build agent logger factory fail: %s<%s>.\", e.getClass().getName(), e.getMessage()));\n        }\n        FACTORY = factory;\n    }\n\n    private static ClassloaderSupplier classLoaderSupplier() {\n        return new ClassloaderSupplier.ClassloaderSupplierImpl();\n    }\n\n    public static <N extends AgentLogger> AgentLoggerFactory<N> newFactory(Function<java.util.logging.Logger, N> loggerSupplier, Class<N> tClass) {\n        if (FACTORY == null) {\n            return null;\n        }\n        return FACTORY.newFactory(loggerSupplier, tClass);\n    }\n\n    public static Logger getLogger(String name) {\n        if (FACTORY == null) {\n            return new NoopLogger(name);\n        }\n        return FACTORY.getLogger(name);\n    }\n\n\n    public static Logger getLogger(Class<?> clazz) {\n        return getLogger(clazz.getName());\n    }\n\n\n    public static class NoopLogger implements Logger {\n        private final String name;\n\n        public NoopLogger(String name) {\n            this.name = name;\n        }\n\n        @Override\n        public String getName() {\n            return name;\n        }\n\n        @Override\n        public boolean isTraceEnabled() {\n            return false;\n        }\n\n        @Override\n        public void trace(String msg) {\n            //ignore\n        }\n\n        @Override\n        public void trace(String format, Object arg) {\n            //ignore\n        }\n\n        @Override\n        public void trace(String format, Object arg1, Object arg2) {\n            //ignore\n        }\n\n        @Override\n        public void trace(String format, Object... arguments) {\n            //ignore\n        }\n\n        @Override\n        public void trace(String msg, Throwable t) {\n            //ignore\n        }\n\n        @Override\n        public boolean isDebugEnabled() {\n            return false;\n        }\n\n        @Override\n        public void debug(String msg) {\n            //ignore\n        }\n\n        @Override\n        public void debug(String format, Object arg) {\n            //ignore\n        }\n\n        @Override\n        public void debug(String format, Object arg1, Object arg2) {\n            //ignore\n        }\n\n        @Override\n        public void debug(String format, Object... arguments) {\n            //ignore\n        }\n\n        @Override\n        public void debug(String msg, Throwable t) {\n            //ignore\n        }\n\n        @Override\n        public boolean isInfoEnabled() {\n            return false;\n        }\n\n        @Override\n        public void info(String msg) {\n            //ignore\n        }\n\n        @Override\n        public void info(String format, Object arg) {\n            //ignore\n        }\n\n        @Override\n        public void info(String format, Object arg1, Object arg2) {\n            //ignore\n        }\n\n        @Override\n        public void info(String format, Object... arguments) {\n            //ignore\n        }\n\n        @Override\n        public void info(String msg, Throwable t) {\n            //ignore\n        }\n\n        @Override\n        public boolean isWarnEnabled() {\n            return false;\n        }\n\n        @Override\n        public void warn(String msg) {\n            //ignore\n        }\n\n        @Override\n        public void warn(String format, Object arg) {\n            //ignore\n        }\n\n        @Override\n        public void warn(String format, Object... arguments) {\n            //ignore\n        }\n\n        @Override\n        public void warn(String format, Object arg1, Object arg2) {\n            //ignore\n        }\n\n        @Override\n        public void warn(String msg, Throwable t) {\n            //ignore\n        }\n\n        @Override\n        public boolean isErrorEnabled() {\n            return false;\n        }\n\n        @Override\n        public void error(String msg) {\n            //ignore\n        }\n\n        @Override\n        public void error(String format, Object arg) {\n            //ignore\n        }\n\n        @Override\n        public void error(String format, Object arg1, Object arg2) {\n            //ignore\n        }\n\n        @Override\n        public void error(String format, Object... arguments) {\n            //ignore\n        }\n\n        @Override\n        public void error(String msg, Throwable t) {\n            //ignore\n        }\n    }\n}\n"
  },
  {
    "path": "log4j2/log4j2-api/src/main/java/com/megaease/easeagent/log4j2/MDC.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.log4j2;\n\nimport com.megaease.easeagent.log4j2.api.Mdc;\n\npublic class MDC {\n    private static final Mdc MDC_I = LoggerFactory.FACTORY.mdc();\n\n    public static void put(String key, String value) {\n        MDC_I.put(key, value);\n    }\n\n    public static void remove(String key) {\n        MDC_I.remove(key);\n    }\n\n    public static String get(String key) {\n        return MDC_I.get(key);\n    }\n}\n"
  },
  {
    "path": "log4j2/log4j2-api/src/main/java/com/megaease/easeagent/log4j2/api/AgentLogger.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.log4j2.api;\n\nimport java.util.function.Function;\nimport java.util.logging.Logger;\n\npublic class AgentLogger implements com.megaease.easeagent.log4j2.Logger {\n    public static final Function<Logger, AgentLogger> LOGGER_SUPPLIER = AgentLogger::new;\n\n    private final Logger logger;\n\n    public AgentLogger(Logger logger) {\n        this.logger = logger;\n    }\n\n    public Logger getLogger() {\n        return logger;\n    }\n\n    @Override\n    public String getName() {\n        return logger.getName();\n    }\n\n    @Override\n    public boolean isTraceEnabled() {\n        return logger.isLoggable(ILevel.TRACE);\n    }\n\n    @Override\n    public void trace(String msg) {\n        logger.log(ILevel.TRACE, msg);\n    }\n\n    @Override\n    public void trace(String format, Object arg) {\n        logger.log(ILevel.TRACE, format, arg);\n    }\n\n    @Override\n    public void trace(String format, Object arg1, Object arg2) {\n        logger.log(ILevel.TRACE, format, new Object[]{arg1, arg2});\n    }\n\n    @Override\n    public void trace(String format, Object... arguments) {\n        logger.log(ILevel.TRACE, format, arguments);\n    }\n\n    @Override\n    public void trace(String msg, Throwable t) {\n        logger.log(ILevel.TRACE, msg, t);\n    }\n\n    @Override\n    public boolean isDebugEnabled() {\n        return logger.isLoggable(ILevel.DEBUG);\n    }\n\n    @Override\n    public void debug(String msg) {\n        logger.log(ILevel.DEBUG, msg);\n    }\n\n    @Override\n    public void debug(String format, Object arg) {\n        logger.log(ILevel.DEBUG, format, arg);\n    }\n\n    @Override\n    public void debug(String format, Object arg1, Object arg2) {\n        logger.log(ILevel.DEBUG, format, new Object[]{arg1, arg2});\n    }\n\n    @Override\n    public void debug(String format, Object... arguments) {\n        logger.log(ILevel.DEBUG, format, arguments);\n    }\n\n    @Override\n    public void debug(String msg, Throwable t) {\n        logger.log(ILevel.DEBUG, msg, t);\n    }\n\n    @Override\n    public boolean isInfoEnabled() {\n        return logger.isLoggable(ILevel.INFO);\n    }\n\n    @Override\n    public void info(String msg) {\n        logger.log(ILevel.INFO, msg);\n    }\n\n    @Override\n    public void info(String format, Object arg) {\n        logger.log(ILevel.INFO, format, arg);\n    }\n\n    @Override\n    public void info(String format, Object arg1, Object arg2) {\n        logger.log(ILevel.INFO, format, new Object[]{arg1, arg2});\n    }\n\n    @Override\n    public void info(String format, Object... arguments) {\n        logger.log(ILevel.INFO, format, arguments);\n    }\n\n    @Override\n    public void info(String msg, Throwable t) {\n        logger.log(ILevel.INFO, msg, t);\n    }\n\n    @Override\n    public boolean isWarnEnabled() {\n        return logger.isLoggable(ILevel.WARN);\n    }\n\n    @Override\n    public void warn(String msg) {\n        logger.log(ILevel.WARN, msg);\n    }\n\n    @Override\n    public void warn(String format, Object arg) {\n        logger.log(ILevel.WARN, format, arg);\n    }\n\n\n    @Override\n    public void warn(String format, Object arg1, Object arg2) {\n        logger.log(ILevel.WARN, format, new Object[]{arg1, arg2});\n    }\n\n    @Override\n    public void warn(String format, Object... arguments) {\n        logger.log(ILevel.WARN, format, arguments);\n    }\n\n\n    @Override\n    public void warn(String msg, Throwable t) {\n        logger.log(ILevel.WARN, msg, t);\n    }\n\n    @Override\n    public boolean isErrorEnabled() {\n        return logger.isLoggable(ILevel.ERROR);\n    }\n\n    @Override\n    public void error(String msg) {\n        logger.log(ILevel.ERROR, msg);\n    }\n\n    @Override\n    public void error(String format, Object arg) {\n        logger.log(ILevel.ERROR, format, arg);\n\n    }\n\n    @Override\n    public void error(String format, Object arg1, Object arg2) {\n        logger.log(ILevel.ERROR, format, new Object[]{arg1, arg2});\n    }\n\n    @Override\n    public void error(String format, Object... arguments) {\n        logger.log(ILevel.ERROR, format, arguments);\n    }\n\n    @Override\n    public void error(String msg, Throwable t) {\n        logger.log(ILevel.ERROR, msg, t);\n    }\n}\n"
  },
  {
    "path": "log4j2/log4j2-api/src/main/java/com/megaease/easeagent/log4j2/api/AgentLoggerFactory.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.log4j2.api;\n\nimport com.megaease.easeagent.log4j2.ClassloaderSupplier;\nimport com.megaease.easeagent.log4j2.exception.Log4j2Exception;\n\nimport javax.annotation.Nonnull;\nimport java.lang.reflect.Constructor;\nimport java.lang.reflect.Field;\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Method;\nimport java.util.Objects;\nimport java.util.function.BiFunction;\nimport java.util.function.Function;\nimport java.util.logging.Logger;\n\npublic class AgentLoggerFactory<T extends AgentLogger> {\n    private final AgentLogger agentLogger;\n    private final ClassLoader classLoader;\n    private final Object factory;\n    private final Method method;\n    private final Function<Logger, T> loggerSupplier;\n    private final Mdc mdc;\n\n    private AgentLoggerFactory(@Nonnull ClassLoader classLoader,\n                               @Nonnull Object factory, @Nonnull Method method,\n                               @Nonnull Function<Logger, T> loggerSupplier, @Nonnull Mdc mdc) {\n        this.classLoader = classLoader;\n        this.factory = factory;\n        this.method = method;\n        this.loggerSupplier = loggerSupplier;\n        this.mdc = mdc;\n        this.agentLogger = this.getLogger(AgentLoggerFactory.class.getName());\n    }\n\n    public static <T extends AgentLogger> Builder<T> builder(ClassloaderSupplier classLoaderSupplier,\n                                                             Function<Logger, T> loggerSupplier,\n                                                             Class<T> tClass) {\n        ClassLoader classLoader = Objects.requireNonNull(classLoaderSupplier.get(), \"classLoader must not be null.\");\n        return new Builder<>(classLoader, loggerSupplier, tClass);\n    }\n\n    public <N extends AgentLogger> AgentLoggerFactory<N> newFactory(Function<Logger, N> loggerSupplier, Class<N> tClass) {\n        try {\n            return new Builder<N>(classLoader, loggerSupplier, tClass).build();\n        } catch (ClassNotFoundException | NoSuchMethodException\n            | NoSuchFieldException | InstantiationException | InvocationTargetException | IllegalAccessException e) {\n            agentLogger.error(\"new factory fail: {}\", e);\n        }\n        return null;\n    }\n\n    public T getLogger(String name) {\n        ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();\n        try {\n            Thread.currentThread().setContextClassLoader(classLoader);\n            Object o = method.invoke(factory, name);\n            Thread.currentThread().setContextClassLoader(oldClassLoader);\n            java.util.logging.Logger logger = (java.util.logging.Logger) o;\n            // 还原为之前的 ClassLoader\n            return loggerSupplier.apply(logger);\n        } catch (IllegalAccessException | InvocationTargetException e) {\n            throw new Log4j2Exception(e);\n        } finally {\n            Thread.currentThread().setContextClassLoader(oldClassLoader);\n        }\n    }\n\n    public Mdc mdc() {\n        return mdc;\n    }\n\n    public static class Builder<T extends AgentLogger> {\n        private final ClassLoader classLoader;\n        private final Function<java.util.logging.Logger, T> loggerSupplier;\n        private final Class<T> tClass;\n\n        public Builder(@Nonnull ClassLoader classLoader, @Nonnull Function<Logger, T> loggerSupplier, @Nonnull Class<T> tClass) {\n            this.classLoader = classLoader;\n            this.loggerSupplier = loggerSupplier;\n            this.tClass = tClass;\n        }\n\n        public AgentLoggerFactory<T> build() throws ClassNotFoundException, NoSuchMethodException,\n            IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {\n\n            ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();\n            try {\n                Thread.currentThread().setContextClassLoader(classLoader);\n                Class<?> clazz = classLoader.loadClass(\"com.megaease.easeagent.log4j2.impl.LoggerProxyFactory\");\n                Class<?> parameterTypes = classLoader.loadClass(String.class.getName());\n                Constructor<?> constructor = clazz.getDeclaredConstructor(String.class);\n                Object factory = constructor.newInstance(tClass.getName());\n                Method method = clazz.getDeclaredMethod(\"getAgentLogger\", parameterTypes);\n                return new AgentLoggerFactory<>(classLoader, factory, method, loggerSupplier, buildMdc());\n            } finally {\n                Thread.currentThread().setContextClassLoader(oldClassLoader);\n            }\n        }\n\n\n        @SuppressWarnings(\"unchecked\")\n        private Mdc buildMdc() throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {\n            Class<?> mdcClazz = classLoader.loadClass(\"com.megaease.easeagent.log4j2.impl.MdcProxy\");\n            Field putField = mdcClazz.getDeclaredField(\"PUT_INSTANCE\");\n            BiFunction<String, String, Void> put = (BiFunction<String, String, Void>) putField.get(null);\n\n            Field removeField = mdcClazz.getDeclaredField(\"REMOVE_INSTANCE\");\n            Function<String, Void> remove = (Function<String, Void>) removeField.get(null);\n\n\n            Field getField = mdcClazz.getDeclaredField(\"GET_INSTANCE\");\n            Function<String, String> get = (Function<String, String>) getField.get(null);\n            return new Mdc(put, remove, get);\n        }\n    }\n}\n"
  },
  {
    "path": "log4j2/log4j2-api/src/main/java/com/megaease/easeagent/log4j2/api/ILevel.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.log4j2.api;\n\npublic class ILevel extends java.util.logging.Level {\n    public static final int OFF_VALUE = 1;\n    public static final int FATAL_VALUE = 2;\n    public static final int ERROR_VALUE = 3;\n    public static final int WARN_VALUE = 4;\n    public static final int INFO_VALUE = 5;\n    public static final int DEBUG_VALUE = 6;\n    public static final int TRACE_VALUE = 7;\n    public static final int ALL_VALUE = 8;\n\n\n    public static final ILevel OFF = new ILevel(\"OFF\", OFF_VALUE);\n    public static final ILevel FATAL = new ILevel(\"FATAL\", FATAL_VALUE);\n    public static final ILevel ERROR = new ILevel(\"ERROR\", ERROR_VALUE);\n    public static final ILevel WARN = new ILevel(\"WARN\", WARN_VALUE);\n    public static final ILevel INFO = new ILevel(\"INFO\", INFO_VALUE);\n    public static final ILevel DEBUG = new ILevel(\"DEBUG\", DEBUG_VALUE);\n    public static final ILevel TRACE = new ILevel(\"TRACE\", TRACE_VALUE);\n    public static final ILevel ALL = new ILevel(\"ALL\", ALL_VALUE);\n\n\n    protected ILevel(String name, int value) {\n        super(name, value);\n    }\n}\n"
  },
  {
    "path": "log4j2/log4j2-api/src/main/java/com/megaease/easeagent/log4j2/api/Mdc.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.log4j2.api;\n\nimport javax.annotation.Nonnull;\nimport java.util.function.BiFunction;\nimport java.util.function.Function;\n\npublic class Mdc {\n    private final BiFunction<String, String, Void> putFunction;\n    private final Function<String, Void> removeFunction;\n    private final Function<String, String> getFunction;\n\n    public Mdc(@Nonnull BiFunction<String, String, Void> putFunction, @Nonnull Function<String, Void> removeFunction, @Nonnull Function<String, String> getFunction) {\n        this.putFunction = putFunction;\n        this.removeFunction = removeFunction;\n        this.getFunction = getFunction;\n    }\n\n    public void put(String key, String value) {\n        putFunction.apply(key, value);\n    }\n\n    public void remove(String key) {\n        removeFunction.apply(key);\n    }\n\n    public String get(String key) {\n        return getFunction.apply(key);\n    }\n}\n"
  },
  {
    "path": "log4j2/log4j2-api/src/main/java/com/megaease/easeagent/log4j2/exception/Log4j2Exception.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.log4j2.exception;\n\npublic class Log4j2Exception extends RuntimeException {\n    public Log4j2Exception(Throwable cause) {\n        super(cause);\n    }\n}\n"
  },
  {
    "path": "log4j2/log4j2-impl/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright (c) 2021, MegaEase\n  ~ All rights reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~   http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>log4j2</artifactId>\n        <groupId>com.megaease.easeagent</groupId>\n        <version>2.3.0</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>log4j2-impl</artifactId>\n\n\n    <dependencies>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>log4j2-api</artifactId>\n            <version>${project.version}</version>\n            <!--\n            <exclusions>\n                <exclusion>\n                    <groupId>*</groupId>\n                    <artifactId>*</artifactId>\n                </exclusion>\n            </exclusions>\n            -->\n        </dependency>\n        <dependency>\n            <groupId>org.apache.logging.log4j</groupId>\n            <artifactId>log4j-slf4j-impl</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.apache.logging.log4j</groupId>\n            <artifactId>log4j-core</artifactId>\n        </dependency>\n        \n        <dependency>\n            <groupId>com.lmax</groupId>\n            <artifactId>disruptor</artifactId>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-compiler-plugin</artifactId>\n                <version>3.8.1</version>\n                <configuration>\n                    <source>${version.java}</source>\n                    <target>${version.java}</target>\n                    <encoding>${encoding.file}</encoding>\n                </configuration>\n                <executions>\n                    <execution>\n                        <id>log4j-plugin-processor</id>\n                        <goals>\n                            <goal>compile</goal>\n                        </goals>\n                        <phase>process-classes</phase>\n                        <configuration>\n                            <proc>only</proc>\n                            <annotationProcessors>\n                                <annotationProcessor>\n                                    org.apache.logging.log4j.core.config.plugins.processor.PluginProcessor\n                                </annotationProcessor>\n                            </annotationProcessors>\n                        </configuration>\n                    </execution>\n                </executions>\n            </plugin>\n\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-shade-plugin</artifactId>\n                <version>${version.maven-shade-plugin}</version>\n                <executions>\n                    <execution>\n                        <phase>package</phase>\n                        <goals>\n                            <goal>shade</goal>\n                        </goals>\n                        <configuration>\n                            <shadedArtifactAttached>true</shadedArtifactAttached><!-- optional -->\n                            <shadedClassifierName>agent-lib</shadedClassifierName>\n\n                            <relocations>\n                                <relocation>\n                                    <pattern>org.apache.kafka</pattern>\n                                    <shadedPattern>com.megaease.easeagent.org.apache.kafka</shadedPattern>\n                                </relocation>\n                            </relocations>\n                        </configuration>\n                    </execution>\n                </executions>\n            </plugin>\n        </plugins>\n\n\n    </build>\n</project>\n"
  },
  {
    "path": "log4j2/log4j2-impl/src/main/java/com/megaease/easeagent/log4j2/impl/AgentLoggerProxy.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.megaease.easeagent.log4j2.impl;\n\nimport org.apache.logging.log4j.Level;\nimport org.apache.logging.log4j.LogManager;\nimport org.apache.logging.log4j.message.Message;\nimport org.apache.logging.log4j.message.ParameterizedMessage;\nimport org.apache.logging.log4j.message.SimpleMessage;\nimport org.apache.logging.log4j.spi.ExtendedLogger;\nimport org.apache.logging.log4j.util.LoaderUtil;\nimport org.apache.logging.slf4j.EventDataConverter;\nimport org.apache.logging.slf4j.Log4jMarker;\nimport org.apache.logging.slf4j.Log4jMarkerFactory;\nimport org.slf4j.Marker;\nimport org.slf4j.MarkerFactory;\nimport org.slf4j.impl.StaticMarkerBinder;\nimport org.slf4j.spi.LocationAwareLogger;\n\nimport java.io.IOException;\nimport java.io.ObjectInputStream;\nimport java.io.ObjectOutputStream;\nimport java.io.Serializable;\n\n/**\n * SLF4J logger implementation that uses Log4j.\n */\npublic class AgentLoggerProxy implements LocationAwareLogger, Serializable {\n\n    private static final long serialVersionUID = 4906175597262951747L;\n    private static final Marker EVENT_MARKER = MarkerFactory.getMarker(\"EVENT\");\n    private static final EventDataConverter CONVERTER = createConverter();\n\n    private final boolean eventLogger;\n    private transient ExtendedLogger logger;\n    private final String name;\n    private final String loggerFqcn;\n\n    public AgentLoggerProxy(final ExtendedLogger logger, final String name, String loggerFqcn) {\n        this.logger = logger;\n        this.eventLogger = \"EventLogger\".equals(name);\n        this.name = name;\n        this.loggerFqcn = loggerFqcn;\n    }\n\n    @Override\n    public void trace(final String format) {\n        logger.logIfEnabled(loggerFqcn, Level.TRACE, null, format);\n    }\n\n    @Override\n    public void trace(final String format, final Object o) {\n        logger.logIfEnabled(loggerFqcn, Level.TRACE, null, format, o);\n    }\n\n    @Override\n    public void trace(final String format, final Object arg1, final Object arg2) {\n        logger.logIfEnabled(loggerFqcn, Level.TRACE, null, format, arg1, arg2);\n    }\n\n    @Override\n    public void trace(final String format, final Object... args) {\n        logger.logIfEnabled(loggerFqcn, Level.TRACE, null, format, args);\n    }\n\n    @Override\n    public void trace(final String format, final Throwable t) {\n        logger.logIfEnabled(loggerFqcn, Level.TRACE, null, format, t);\n    }\n\n    @Override\n    public boolean isTraceEnabled() {\n        return logger.isEnabled(Level.TRACE, null, null);\n    }\n\n    @Override\n    public boolean isTraceEnabled(final Marker marker) {\n        return logger.isEnabled(Level.TRACE, getMarker(marker), null);\n    }\n\n    @Override\n    public void trace(final Marker marker, final String s) {\n        logger.logIfEnabled(loggerFqcn, Level.TRACE, getMarker(marker), s);\n    }\n\n    @Override\n    public void trace(final Marker marker, final String s, final Object o) {\n        logger.logIfEnabled(loggerFqcn, Level.TRACE, getMarker(marker), s, o);\n    }\n\n    @Override\n    public void trace(final Marker marker, final String s, final Object o, final Object o1) {\n        logger.logIfEnabled(loggerFqcn, Level.TRACE, getMarker(marker), s, o, o1);\n    }\n\n    @Override\n    public void trace(final Marker marker, final String s, final Object... objects) {\n        logger.logIfEnabled(loggerFqcn, Level.TRACE, getMarker(marker), s, objects);\n    }\n\n    @Override\n    public void trace(final Marker marker, final String s, final Throwable throwable) {\n        logger.logIfEnabled(loggerFqcn, Level.TRACE, getMarker(marker), s, throwable);\n    }\n\n    @Override\n    public void debug(final String format) {\n        logger.logIfEnabled(loggerFqcn, Level.DEBUG, null, format);\n    }\n\n    @Override\n    public void debug(final String format, final Object o) {\n        logger.logIfEnabled(loggerFqcn, Level.DEBUG, null, format, o);\n    }\n\n    @Override\n    public void debug(final String format, final Object arg1, final Object arg2) {\n        logger.logIfEnabled(loggerFqcn, Level.DEBUG, null, format, arg1, arg2);\n    }\n\n    @Override\n    public void debug(final String format, final Object... args) {\n        logger.logIfEnabled(loggerFqcn, Level.DEBUG, null, format, args);\n    }\n\n    @Override\n    public void debug(final String format, final Throwable t) {\n        logger.logIfEnabled(loggerFqcn, Level.DEBUG, null, format, t);\n    }\n\n    @Override\n    public boolean isDebugEnabled() {\n        return logger.isEnabled(Level.DEBUG, null, null);\n    }\n\n    @Override\n    public boolean isDebugEnabled(final Marker marker) {\n        return logger.isEnabled(Level.DEBUG, getMarker(marker), null);\n    }\n\n    @Override\n    public void debug(final Marker marker, final String s) {\n        logger.logIfEnabled(loggerFqcn, Level.DEBUG, getMarker(marker), s);\n    }\n\n    @Override\n    public void debug(final Marker marker, final String s, final Object o) {\n        logger.logIfEnabled(loggerFqcn, Level.DEBUG, getMarker(marker), s, o);\n    }\n\n    @Override\n    public void debug(final Marker marker, final String s, final Object o, final Object o1) {\n        logger.logIfEnabled(loggerFqcn, Level.DEBUG, getMarker(marker), s, o, o1);\n    }\n\n    @Override\n    public void debug(final Marker marker, final String s, final Object... objects) {\n        logger.logIfEnabled(loggerFqcn, Level.DEBUG, getMarker(marker), s, objects);\n    }\n\n    @Override\n    public void debug(final Marker marker, final String s, final Throwable throwable) {\n        logger.logIfEnabled(loggerFqcn, Level.DEBUG, getMarker(marker), s, throwable);\n    }\n\n    @Override\n    public void info(final String format) {\n        logger.logIfEnabled(loggerFqcn, Level.INFO, null, format);\n    }\n\n    @Override\n    public void info(final String format, final Object o) {\n        logger.logIfEnabled(loggerFqcn, Level.INFO, null, format, o);\n    }\n\n    @Override\n    public void info(final String format, final Object arg1, final Object arg2) {\n        logger.logIfEnabled(loggerFqcn, Level.INFO, null, format, arg1, arg2);\n    }\n\n    @Override\n    public void info(final String format, final Object... args) {\n        logger.logIfEnabled(loggerFqcn, Level.INFO, null, format, args);\n    }\n\n    @Override\n    public void info(final String format, final Throwable t) {\n        logger.logIfEnabled(loggerFqcn, Level.INFO, null, format, t);\n    }\n\n    @Override\n    public boolean isInfoEnabled() {\n        return logger.isEnabled(Level.INFO, null, null);\n    }\n\n    @Override\n    public boolean isInfoEnabled(final Marker marker) {\n        return logger.isEnabled(Level.INFO, getMarker(marker), null);\n    }\n\n    @Override\n    public void info(final Marker marker, final String s) {\n        logger.logIfEnabled(loggerFqcn, Level.INFO, getMarker(marker), s);\n    }\n\n    @Override\n    public void info(final Marker marker, final String s, final Object o) {\n        logger.logIfEnabled(loggerFqcn, Level.INFO, getMarker(marker), s, o);\n    }\n\n    @Override\n    public void info(final Marker marker, final String s, final Object o, final Object o1) {\n        logger.logIfEnabled(loggerFqcn, Level.INFO, getMarker(marker), s, o, o1);\n    }\n\n    @Override\n    public void info(final Marker marker, final String s, final Object... objects) {\n        logger.logIfEnabled(loggerFqcn, Level.INFO, getMarker(marker), s, objects);\n    }\n\n    @Override\n    public void info(final Marker marker, final String s, final Throwable throwable) {\n        logger.logIfEnabled(loggerFqcn, Level.INFO, getMarker(marker), s, throwable);\n    }\n\n    @Override\n    public void warn(final String format) {\n        logger.logIfEnabled(loggerFqcn, Level.WARN, null, format);\n    }\n\n    @Override\n    public void warn(final String format, final Object o) {\n        logger.logIfEnabled(loggerFqcn, Level.WARN, null, format, o);\n    }\n\n    @Override\n    public void warn(final String format, final Object arg1, final Object arg2) {\n        logger.logIfEnabled(loggerFqcn, Level.WARN, null, format, arg1, arg2);\n    }\n\n    @Override\n    public void warn(final String format, final Object... args) {\n        logger.logIfEnabled(loggerFqcn, Level.WARN, null, format, args);\n    }\n\n    @Override\n    public void warn(final String format, final Throwable t) {\n        logger.logIfEnabled(loggerFqcn, Level.WARN, null, format, t);\n    }\n\n    @Override\n    public boolean isWarnEnabled() {\n        return logger.isEnabled(Level.WARN, null, null);\n    }\n\n    @Override\n    public boolean isWarnEnabled(final Marker marker) {\n        return logger.isEnabled(Level.WARN, getMarker(marker), null);\n    }\n\n    @Override\n    public void warn(final Marker marker, final String s) {\n        logger.logIfEnabled(loggerFqcn, Level.WARN, getMarker(marker), s);\n    }\n\n    @Override\n    public void warn(final Marker marker, final String s, final Object o) {\n        logger.logIfEnabled(loggerFqcn, Level.WARN, getMarker(marker), s, o);\n    }\n\n    @Override\n    public void warn(final Marker marker, final String s, final Object o, final Object o1) {\n        logger.logIfEnabled(loggerFqcn, Level.WARN, getMarker(marker), s, o, o1);\n    }\n\n    @Override\n    public void warn(final Marker marker, final String s, final Object... objects) {\n        logger.logIfEnabled(loggerFqcn, Level.WARN, getMarker(marker), s, objects);\n    }\n\n    @Override\n    public void warn(final Marker marker, final String s, final Throwable throwable) {\n        logger.logIfEnabled(loggerFqcn, Level.WARN, getMarker(marker), s, throwable);\n    }\n\n    @Override\n    public void error(final String format) {\n        logger.logIfEnabled(loggerFqcn, Level.ERROR, null, format);\n    }\n\n    @Override\n    public void error(final String format, final Object o) {\n        logger.logIfEnabled(loggerFqcn, Level.ERROR, null, format, o);\n    }\n\n    @Override\n    public void error(final String format, final Object arg1, final Object arg2) {\n        logger.logIfEnabled(loggerFqcn, Level.ERROR, null, format, arg1, arg2);\n    }\n\n    @Override\n    public void error(final String format, final Object... args) {\n        logger.logIfEnabled(loggerFqcn, Level.ERROR, null, format, args);\n    }\n\n    @Override\n    public void error(final String format, final Throwable t) {\n        logger.logIfEnabled(loggerFqcn, Level.ERROR, null, format, t);\n    }\n\n    @Override\n    public boolean isErrorEnabled() {\n        return logger.isEnabled(Level.ERROR, null, null);\n    }\n\n    @Override\n    public boolean isErrorEnabled(final Marker marker) {\n        return logger.isEnabled(Level.ERROR, getMarker(marker), null);\n    }\n\n    @Override\n    public void error(final Marker marker, final String s) {\n        logger.logIfEnabled(loggerFqcn, Level.ERROR, getMarker(marker), s);\n    }\n\n    @Override\n    public void error(final Marker marker, final String s, final Object o) {\n        logger.logIfEnabled(loggerFqcn, Level.ERROR, getMarker(marker), s, o);\n    }\n\n    @Override\n    public void error(final Marker marker, final String s, final Object o, final Object o1) {\n        logger.logIfEnabled(loggerFqcn, Level.ERROR, getMarker(marker), s, o, o1);\n    }\n\n    @Override\n    public void error(final Marker marker, final String s, final Object... objects) {\n        logger.logIfEnabled(loggerFqcn, Level.ERROR, getMarker(marker), s, objects);\n    }\n\n    @Override\n    public void error(final Marker marker, final String s, final Throwable throwable) {\n        logger.logIfEnabled(loggerFqcn, Level.ERROR, getMarker(marker), s, throwable);\n    }\n\n    @Override\n    public void log(final Marker marker, final String fqcn, final int level, final String message, final Object[] params, Throwable throwable) {\n        final Level log4jLevel = getLevel(level);\n        final org.apache.logging.log4j.Marker log4jMarker = getMarker(marker);\n\n        if (!logger.isEnabled(log4jLevel, log4jMarker, message, params)) {\n            return;\n        }\n        final Message msg;\n        if (CONVERTER != null && eventLogger && marker != null && marker.contains(EVENT_MARKER)) {\n            msg = CONVERTER.convertEvent(message, params, throwable);\n        } else if (params == null) {\n            msg = new SimpleMessage(message);\n        } else {\n            msg = new ParameterizedMessage(message, params, throwable);\n            if (throwable != null) {\n                throwable = msg.getThrowable();\n            }\n        }\n        logger.logMessage(fqcn, log4jLevel, log4jMarker, msg, throwable);\n    }\n\n    private static org.apache.logging.log4j.Marker getMarker(final Marker marker) {\n        if (marker == null) {\n            return null;\n        } else if (marker instanceof Log4jMarker) {\n            return ((Log4jMarker) marker).getLog4jMarker();\n        } else {\n            final Log4jMarkerFactory factory = (Log4jMarkerFactory) StaticMarkerBinder.SINGLETON.getMarkerFactory();\n            return ((Log4jMarker) factory.getMarker(marker)).getLog4jMarker();\n        }\n    }\n\n    @Override\n    public String getName() {\n        return name;\n    }\n\n    /**\n     * Always treat de-serialization as a full-blown constructor, by validating the final state of\n     * the de-serialized object.\n     */\n    private void readObject(final ObjectInputStream aInputStream) throws ClassNotFoundException, IOException {\n        // always perform the default de-serialization first\n        aInputStream.defaultReadObject();\n        logger = LogManager.getContext().getLogger(name);\n    }\n\n    /**\n     * This is the default implementation of writeObject. Customise if necessary.\n     */\n    private void writeObject(final ObjectOutputStream aOutputStream) throws IOException {\n        // perform the default serialization for all non-transient, non-static fields\n        aOutputStream.defaultWriteObject();\n    }\n\n    private static EventDataConverter createConverter() {\n        try {\n            LoaderUtil.loadClass(\"org.slf4j.ext.EventData\");\n            return new EventDataConverter();\n        } catch (final ClassNotFoundException cnfe) {\n            return null;\n        }\n    }\n\n    private static Level getLevel(final int i) {\n        switch (i) {\n            case TRACE_INT:\n                return Level.TRACE;\n            case DEBUG_INT:\n                return Level.DEBUG;\n            case INFO_INT:\n                return Level.INFO;\n            case WARN_INT:\n                return Level.WARN;\n            case ERROR_INT:\n                return Level.ERROR;\n            default:\n                return Level.ERROR;\n        }\n    }\n}\n"
  },
  {
    "path": "log4j2/log4j2-impl/src/main/java/com/megaease/easeagent/log4j2/impl/LoggerProxyFactory.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.log4j2.impl;\n\nimport com.megaease.easeagent.log4j2.api.AgentLogger;\nimport org.apache.logging.log4j.LogManager;\nimport org.apache.logging.log4j.LoggingException;\nimport org.apache.logging.log4j.spi.AbstractLoggerAdapter;\nimport org.apache.logging.log4j.spi.LoggerContext;\nimport org.apache.logging.log4j.util.StackLocatorUtil;\nimport org.slf4j.Logger;\n\n/**\n * Log4j implementation of SLF4J ILoggerFactory interface.\n */\npublic class LoggerProxyFactory extends AbstractLoggerAdapter<AgentLoggerProxy> {\n    private static final LoggerProxyFactory LOGGER_FACTORY = new LoggerProxyFactory();\n\n    private static final String FQCN = LoggerProxyFactory.class.getName();\n    private static final String PACKAGE = \"org.slf4j\";\n    private static final String TO_SLF4J_CONTEXT = \"org.apache.logging.slf4j.SLF4JLoggerContext\";\n\n    private final String loggerFqcn;\n\n    public LoggerProxyFactory() {\n        this(AgentLogger.class.getName());\n    }\n\n    public LoggerProxyFactory(String loggerFqcn) {\n        this.loggerFqcn = loggerFqcn;\n    }\n\n    public static Slf4jLogger getAgentLogger(String name) {\n        return new Slf4jLogger(LOGGER_FACTORY.getLogger(name));\n    }\n\n    @Override\n    protected AgentLoggerProxy newLogger(final String name, final LoggerContext context) {\n        final String key = Logger.ROOT_LOGGER_NAME.equals(name) ? LogManager.ROOT_LOGGER_NAME : name;\n        return new AgentLoggerProxy(validateContext(context).getLogger(key), name, loggerFqcn);\n    }\n\n    @Override\n    protected LoggerContext getContext() {\n        final Class<?> anchor = StackLocatorUtil.getCallerClass(FQCN, PACKAGE);\n        return anchor == null ? LogManager.getContext() : getContext(StackLocatorUtil.getCallerClass(anchor));\n    }\n\n    private LoggerContext validateContext(final LoggerContext context) {\n        if (TO_SLF4J_CONTEXT.equals(context.getClass().getName())) {\n            throw new LoggingException(\"log4j-slf4j-impl cannot be present with log4j-to-slf4j\");\n        }\n        return context;\n    }\n}\n"
  },
  {
    "path": "log4j2/log4j2-impl/src/main/java/com/megaease/easeagent/log4j2/impl/MdcProxy.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.log4j2.impl;\n\nimport org.slf4j.MDC;\n\nimport java.util.function.BiFunction;\nimport java.util.function.Function;\n\npublic class MdcProxy {\n    public static final MdcPut PUT_INSTANCE = new MdcPut();\n    public static final MdcRemove REMOVE_INSTANCE = new MdcRemove();\n    public static final MdcGet GET_INSTANCE = new MdcGet();\n\n    private static class MdcPut implements BiFunction<String, String, Void> {\n\n        @Override\n        public Void apply(String key, String value) {\n            MDC.put(key, value);\n            return null;\n        }\n    }\n\n    private static class MdcRemove implements Function<String, Void> {\n\n        @Override\n        public Void apply(String key) {\n            MDC.remove(key);\n            return null;\n        }\n    }\n\n    private static class MdcGet implements Function<String, String> {\n\n        @Override\n        public String apply(String key) {\n            return MDC.get(key);\n        }\n    }\n}\n"
  },
  {
    "path": "log4j2/log4j2-impl/src/main/java/com/megaease/easeagent/log4j2/impl/Slf4jLogger.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.log4j2.impl;\n\n\nimport org.slf4j.Logger;\n\nimport java.util.logging.Level;\n\nimport static com.megaease.easeagent.log4j2.api.ILevel.*;\n\npublic class Slf4jLogger extends java.util.logging.Logger {\n    private final Logger logger;\n\n    public Slf4jLogger(Logger logger) {\n        super(\"\", null);\n        this.logger = logger;\n    }\n\n    @Override\n    public String getName() {\n        return logger.getName();\n    }\n\n    @Override\n    public void info(String msg) {\n        logger.info(msg);\n    }\n\n    @Override\n    public boolean isLoggable(Level level) {\n        switch (level.intValue()) {\n            case ERROR_VALUE:\n                return logger.isErrorEnabled();\n            case WARN_VALUE:\n                return logger.isWarnEnabled();\n            case INFO_VALUE:\n                return logger.isInfoEnabled();\n            case DEBUG_VALUE:\n                return logger.isDebugEnabled();\n            case TRACE_VALUE:\n                return logger.isTraceEnabled();\n            default:\n        }\n        return false;\n    }\n\n    @Override\n    public void log(Level level, String msg) {\n        switch (level.intValue()) {\n            case ERROR_VALUE:\n                logger.error(msg);\n                break;\n            case WARN_VALUE:\n                logger.warn(msg);\n                break;\n            case INFO_VALUE:\n                logger.info(msg);\n                break;\n            case DEBUG_VALUE:\n                logger.debug(msg);\n                break;\n            case TRACE_VALUE:\n                logger.trace(msg);\n                break;\n            default:\n        }\n    }\n\n    @Override\n    public void log(Level level, String msg, Object param1) {\n        switch (level.intValue()) {\n            case ERROR_VALUE:\n                logger.error(msg, param1);\n                break;\n            case WARN_VALUE:\n                logger.warn(msg, param1);\n                break;\n            case INFO_VALUE:\n                logger.info(msg, param1);\n                break;\n            case DEBUG_VALUE:\n                logger.debug(msg, param1);\n                break;\n            case TRACE_VALUE:\n                logger.trace(msg, param1);\n                break;\n            default:\n        }\n    }\n\n    @Override\n    public void log(Level level, String msg, Object[] params) {\n\n        switch (level.intValue()) {\n            case ERROR_VALUE:\n                logger.error(msg, params);\n                break;\n            case WARN_VALUE:\n                logger.warn(msg, params);\n                break;\n            case INFO_VALUE:\n                logger.info(msg, params);\n                break;\n            case DEBUG_VALUE:\n                logger.debug(msg, params);\n                break;\n            case TRACE_VALUE:\n                logger.trace(msg, params);\n                break;\n            default:\n        }\n    }\n\n    @Override\n    public void log(Level level, String msg, Throwable thrown) {\n        switch (level.intValue()) {\n            case ERROR_VALUE:\n                logger.error(msg, thrown);\n                break;\n            case WARN_VALUE:\n                logger.warn(msg, thrown);\n                break;\n            case INFO_VALUE:\n                logger.info(msg, thrown);\n                break;\n            case DEBUG_VALUE:\n                logger.debug(msg, thrown);\n                break;\n            case TRACE_VALUE:\n                logger.trace(msg, thrown);\n                break;\n            default:\n        }\n    }\n}\n"
  },
  {
    "path": "log4j2/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  Copyright (c) 2017, MegaEase\n  All rights reserved.\n\n  Licensed under the Apache License, Version 2.0 (the \"License\");\n  you may not use this file except in compliance with the License.\n  You may obtain a copy of the License at\n\n      http://www.apache.org/licenses/LICENSE-2.0\n\n  Unless required by applicable law or agreed to in writing, software\n  distributed under the License is distributed on an \"AS IS\" BASIS,\n  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  See the License for the specific language governing permissions and\n  limitations under the License.\n-->\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>easeagent</artifactId>\n        <groupId>com.megaease.easeagent</groupId>\n        <version>2.3.0</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>log4j2</artifactId>\n    <packaging>pom</packaging>\n    <modules>\n        <module>log4j2-api</module>\n        <module>log4j2-impl</module>\n    </modules>\n\n</project>\n"
  },
  {
    "path": "metrics/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  Copyright (c) 2017, MegaEase\n  All rights reserved.\n\n  Licensed under the Apache License, Version 2.0 (the \"License\");\n  you may not use this file except in compliance with the License.\n  You may obtain a copy of the License at\n\n      http://www.apache.org/licenses/LICENSE-2.0\n\n  Unless required by applicable law or agreed to in writing, software\n  distributed under the License is distributed on an \"AS IS\" BASIS,\n  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  See the License for the specific language governing permissions and\n  limitations under the License.\n-->\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>easeagent</artifactId>\n        <groupId>com.megaease.easeagent</groupId>\n        <version>2.3.0</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>metrics</artifactId>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>config</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n        <!--<dependency>-->\n        <!--<groupId>com.megaease.easeagent</groupId>-->\n        <!--<artifactId>common</artifactId>-->\n        <!--<version>${project.version}</version>-->\n        <!--</dependency>-->\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>report</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>httpserver</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>io.dropwizard.metrics</groupId>\n            <artifactId>metrics-core</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.apache.kafka</groupId>\n            <artifactId>kafka-clients</artifactId>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>javax.servlet</groupId>\n            <artifactId>javax.servlet-api</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>io.prometheus</groupId>\n            <artifactId>simpleclient_dropwizard</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>io.prometheus</groupId>\n            <artifactId>simpleclient_common</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>com.github.dreamhead</groupId>\n            <artifactId>moco-core</artifactId>\n            <scope>test</scope>\n        </dependency>\n\n        <dependency>\n            <groupId>com.squareup.okhttp3</groupId>\n            <artifactId>okhttp</artifactId>\n            <scope>test</scope>\n        </dependency>\n\n        <dependency>\n            <groupId>org.awaitility</groupId>\n            <artifactId>awaitility</artifactId>\n            <version>3.0.0</version>\n            <scope>test</scope>\n        </dependency>\n\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>config-mock</artifactId>\n            <version>${project.version}</version>\n            <scope>test</scope>\n        </dependency>\n\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>context-mock</artifactId>\n            <version>${project.version}</version>\n            <scope>test</scope>\n        </dependency>\n    </dependencies>\n</project>\n"
  },
  {
    "path": "metrics/src/main/java/com/megaease/easeagent/metrics/AgentScheduledReporter.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.metrics;\n\nimport com.codahale.metrics.*;\nimport com.megaease.easeagent.config.Configs;\nimport com.megaease.easeagent.config.report.ReportConfigAdapter;\nimport com.megaease.easeagent.metrics.converter.Converter;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.report.EncodedData;\nimport com.megaease.easeagent.plugin.report.Encoder;\nimport com.megaease.easeagent.plugin.utils.NoNull;\nimport com.megaease.easeagent.report.encoder.metric.MetricJsonEncoder;\nimport com.megaease.easeagent.report.plugin.ReporterRegistry;\nimport lombok.SneakyThrows;\n\nimport java.util.*;\nimport java.util.concurrent.ScheduledExecutorService;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.Consumer;\nimport java.util.function.Supplier;\n\nimport static com.megaease.easeagent.config.report.ReportConfigConst.METRIC_ENCODER;\n\n@SuppressWarnings(\"unused\")\npublic class AgentScheduledReporter extends ScheduledReporter {\n    private Converter converter;\n    private final Consumer<EncodedData> dataConsumer;\n    private final Supplier<Boolean> enabled;\n    private final Encoder<Map<String, Object>> encoder;\n\n    @SuppressWarnings(\"all\")\n    private AgentScheduledReporter(MetricRegistry registry,\n                                   Consumer<EncodedData> dataConsumer,\n                                   TimeUnit rateUnit,\n                                   TimeUnit durationUnit,\n                                   MetricFilter filter,\n                                   ScheduledExecutorService executor,\n                                   boolean shutdownExecutorOnStop,\n                                   Set<MetricAttribute> disabledMetricAttributes,\n                                   Supplier<Boolean> enabled,\n                                   Converter converter) {\n        super(registry, \"logger-reporter\", filter, rateUnit, durationUnit, executor, shutdownExecutorOnStop,\n            disabledMetricAttributes);\n        this.converter = converter;\n        // encoder\n        this.dataConsumer = dataConsumer;\n        this.enabled = enabled;\n        Map<String, String> reporterCfg = ReportConfigAdapter.extractReporterConfig(EaseAgent.getConfig());\n        String name = NoNull.of(reporterCfg.get(METRIC_ENCODER), MetricJsonEncoder.ENCODER_NAME);\n        this.encoder = ReporterRegistry.getEncoder(name);\n        this.encoder.init(new Configs(reporterCfg));\n    }\n\n    /**\n     * Returns a new {@link Slf4jReporter.Builder} for {@link Slf4jReporter}.\n     *\n     * @param registry the registry to report\n     * @return a {@link Slf4jReporter.Builder} instance for a {@link Slf4jReporter}\n     */\n    public static Builder forRegistry(MetricRegistry registry) {\n        return new Builder(registry);\n    }\n\n\n    @SneakyThrows\n    @Override\n    public void report(SortedMap<String, Gauge> gauges,\n                       SortedMap<String, Counter> counters,\n                       SortedMap<String, Histogram> histograms,\n                       SortedMap<String, Meter> meters,\n                       SortedMap<String, com.codahale.metrics.Timer> timers) {\n        Boolean e = this.enabled.get();\n        if (e == null || !e) {\n            return;\n        }\n\n        List<Map<String, Object>> outputs = converter.convertMap(gauges, counters, histograms, meters, timers);\n\n        for (Map<String, Object> output : outputs) {\n            this.dataConsumer.accept(this.encoder.encode(output));\n        }\n    }\n\n\n    @Override\n    protected String getRateUnit() {\n        return \"events/\" + super.getRateUnit();\n    }\n\n\n    public Converter getConverter() {\n        return this.converter;\n    }\n\n    /**\n     * Invoke it in the Metric constructor only.\n     *\n     * @param converter convert meter to List<Map<String, Object>> to serialize to json\n     */\n    public void setConverter(Converter converter) {\n        this.converter = converter;\n    }\n\n    /**\n     * A builder for {@link Slf4jReporter} instances. Defaults to logging to {@code metrics}, not\n     * using a marker, converting rates to events/second, converting durations to milliseconds, and\n     * not filtering metrics.\n     */\n    public static class Builder {\n        private final MetricRegistry registry;\n        private TimeUnit rateUnit;\n        private TimeUnit durationUnit;\n        private MetricFilter filter;\n        private ScheduledExecutorService executor;\n        private boolean shutdownExecutorOnStop;\n        private Set<MetricAttribute> disabledMetricAttributes;\n        private Converter converter;\n        private Supplier<Boolean> enabled;\n        private Consumer<EncodedData> dataConsumer;\n\n        private Builder(MetricRegistry registry) {\n            this.registry = registry;\n            this.rateUnit = TimeUnit.SECONDS;\n            this.durationUnit = TimeUnit.MILLISECONDS;\n            this.filter = MetricFilter.ALL;\n            this.executor = null;\n            this.shutdownExecutorOnStop = true;\n            this.disabledMetricAttributes = Collections.emptySet();\n        }\n\n        /**\n         * Specifies whether or not, the executor (used for reporting) will be stopped with same time with reporter.\n         * Default value is true.\n         * Setting this parameter to false, has the sense in combining with providing external managed executor via {@link #scheduleOn(ScheduledExecutorService)}.\n         *\n         * @param shutdownExecutorOnStop if true, then executor will be stopped in same time with this reporter\n         * @return {@code this}\n         */\n        public Builder shutdownExecutorOnStop(boolean shutdownExecutorOnStop) {\n            this.shutdownExecutorOnStop = shutdownExecutorOnStop;\n            return this;\n        }\n\n        /**\n         * Specifies the executor to use while scheduling reporting of metrics.\n         * Default value is null.\n         * Null value leads to executor will be auto created on start.\n         *\n         * @param executor the executor to use while scheduling reporting of metrics.\n         * @return {@code this}\n         */\n        public Builder scheduleOn(ScheduledExecutorService executor) {\n            this.executor = executor;\n            return this;\n        }\n\n        public Builder converter(Converter converter) {\n            this.converter = converter;\n            return this;\n        }\n\n        /**\n         * Log metrics to the given consumer.\n         *\n         * @return {@code this}\n         */\n        public Builder outputTo(Consumer<EncodedData> dataConsumer) {\n            this.dataConsumer = dataConsumer;\n            return this;\n        }\n\n        public Builder enabled(Supplier<Boolean> enabled) {\n            this.enabled = enabled;\n            return this;\n        }\n\n        /**\n         * Convert rates to the given time unit.\n         *\n         * @param rateUnit a unit of time\n         * @return {@code this}\n         */\n        public Builder convertRatesTo(TimeUnit rateUnit) {\n            this.rateUnit = rateUnit;\n            return this;\n        }\n\n        /**\n         * Convert durations to the given time unit.\n         *\n         * @param durationUnit a unit of time\n         * @return {@code this}\n         */\n        public Builder convertDurationsTo(TimeUnit durationUnit) {\n            this.durationUnit = durationUnit;\n            return this;\n        }\n\n        /**\n         * Only report metrics which match the given filter.\n         *\n         * @param filter a {@link MetricFilter}\n         * @return {@code this}\n         */\n        public Builder filter(MetricFilter filter) {\n            this.filter = filter;\n            return this;\n        }\n\n        /**\n         * Don't report the passed metric attributes for all metrics (e.g. \"p999\", \"stdDev\" or \"m15\").\n         * See {@link MetricAttribute}.\n         *\n         * @param disabledMetricAttributes a set of {@link MetricAttribute}\n         * @return {@code this}\n         */\n        public Builder disabledMetricAttributes(Set<MetricAttribute> disabledMetricAttributes) {\n            this.disabledMetricAttributes = disabledMetricAttributes;\n            return this;\n        }\n\n        /**\n         * Builds a {@link Slf4jReporter} with the given properties.\n         *\n         * @return a {@link Slf4jReporter}\n         */\n        public AgentScheduledReporter build() {\n            return new AgentScheduledReporter(registry,\n                dataConsumer,\n                rateUnit, durationUnit,\n                filter, executor, shutdownExecutorOnStop,\n                disabledMetricAttributes, enabled, converter);\n        }\n\n    }\n}\n"
  },
  {
    "path": "metrics/src/main/java/com/megaease/easeagent/metrics/AutoRefreshReporter.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.metrics;\n\nimport com.codahale.metrics.MetricRegistry;\nimport com.megaease.easeagent.metrics.config.MetricsConfig;\nimport com.megaease.easeagent.metrics.converter.Converter;\nimport com.megaease.easeagent.plugin.report.EncodedData;\n\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.Consumer;\n\npublic class AutoRefreshReporter implements Runnable {\n    private final MetricsConfig config;\n    private final Converter converter;\n    private final Consumer<EncodedData> consumer;\n    private final MetricRegistry metricRegistry;\n    private AgentScheduledReporter reporter;\n\n    public AutoRefreshReporter(MetricRegistry metricRegistry,\n                               MetricsConfig config,\n                               Converter converter,\n                               Consumer<EncodedData> consumer) {\n        this.metricRegistry = metricRegistry;\n        this.config = config;\n        this.consumer = consumer;\n        this.converter = converter;\n        config.setIntervalChangeCallback(this);\n    }\n\n    @Override\n    public synchronized void run() {\n        // config changed\n        if (reporter != null) {\n            reporter.close();\n            reporter = null;\n        }\n        reporter = AgentScheduledReporter.forRegistry(metricRegistry)\n            .outputTo(consumer)\n            .enabled(config::isEnabled)\n            .convertRatesTo(TimeUnit.SECONDS)\n            .convertDurationsTo(TimeUnit.MILLISECONDS)\n            .build();\n        reporter.setConverter(converter);\n        reporter.start(config.getInterval(), config.getIntervalUnit());\n    }\n\n    public AgentScheduledReporter getReporter() {\n        return reporter;\n    }\n}\n"
  },
  {
    "path": "metrics/src/main/java/com/megaease/easeagent/metrics/MetricBeanProviderImpl.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.metrics;\n\nimport com.megaease.easeagent.config.ConfigAware;\nimport com.megaease.easeagent.httpserver.nano.AgentHttpHandler;\nimport com.megaease.easeagent.httpserver.nano.AgentHttpHandlerProvider;\nimport com.megaease.easeagent.plugin.api.config.Config;\nimport com.megaease.easeagent.plugin.api.metric.MetricProvider;\nimport com.megaease.easeagent.plugin.api.metric.MetricRegistrySupplier;\nimport com.megaease.easeagent.plugin.bean.BeanProvider;\nimport com.megaease.easeagent.plugin.report.AgentReport;\nimport com.megaease.easeagent.report.AgentReportAware;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class MetricBeanProviderImpl implements BeanProvider, AgentHttpHandlerProvider, ConfigAware,\n    MetricProvider, AgentReportAware {\n    private final MetricProviderImpl metricProvider = new MetricProviderImpl();\n\n    @Override\n    public List<AgentHttpHandler> getAgentHttpHandlers() {\n        List<AgentHttpHandler> list = new ArrayList<>();\n        list.add(new PrometheusAgentHttpHandler());\n        return list;\n\n    }\n\n    @Override\n    public void setConfig(Config config) {\n        this.metricProvider.setConfig(config);\n    }\n\n    @Override\n    public MetricRegistrySupplier metricSupplier() {\n        return metricProvider.metricSupplier();\n    }\n\n    @Override\n    public void setAgentReport(AgentReport report) {\n        this.metricProvider.setAgentReport(report);\n    }\n\n    public MetricProviderImpl getMetricProvider() {\n        return metricProvider;\n    }\n}\n"
  },
  {
    "path": "metrics/src/main/java/com/megaease/easeagent/metrics/MetricProviderImpl.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.metrics;\n\nimport com.codahale.metrics.MetricRegistry;\nimport com.megaease.easeagent.config.ConfigAware;\nimport com.megaease.easeagent.metrics.config.MetricsConfig;\nimport com.megaease.easeagent.metrics.config.PluginMetricsConfig;\nimport com.megaease.easeagent.metrics.converter.ConverterAdapter;\nimport com.megaease.easeagent.metrics.converter.KeyType;\nimport com.megaease.easeagent.metrics.converter.MetricsAdditionalAttributes;\nimport com.megaease.easeagent.metrics.impl.MetricRegistryImpl;\nimport com.megaease.easeagent.plugin.api.Reporter;\nimport com.megaease.easeagent.plugin.api.config.Config;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.metric.MetricProvider;\nimport com.megaease.easeagent.plugin.api.metric.MetricRegistrySupplier;\nimport com.megaease.easeagent.plugin.api.metric.name.MetricType;\nimport com.megaease.easeagent.plugin.api.metric.name.NameFactory;\nimport com.megaease.easeagent.plugin.api.metric.name.Tags;\nimport com.megaease.easeagent.plugin.report.AgentReport;\nimport com.megaease.easeagent.report.AgentReportAware;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.function.Supplier;\n\npublic class MetricProviderImpl implements AgentReportAware, ConfigAware, MetricProvider {\n    @SuppressWarnings(\"unused\")\n    private Config config;\n    private final List<com.megaease.easeagent.plugin.api.metric.MetricRegistry> registries = new ArrayList<>();\n    private final List<AutoRefreshReporter> reporters = new ArrayList<>();\n    private AgentReport agentReport;\n    private Supplier<Map<String, Object>> additionalAttributes;\n\n\n    @Override\n    public void setConfig(Config config) {\n        this.config = config;\n        this.additionalAttributes = new MetricsAdditionalAttributes(config);\n    }\n\n    @Override\n    public void setAgentReport(AgentReport report) {\n        this.agentReport = report;\n    }\n\n\n    @Override\n    public MetricRegistrySupplier metricSupplier() {\n        return new ApplicationMetricRegistrySupplier();\n    }\n\n    public void registerMetricRegistry(com.megaease.easeagent.plugin.api.metric.MetricRegistry metricRegistry) {\n        synchronized (registries) {\n            registries.add(metricRegistry);\n        }\n    }\n\n    public void registerReporter(AutoRefreshReporter refreshReporter) {\n        synchronized (reporters) {\n            reporters.add(refreshReporter);\n        }\n    }\n\n    public static List<KeyType> keyTypes(NameFactory nameFactory) {\n        List<KeyType> keyTypes = new ArrayList<>();\n        for (MetricType metricType : nameFactory.metricTypes()) {\n            switch (metricType) {\n                case TimerType:\n                    keyTypes.add(KeyType.Timer);\n                    break;\n                case GaugeType:\n                    keyTypes.add(KeyType.Gauge);\n                    break;\n                case MeterType:\n                    keyTypes.add(KeyType.Meter);\n                    break;\n                case CounterType:\n                    keyTypes.add(KeyType.Counter);\n                    break;\n                case HistogramType:\n                    keyTypes.add(KeyType.Histogram);\n                    break;\n                default:\n                    break;\n            }\n        }\n        return keyTypes;\n    }\n\n    public class ApplicationMetricRegistrySupplier implements MetricRegistrySupplier {\n\n        @Override\n        public com.megaease.easeagent.plugin.api.metric.MetricRegistry newMetricRegistry(\n            IPluginConfig config,\n            NameFactory nameFactory, Tags tags) {\n            MetricsConfig metricsConfig = new PluginMetricsConfig(config);\n            List<KeyType> keyTypes = keyTypes(nameFactory);\n            ConverterAdapter converterAdapter = new ConverterAdapter(nameFactory, keyTypes,\n                MetricProviderImpl.this.additionalAttributes, tags);\n            Reporter reporter = agentReport.metricReporter().reporter(config);\n            MetricRegistry metricRegistry = MetricRegistryService.DEFAULT.createMetricRegistry(converterAdapter, additionalAttributes, tags);\n            AutoRefreshReporter autoRefreshReporter = new AutoRefreshReporter(metricRegistry, metricsConfig,\n                converterAdapter,\n                reporter::report);\n            autoRefreshReporter.run();\n            registerReporter(autoRefreshReporter);\n\n            com.megaease.easeagent.plugin.api.metric.MetricRegistry result = MetricRegistryImpl.build(metricRegistry);\n            registerMetricRegistry(result);\n            return result;\n        }\n\n        @Override\n        public Reporter reporter(IPluginConfig config) {\n            return agentReport.metricReporter().reporter(config);\n        }\n    }\n\n    public List<com.megaease.easeagent.plugin.api.metric.MetricRegistry> getRegistryList() {\n        return registries;\n    }\n\n    public List<AutoRefreshReporter> getReporterList() {\n        return reporters;\n    }\n}\n"
  },
  {
    "path": "metrics/src/main/java/com/megaease/easeagent/metrics/MetricRegistryService.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.metrics;\n\nimport com.codahale.metrics.MetricRegistry;\nimport com.megaease.easeagent.log4j2.Logger;\nimport com.megaease.easeagent.log4j2.LoggerFactory;\nimport com.megaease.easeagent.metrics.converter.AbstractConverter;\nimport com.megaease.easeagent.metrics.converter.EaseAgentPrometheusExports;\nimport com.megaease.easeagent.plugin.api.metric.name.MetricName;\nimport com.megaease.easeagent.plugin.api.metric.name.Tags;\nimport io.prometheus.client.Collector;\nimport io.prometheus.client.dropwizard.samplebuilder.DefaultSampleBuilder;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.function.Supplier;\n\npublic class MetricRegistryService {\n    private static final Logger LOGGER = LoggerFactory.getLogger(MetricRegistryService.class);\n    public static final String METRIC_TYPE_LABEL_NAME = \"MetricType\";\n    public static final String METRIC_SUB_TYPE_LABEL_NAME = \"MetricSubType\";\n    public static final MetricRegistryService DEFAULT = new MetricRegistryService();\n\n    private static final List<MetricRegistry> REGISTRY_LIST = new ArrayList<>();\n\n    public MetricRegistry createMetricRegistry(AbstractConverter abstractConverter, Supplier<Map<String, Object>> additionalAttributes, Tags tags) {\n        MetricRegistry registry = new MetricRegistry();\n        REGISTRY_LIST.add(registry);\n        EaseAgentSampleBuilder easeAgentSampleBuilder = new EaseAgentSampleBuilder(additionalAttributes, tags);\n        EaseAgentPrometheusExports easeAgentPrometheusExports = new EaseAgentPrometheusExports(registry, abstractConverter, easeAgentSampleBuilder);\n        easeAgentPrometheusExports.register();\n        return registry;\n    }\n\n    static class EaseAgentSampleBuilder extends DefaultSampleBuilder {\n        private final Supplier<Map<String, Object>> additionalAttributes;\n        private final Tags tags;\n\n        EaseAgentSampleBuilder(Supplier<Map<String, Object>> additionalAttributes, Tags tags) {\n            this.additionalAttributes = additionalAttributes;\n            this.tags = tags;\n        }\n\n        private void additionalAttributes(List<String> additionalLabelNames, List<String> additionalLabelValues) {\n            if (additionalAttributes == null) {\n                return;\n            }\n            Map<String, Object> labels = additionalAttributes.get();\n            if (labels == null || labels.isEmpty()) {\n                return;\n            }\n            for (Map.Entry<String, Object> entry : labels.entrySet()) {\n                additionalLabelNames.add(entry.getKey());\n                additionalLabelValues.add(entry.getValue().toString());\n            }\n        }\n\n        private void tags(List<String> additionalLabelNames, List<String> additionalLabelValues) {\n            if (tags == null) {\n                return;\n            }\n            Map<String, String> other = tags.getTags();\n            if (other == null || other.isEmpty()) {\n                return;\n            }\n            for (Map.Entry<String, String> entry : other.entrySet()) {\n                additionalLabelNames.add(entry.getKey());\n                additionalLabelValues.add(entry.getValue());\n            }\n        }\n\n        @Override\n        public Collector.MetricFamilySamples.Sample createSample(String dropwizardName, String nameSuffix, List<String> additionalLabelNames, List<String> additionalLabelValues, double value) {\n            List<String> newAdditionalLabelNames = new ArrayList<>(additionalLabelNames);\n            List<String> newAdditionalLabelValues = new ArrayList<>(additionalLabelValues);\n            additionalAttributes(newAdditionalLabelNames, newAdditionalLabelValues);\n            tags(newAdditionalLabelNames, newAdditionalLabelValues);\n            return super.createSample(rebuildName(dropwizardName, newAdditionalLabelNames, newAdditionalLabelValues), nameSuffix, newAdditionalLabelNames, newAdditionalLabelValues, value);\n        }\n\n        private String rebuildName(String name, List<String> additionalLabelNames, List<String> additionalLabelValues) {\n            try {\n                MetricName metricName = MetricName.metricNameFor(name);\n                StringBuilder stringBuilder = new StringBuilder();\n                additionalLabelNames.add(METRIC_TYPE_LABEL_NAME);\n                additionalLabelNames.add(METRIC_SUB_TYPE_LABEL_NAME);\n                additionalLabelValues.add(metricName.getMetricType().name());\n                additionalLabelValues.add(metricName.getMetricSubType().name());\n                additionalLabelNames.add(tags.getKeyFieldName());\n                additionalLabelValues.add(metricName.getKey());\n                stringBuilder.append(tags.getCategory());\n                stringBuilder.append(\".\");\n                stringBuilder.append(tags.getType());\n                return stringBuilder.toString();\n            } catch (Exception e) {\n                LOGGER.error(\"rebuild metric name[{}] fail.{}\", name, e);\n                return name;\n            }\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "metrics/src/main/java/com/megaease/easeagent/metrics/PrometheusAgentHttpHandler.java",
    "content": "/*\n *   Copyright (c) 2017, MegaEase\n *   All rights reserved.\n *\n *   Licensed under the Apache License, Version 2.0 (the \"License\");\n *   you may not use this file except in compliance with the License.\n *   You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n *   Unless required by applicable law or agreed to in writing, software\n *   distributed under the License is distributed on an \"AS IS\" BASIS,\n *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *   See the License for the specific language governing permissions and\n *   limitations under the License.\n */\n\npackage com.megaease.easeagent.metrics;\n\nimport com.megaease.easeagent.httpserver.nano.AgentHttpHandler;\nimport com.megaease.easeagent.httpserver.nano.AgentHttpServer;\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.IHTTPSession;\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.response.Response;\nimport com.megaease.easeagent.httpserver.nanohttpd.protocols.http.response.Status;\nimport com.megaease.easeagent.httpserver.nanohttpd.router.RouterNanoHTTPD;\nimport com.megaease.easeagent.log4j2.Logger;\nimport com.megaease.easeagent.log4j2.LoggerFactory;\nimport io.prometheus.client.Collector;\nimport io.prometheus.client.CollectorRegistry;\nimport io.prometheus.client.exporter.common.TextFormat;\n\nimport java.io.BufferedWriter;\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.io.Writer;\nimport java.util.Collections;\nimport java.util.Enumeration;\nimport java.util.Map;\n\npublic class PrometheusAgentHttpHandler extends AgentHttpHandler {\n    private static final Logger LOGGER = LoggerFactory.getLogger(PrometheusAgentHttpHandler.class);\n\n    @Override\n    public String getPath() {\n        return \"/prometheus/metrics\";\n    }\n\n    @Override\n    public Response process(RouterNanoHTTPD.UriResource uriResource, Map<String, String> urlParams, IHTTPSession session) {\n        Map<String, String> headers = session.getHeaders();\n        String contentType = TextFormat.chooseContentType(headers.get(\"Accept\"));\n\n        Enumeration<Collector.MetricFamilySamples> samples = CollectorRegistry.defaultRegistry\n            .filteredMetricFamilySamples(Collections.emptySet());\n\n        StringWriter stringWriter = new StringWriter();\n        try (Writer writer = new BufferedWriter(stringWriter)) {\n            TextFormat.writeFormat(contentType, writer, samples);\n            writer.flush();\n        } catch (IOException e) {\n            LOGGER.warn(\"write data error. {}\", e.getMessage());\n        }\n        String data = stringWriter.toString();\n        return Response.newFixedLengthResponse(Status.OK, AgentHttpServer.JSON_TYPE, data);\n    }\n}\n"
  },
  {
    "path": "metrics/src/main/java/com/megaease/easeagent/metrics/config/MetricsCollectorConfig.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.metrics.config;\n\nimport com.megaease.easeagent.config.ConfigUtils;\nimport com.megaease.easeagent.plugin.api.config.Config;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\n\nimport java.util.concurrent.TimeUnit;\n\nimport static com.megaease.easeagent.plugin.api.config.ConfigConst.Observability;\nimport static com.megaease.easeagent.plugin.api.config.ConfigConst.join;\n\npublic class MetricsCollectorConfig implements MetricsConfig {\n    private volatile boolean globalEnabled;\n    private volatile boolean enabled;\n    private volatile int interval;\n    private Runnable callback;\n\n    public MetricsCollectorConfig(Config config, String type) {\n        ConfigUtils.bindProp(ConfigConst.Observability.METRICS_ENABLED, config, Config::getBoolean, v -> this.globalEnabled = v);\n        ConfigUtils.bindProp(join(Observability.METRICS, type, Observability.KEY_COMM_ENABLED), config, Config::getBoolean, v -> this.enabled = v);\n        ConfigUtils.bindProp(join(Observability.METRICS, type, Observability.KEY_COMM_INTERVAL), config, Config::getInt, v -> {\n            this.interval = v;\n            if (callback != null) {\n                callback.run();\n            }\n        });\n    }\n\n    @Override\n    public boolean isEnabled() {\n        return globalEnabled && enabled;\n    }\n\n    @Override\n    public TimeUnit getIntervalUnit() {\n        return TimeUnit.SECONDS;\n    }\n\n    @Override\n    public int getInterval() {\n        return interval;\n    }\n\n    @Override\n    public void setIntervalChangeCallback(Runnable runnable) {\n        this.callback = runnable;\n    }\n}\n"
  },
  {
    "path": "metrics/src/main/java/com/megaease/easeagent/metrics/config/MetricsConfig.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.metrics.config;\n\nimport java.util.concurrent.TimeUnit;\n\npublic interface MetricsConfig {\n    boolean isEnabled();\n\n    int getInterval();\n\n    TimeUnit getIntervalUnit();\n\n    void setIntervalChangeCallback(Runnable runnable);\n}\n"
  },
  {
    "path": "metrics/src/main/java/com/megaease/easeagent/metrics/config/PluginMetricsConfig.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.metrics.config;\n\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.config.PluginConfigChangeListener;\nimport com.megaease.easeagent.plugin.utils.NoNull;\n\nimport java.util.concurrent.TimeUnit;\n\nimport static com.megaease.easeagent.plugin.api.config.ConfigConst.Observability.KEY_COMM_INTERVAL;\nimport static com.megaease.easeagent.plugin.api.config.ConfigConst.Observability.KEY_COMM_INTERVAL_UNIT;\nimport static com.megaease.easeagent.plugin.api.config.Const.METRIC_DEFAULT_INTERVAL;\nimport static com.megaease.easeagent.plugin.api.config.Const.METRIC_DEFAULT_INTERVAL_UNIT;\n\npublic class PluginMetricsConfig implements MetricsConfig {\n    private volatile boolean enabled;\n    private volatile int interval;\n    private volatile TimeUnit intervalUnit;\n    private Runnable callback;\n\n    public PluginMetricsConfig(IPluginConfig config) {\n        set(config);\n        config.addChangeListener(new PluginConfigChange());\n    }\n\n    @Override\n    public boolean isEnabled() {\n        return enabled;\n    }\n\n    @Override\n    public int getInterval() {\n        return interval;\n    }\n\n    @Override\n    public TimeUnit getIntervalUnit() {\n        return intervalUnit;\n    }\n\n    @Override\n    public void setIntervalChangeCallback(Runnable runnable) {\n        this.callback = runnable;\n    }\n\n    private void set(IPluginConfig config) {\n        this.enabled = config.enabled();\n        this.interval = NoNull.of(config.getInt(KEY_COMM_INTERVAL), METRIC_DEFAULT_INTERVAL);\n        String timeUnit = NoNull.of(config.getString(KEY_COMM_INTERVAL_UNIT), METRIC_DEFAULT_INTERVAL_UNIT);\n        try {\n            this.intervalUnit = TimeUnit.valueOf(timeUnit);\n        } catch (Exception e) {\n            this.intervalUnit = TimeUnit.SECONDS;\n        }\n\n    }\n\n    class PluginConfigChange implements PluginConfigChangeListener {\n\n        @Override\n        public void onChange(IPluginConfig oldConfig, IPluginConfig newConfig) {\n            int oldInterval = PluginMetricsConfig.this.interval;\n            set(newConfig);\n            Runnable runnable = callback;\n            if (oldInterval != PluginMetricsConfig.this.interval && runnable != null) {\n                runnable.run();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "metrics/src/main/java/com/megaease/easeagent/metrics/converter/AbstractConverter.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.metrics.converter;\n\nimport com.codahale.metrics.*;\nimport com.codahale.metrics.Timer;\nimport com.megaease.easeagent.log4j2.Logger;\nimport com.megaease.easeagent.log4j2.LoggerFactory;\nimport com.megaease.easeagent.plugin.api.metric.name.MetricSubType;\nimport com.megaease.easeagent.plugin.api.metric.name.Tags;\nimport lombok.SneakyThrows;\n\nimport java.util.*;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.Supplier;\n\npublic abstract class AbstractConverter implements Converter {\n    private static final Logger LOGGER = LoggerFactory.getLogger(AbstractConverter.class);\n\n    @SuppressWarnings(\"unused\")\n    private final String rateUnit;\n    @SuppressWarnings(\"unused\")\n    private final String durationUnit;\n    final Long durationFactor;\n    final Long rateFactor;\n    private final Tags tags;\n    private final Supplier<Map<String, Object>> additionalAttributes;\n\n    AbstractConverter(String category, String type, String keyFieldName, Supplier<Map<String, Object>> additionalAttributes) {\n        this(additionalAttributes, new Tags(category, type, keyFieldName));\n    }\n\n    AbstractConverter(Supplier<Map<String, Object>> additionalAttributes, Tags tags) {\n        this.rateFactor = TimeUnit.SECONDS.toSeconds(1);\n        this.rateUnit = calculateRateUnit();\n        this.durationFactor = TimeUnit.MILLISECONDS.toNanos(1);\n        this.durationUnit = TimeUnit.MILLISECONDS.toString().toLowerCase(Locale.US);\n        this.additionalAttributes = additionalAttributes;\n        this.tags = tags;\n    }\n\n    private String calculateRateUnit() {\n        final String s = TimeUnit.SECONDS.toString().toLowerCase(Locale.US);\n        return s.substring(0, s.length() - 1);\n    }\n\n    @SneakyThrows\n    @SuppressWarnings(\"rawtypes\")\n    public List<Map<String, Object>> convertMap(SortedMap<String, Gauge> gauges,\n                                                SortedMap<String, Counter> counters,\n                                                SortedMap<String, Histogram> histograms,\n                                                SortedMap<String, Meter> meters,\n                                                SortedMap<String, Timer> timers) {\n\n\n        List<String> keys = keysFromMetrics(gauges, counters, histograms, meters, timers);\n        final List<Map<String, Object>> result = new ArrayList<>();\n        for (String k : keys) {\n            try {\n                Map<String, Object> output = buildMap();\n                writeKey(output, k);\n                writeTag(output);\n                writeGauges(k, null, gauges, output);\n                writeCounters(k, null, counters, output);\n                writeHistograms(k, null, histograms, output);\n                writeMeters(k, null, meters, output);\n                writeTimers(k, null, timers, output);\n                result.add(output);\n            } catch (IgnoreOutputException exception) {\n                LOGGER.trace(\"convert key of \" + k + \" error: \" + exception.getMessage());\n            }\n        }\n        return result;\n    }\n\n    private Map<String, Object> buildMap() {\n        Map<String, Object> map = new HashMap<>();\n        map.put(\"timestamp\", System.currentTimeMillis());\n        map.putAll(additionalAttributes.get());\n        return map;\n    }\n\n\n    private void writeTag(Map<String, Object> output) {\n        output.put(Tags.CATEGORY, tags.getCategory());\n        output.put(Tags.TYPE, tags.getType());\n        output.putAll(tags.getTags());\n    }\n\n    private void writeKey(Map<String, Object> output, String key) {\n        output.put(tags.getKeyFieldName(), key);\n    }\n\n    @SuppressWarnings(\"rawtypes\")\n    protected abstract List<String> keysFromMetrics(\n        SortedMap<String, Gauge> gauges,\n        SortedMap<String, Counter> counters,\n        SortedMap<String, Histogram> histograms,\n        SortedMap<String, Meter> meters,\n        SortedMap<String, Timer> timers);\n\n\n    @SuppressWarnings(\"rawtypes\")\n    protected abstract void writeGauges(String key, MetricSubType metricSubType, SortedMap<String, Gauge> gauges, Map<String, Object> output);\n\n    protected abstract void writeCounters(String key, MetricSubType metricSubType, SortedMap<String, Counter> counters, Map<String, Object> output);\n\n    protected abstract void writeHistograms(String key, MetricSubType metricSubType, SortedMap<String, Histogram> histograms, Map<String, Object> output);\n\n    protected abstract void writeMeters(String key, MetricSubType metricSubType, SortedMap<String, Meter> meters, Map<String, Object> output);\n\n    protected abstract void writeTimers(String key, MetricSubType metricSubType, SortedMap<String, Timer> timers, Map<String, Object> output);\n}\n"
  },
  {
    "path": "metrics/src/main/java/com/megaease/easeagent/metrics/converter/Converter.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.metrics.converter;\n\nimport com.codahale.metrics.*;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.SortedMap;\n\n/**\n * Converter is dedicated to converting metrics object to a\n * serializable <b>HashMap</b> according to * metric scheme\n * definition\n */\npublic interface Converter {\n    @SuppressWarnings(\"rawtypes\")\n    List<Map<String, Object>> convertMap(SortedMap<String, Gauge> gauges,\n                                         SortedMap<String, Counter> counters,\n                                         SortedMap<String, Histogram> histograms,\n                                         SortedMap<String, Meter> meters,\n                                         SortedMap<String, Timer> timers);\n}\n"
  },
  {
    "path": "metrics/src/main/java/com/megaease/easeagent/metrics/converter/ConverterAdapter.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.metrics.converter;\n\nimport com.codahale.metrics.*;\nimport com.codahale.metrics.Timer;\nimport com.megaease.easeagent.metrics.impl.CounterImpl;\nimport com.megaease.easeagent.metrics.impl.MeterImpl;\nimport com.megaease.easeagent.metrics.impl.SnapshotImpl;\nimport com.megaease.easeagent.metrics.impl.TimerImpl;\nimport com.megaease.easeagent.plugin.api.metric.name.*;\nimport com.megaease.easeagent.plugin.tools.metrics.GaugeMetricModel;\n\nimport java.math.BigDecimal;\nimport java.util.*;\nimport java.util.function.Consumer;\nimport java.util.function.Supplier;\n\npublic class ConverterAdapter extends AbstractConverter {\n\n    private final List<KeyType> keyTypes;\n\n    private final NameFactory nameFactory;\n\n    public ConverterAdapter(String category, String type, NameFactory metricNameFactory, KeyType keyType,\n                            Supplier<Map<String, Object>> attributes, String keyFieldName) {\n        super(category, type, keyFieldName, attributes);\n        this.keyTypes = Collections.singletonList(keyType);\n        this.nameFactory = metricNameFactory;\n    }\n\n    public ConverterAdapter(NameFactory metricNameFactory, List<KeyType> keyTypes,\n                            Supplier<Map<String, Object>> attributes, Tags tags) {\n        super(attributes, tags);\n        this.keyTypes = Collections.unmodifiableList(keyTypes);\n        this.nameFactory = metricNameFactory;\n    }\n\n    public ConverterAdapter(String category, String type, NameFactory metricNameFactory, KeyType keyType,\n                            Supplier<Map<String, Object>> attributes) {\n        this(category, type, metricNameFactory, keyType, attributes, \"resource\");\n    }\n\n\n    @Override\n    @SuppressWarnings(\"rawtypes\")\n    protected List<String> keysFromMetrics(SortedMap<String, Gauge> gauges,\n                                           SortedMap<String, Counter> counters,\n                                           SortedMap<String, Histogram> histograms,\n                                           SortedMap<String, Meter> meters,\n                                           SortedMap<String, Timer> timers) {\n        Set<String> results = new HashSet<>();\n        for (KeyType keyType : this.keyTypes) {\n            if (keyType != null) {\n                switch (keyType) {\n                    case Timer:\n                        keys(timers.keySet(), results);\n                        break;\n                    case Histogram:\n                        keys(histograms.keySet(), results);\n                        break;\n                    case Gauge:\n                        keys(gauges.keySet(), results);\n                        break;\n                    case Counter:\n                        keys(counters.keySet(), results);\n                        break;\n                    case Meter:\n                        keys(meters.keySet(), results);\n                        break;\n                    default:\n                        //ignore\n                }\n            }\n        }\n\n        return new ArrayList<>(results);\n    }\n\n    private void keys(Set<String> origins, Set<String> results) {\n        origins.forEach(s -> results.add(MetricName.metricNameFor(s).getKey()));\n    }\n\n    private double convertDuration(Long duration) {\n        return (double) duration / durationFactor;\n    }\n\n    private double convertDuration(Double duration) {\n        return duration / durationFactor;\n    }\n\n    private double convertRate(double rate) {\n        return rate * rateFactor;\n    }\n\n    private double convertRate(Long rate) {\n        return rate == null ? 0 : rate * rateFactor;\n    }\n\n    private void appendRate(Map<String, Object> output, String key, Object value, int scale) {\n        if (value instanceof Long) {\n            output.put(key, convertRate((Long) value));\n        } else if (value instanceof Double) {\n            output.put(key, toDouble(convertRate((Double) value), scale));\n        }\n    }\n\n    private void appendDuration(Map<String, Object> output, String key, Object value, int scale) {\n        if (value instanceof Long) {\n            output.put(key, convertDuration((Long) value));\n        } else if (value instanceof Double) {\n            output.put(key, toDouble(convertDuration((Double) value), scale));\n        }\n    }\n\n    private double toDouble(Double i, int scale) {\n        return BigDecimal.valueOf(i).setScale(scale, BigDecimal.ROUND_HALF_DOWN).doubleValue();\n    }\n\n\n    @Override\n    @SuppressWarnings(\"rawtypes\")\n    protected void writeGauges(String key, MetricSubType metricSubType, SortedMap<String, Gauge> gauges, Map<String, Object> output) {\n        Map<MetricSubType, MetricName> map = nameFactory.gaugeNames(key);\n        consumerMetric(map, metricSubType, v -> {\n            Gauge gauge = gauges.get(v.name());\n            if (gauge == null) {\n                return;\n            }\n            Object value = gauge.getValue();\n            if (value instanceof GaugeMetricModel) {\n                GaugeMetricModel model = (GaugeMetricModel) value;\n                output.putAll(model.toHashMap());\n            } else if (value instanceof Number || value instanceof Boolean) {\n                output.put(\"value\", value);\n            } else {\n                output.put(\"value\", value.toString());\n            }\n        });\n    }\n\n    protected static <T> void consumerMetric(Map<MetricSubType, T> map, MetricSubType metricSubType, Consumer<T> consumer) {\n        if (metricSubType == null) {\n            map.values().forEach(consumer);\n        }\n        T t = map.get(metricSubType);\n        if (t != null) {\n            consumer.accept(t);\n        }\n    }\n\n    @Override\n    protected void writeCounters(String key, MetricSubType metricSubType, SortedMap<String, Counter> counters, Map<String, Object> output) {\n        Map<MetricSubType, MetricName> map = nameFactory.counterNames(key);\n        consumerMetric(map, metricSubType, v -> Optional\n            .ofNullable(counters.get(v.name()))\n            .ifPresent(c -> v.getValueFetcher().forEach((fieldName, fetcher) -> appendField(output, fieldName, fetcher, CounterImpl.build(c)))));\n\n    }\n\n    @Override\n    @SuppressWarnings(\"all\")\n    protected void writeHistograms(String key, MetricSubType metricSubType, SortedMap<String, Histogram> histograms, Map<String, Object> output) {\n        //write histograms, Temporarily unsupported\n        //Please use timer to calculate the time of P95, P99, etc\n    }\n\n    @Override\n    protected void writeMeters(String key, MetricSubType metricSubType, SortedMap<String, Meter> meters, Map<String, Object> output) {\n        Map<MetricSubType, MetricName> map = nameFactory.meterNames(key);\n        consumerMetric(map, metricSubType, v -> Optional\n            .ofNullable(meters.get(v.name()))\n            .ifPresent(m -> v.getValueFetcher().forEach(\n                (fieldName, fetcher) -> appendField(output, fieldName, fetcher, MeterImpl.build(m))))\n        );\n    }\n\n    private void appendField(Map<String, Object> output, MetricField fieldName, MetricValueFetcher fetcher,\n                             com.megaease.easeagent.plugin.api.metric.Metric metric) {\n        switch (fieldName.getType()) {\n            case DURATION:\n                appendDuration(output, fieldName.getField(), fetcher.apply(metric), fieldName.getScale());\n                break;\n            case RATE:\n                appendRate(output, fieldName.getField(), fetcher.apply(metric), fieldName.getScale());\n                break;\n            default:\n                output.put(fieldName.getField(), fetcher.apply(metric));\n                break;\n        }\n    }\n\n    @Override\n    protected void writeTimers(String key, MetricSubType metricSubType, SortedMap<String, Timer> timers, Map<String, Object> output) {\n        Map<MetricSubType, MetricName> map = nameFactory.timerNames(key);\n        consumerMetric(map, metricSubType, v -> Optional.ofNullable(timers.get(v.name())).ifPresent(t -> {\n                final Snapshot snapshot = t.getSnapshot();\n                v.getValueFetcher().forEach((fieldName, fetcher) -> {\n                    if (fetcher.getClazz().equals(com.megaease.easeagent.plugin.api.metric.Snapshot.class)) {\n                        appendField(output, fieldName, fetcher, SnapshotImpl.build(snapshot));\n                    } else {\n                        appendField(output, fieldName, fetcher, TimerImpl.build(t));\n                    }\n                });\n            })\n        );\n    }\n}\n"
  },
  {
    "path": "metrics/src/main/java/com/megaease/easeagent/metrics/converter/EaseAgentPrometheusExports.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.metrics.converter;\n\nimport com.codahale.metrics.*;\nimport com.codahale.metrics.Timer;\nimport com.megaease.easeagent.log4j2.Logger;\nimport com.megaease.easeagent.log4j2.LoggerFactory;\nimport com.megaease.easeagent.plugin.api.metric.name.MetricName;\nimport io.prometheus.client.Collector;\nimport io.prometheus.client.dropwizard.samplebuilder.SampleBuilder;\n\nimport java.util.*;\n\npublic class EaseAgentPrometheusExports extends Collector implements Collector.Describable {\n    private static final Logger LOGGER = LoggerFactory.getLogger(EaseAgentPrometheusExports.class);\n    private final MetricRegistry registry;\n    private final AbstractConverter abstractConverter;\n    private final MetricFilter metricFilter = MetricFilter.ALL;\n    private final SampleBuilder sampleBuilder;\n    private final CounterExports counterExports = new CounterExports();\n    private final MeterExports meterExports = new MeterExports();\n    private final TimerExports timerExports = new TimerExports();\n    private final HistogramExports histogramExports = new HistogramExports();\n    private final GaugeExports gaugeExports = new GaugeExports();\n\n\n    public EaseAgentPrometheusExports(MetricRegistry registry, AbstractConverter abstractConverter, SampleBuilder sampleBuilder) {\n        this.registry = registry;\n        this.abstractConverter = abstractConverter;\n        this.sampleBuilder = sampleBuilder;\n    }\n\n    private static String getHelpMessage(String metricName, Class<?> clzss) {\n        return String.format(\"Generated from Dropwizard metric import (metric=%s, type=%s)\", metricName, clzss.getName());\n    }\n\n    MetricFamilySamples.Sample doubleValue(String dropwizardName, Object obj, String valueType, Class<?> clzss) {\n        double value;\n        if (obj instanceof Number) {\n            value = ((Number) obj).doubleValue();\n        } else {\n            if (!(obj instanceof Boolean)) {\n                LOGGER.warn(String.format(\"Invalid type for %s %s: %s\", clzss.getSimpleName(), sanitizeMetricName(dropwizardName), obj == null ? \"null\" : clzss.getName()));\n                return null;\n            }\n\n            value = (Boolean) obj ? 1.0D : 0.0D;\n        }\n\n        return this.sampleBuilder.createSample(dropwizardName, \"_\" + valueType, Collections.emptyList(), Collections.emptyList(), value);\n    }\n\n    public MetricFilter getMetricFilter() {\n        return metricFilter;\n    }\n\n    @Override\n    public List<MetricFamilySamples> collect() {\n        Map<String, MetricFamilySamples> mfSamplesMap = new HashMap<>();\n\n        gaugeExports.addToMap(mfSamplesMap);\n        counterExports.addToMap(mfSamplesMap);\n        meterExports.addToMap(mfSamplesMap);\n        timerExports.addToMap(mfSamplesMap);\n        histogramExports.addToMap(mfSamplesMap);\n        return new ArrayList<>(mfSamplesMap.values());\n    }\n\n    protected void addToMap(Map<String, MetricFamilySamples> mfSamplesMap, MetricFamilySamples newMfSamples) {\n        if (newMfSamples != null) {\n            MetricFamilySamples currentMfSamples = mfSamplesMap.get(newMfSamples.name);\n            if (currentMfSamples == null) {\n                mfSamplesMap.put(newMfSamples.name, newMfSamples);\n            } else {\n                List<MetricFamilySamples.Sample> samples = new ArrayList<>(currentMfSamples.samples);\n                samples.addAll(newMfSamples.samples);\n                mfSamplesMap.put(newMfSamples.name, new MetricFamilySamples(newMfSamples.name, currentMfSamples.type, currentMfSamples.help, samples));\n            }\n        }\n\n    }\n\n    public List<MetricFamilySamples> describe() {\n        return new ArrayList<>();\n    }\n\n    abstract class Exports<T extends Metric> {\n        private final Collector.Type type;\n        private final Class<?> clzss;\n\n        public Exports(Type type, Class<?> clzss) {\n            this.type = type;\n            this.clzss = clzss;\n        }\n\n        public void addToMap(Map<String, MetricFamilySamples> mfSamplesMap) {\n            Map<String, Object> values = new HashMap<>();\n            SortedMap<String, T> gaugeSortedMap = getMetric();\n            for (String s : gaugeSortedMap.keySet()) {\n                writeValue(MetricName.metricNameFor(s), gaugeSortedMap, values);\n                for (Map.Entry<String, Object> entry : values.entrySet()) {\n                    MetricFamilySamples.Sample sample = doubleValue(s, entry.getValue(), entry.getKey(), clzss);\n                    EaseAgentPrometheusExports.this.addToMap(mfSamplesMap, new MetricFamilySamples(sample.name, type, getHelpMessage(sample.name, clzss), Collections.singletonList(sample)));\n                }\n                values.clear();\n            }\n        }\n\n        protected abstract SortedMap<String, T> getMetric();\n\n        protected abstract void writeValue(MetricName metricName, SortedMap<String, T> metric, Map<String, Object> values);\n    }\n\n\n    class CounterExports extends Exports<Counter> {\n\n        public CounterExports() {\n            super(Type.SUMMARY, Counter.class);\n        }\n\n        @Override\n        protected SortedMap<String, Counter> getMetric() {\n            return registry.getCounters(metricFilter);\n        }\n\n        @Override\n        protected void writeValue(MetricName metricName, SortedMap<String, Counter> metric, Map<String, Object> values) {\n            abstractConverter.writeCounters(metricName.getKey(), metricName.getMetricSubType(), metric, values);\n        }\n    }\n\n    class MeterExports extends Exports<Meter> {\n\n        public MeterExports() {\n            super(Type.SUMMARY, Meter.class);\n        }\n\n        @Override\n        protected SortedMap<String, Meter> getMetric() {\n            return registry.getMeters(metricFilter);\n        }\n\n        @Override\n        protected void writeValue(MetricName metricName, SortedMap<String, Meter> metric, Map<String, Object> values) {\n            abstractConverter.writeMeters(metricName.getKey(), metricName.getMetricSubType(), metric, values);\n        }\n    }\n\n    class TimerExports extends Exports<Timer> {\n\n        public TimerExports() {\n            super(Type.SUMMARY, Timer.class);\n        }\n\n        @Override\n        protected SortedMap<String, Timer> getMetric() {\n            return registry.getTimers(metricFilter);\n        }\n\n        @Override\n        protected void writeValue(MetricName metricName, SortedMap<String, Timer> metric, Map<String, Object> values) {\n            abstractConverter.writeTimers(metricName.getKey(), metricName.getMetricSubType(), metric, values);\n        }\n    }\n\n    class HistogramExports extends Exports<Histogram> {\n\n        public HistogramExports() {\n            super(Type.SUMMARY, Histogram.class);\n        }\n\n        @Override\n        protected SortedMap<String, Histogram> getMetric() {\n            return registry.getHistograms(metricFilter);\n        }\n\n        @Override\n        protected void writeValue(MetricName metricName, SortedMap<String, Histogram> metric, Map<String, Object> values) {\n            abstractConverter.writeHistograms(metricName.getKey(), metricName.getMetricSubType(), metric, values);\n        }\n    }\n\n    class GaugeExports extends Exports<Gauge> {\n\n        public GaugeExports() {\n            super(Type.GAUGE, Gauge.class);\n        }\n\n        @Override\n        protected SortedMap<String, Gauge> getMetric() {\n            return registry.getGauges(metricFilter);\n        }\n\n        @Override\n        protected void writeValue(MetricName metricName, SortedMap<String, Gauge> metric, Map<String, Object> values) {\n            abstractConverter.writeGauges(metricName.getKey(), metricName.getMetricSubType(), metric, values);\n        }\n    }\n}\n"
  },
  {
    "path": "metrics/src/main/java/com/megaease/easeagent/metrics/converter/IgnoreOutputException.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.metrics.converter;\n\nclass IgnoreOutputException extends RuntimeException {\n    public IgnoreOutputException(String message) {\n        super(message);\n    }\n}\n"
  },
  {
    "path": "metrics/src/main/java/com/megaease/easeagent/metrics/converter/KeyType.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.metrics.converter;\n\n/**\n * KeyType indicated how to fetch key value from which type metric of MetricRegistry, first we need to recognize what\n * is key of data?</p>\n * The key is a value which attached many metrics' value with it, for example:</p>\n * In <b>http-request</b>, we think the url is key, other metrics' value describe a special url properties, in\n * <b>jvm-memory</b> resource is key.\n */\n@SuppressWarnings(\"all\")\npublic enum KeyType {\n    Timer,\n    Gauge,\n    Counter,\n    Histogram,\n    Meter;\n}\n"
  },
  {
    "path": "metrics/src/main/java/com/megaease/easeagent/metrics/converter/MetricsAdditionalAttributes.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.metrics.converter;\n\nimport com.megaease.easeagent.plugin.api.config.Config;\nimport com.megaease.easeagent.config.ConfigUtils;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.utils.AdditionalAttributes;\n\nimport java.util.Map;\nimport java.util.function.Supplier;\n\npublic class MetricsAdditionalAttributes implements Supplier<Map<String, Object>> {\n\n    private volatile Map<String, Object> additionalAttributes;\n    private volatile String serviceName = \"\";\n    private volatile String systemName = \"\";\n\n    public MetricsAdditionalAttributes(Config config) {\n        ConfigUtils.bindProp(ConfigConst.SERVICE_NAME, config, Config::getString, v -> {\n            this.serviceName = v;\n            this.additionalAttributes = new AdditionalAttributes(this.serviceName, this.systemName).getAdditionalAttributes();\n        });\n        ConfigUtils.bindProp(ConfigConst.SYSTEM_NAME, config, Config::getString, v -> {\n            this.systemName = v;\n            this.additionalAttributes = new AdditionalAttributes(this.serviceName, this.systemName).getAdditionalAttributes();\n        });\n    }\n\n    @Override\n    public Map<String, Object> get() {\n        return additionalAttributes;\n    }\n}\n"
  },
  {
    "path": "metrics/src/main/java/com/megaease/easeagent/metrics/impl/CounterImpl.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.metrics.impl;\n\n\nimport com.codahale.metrics.Counter;\nimport com.megaease.easeagent.plugin.bridge.NoOpMetrics;\n\nimport java.util.Objects;\n\npublic class CounterImpl implements com.megaease.easeagent.plugin.api.metric.Counter {\n    private final Counter counter;\n\n    private CounterImpl(Counter counter) {\n        this.counter = Objects.requireNonNull(counter, \"counter must not be null\");\n    }\n\n    public static com.megaease.easeagent.plugin.api.metric.Counter build(Counter counter) {\n        return counter == null ? NoOpMetrics.NO_OP_COUNTER : new CounterImpl(counter);\n    }\n\n    @Override\n    public void inc() {\n        counter.inc();\n    }\n\n    @Override\n    public void inc(long n) {\n        counter.inc(n);\n    }\n\n    @Override\n    public void dec() {\n        counter.dec();\n    }\n\n    @Override\n    public void dec(long n) {\n        counter.dec(n);\n    }\n\n    @Override\n    public long getCount() {\n        return counter.getCount();\n    }\n\n    @Override\n    public Object unwrap() {\n        return counter;\n    }\n}\n"
  },
  {
    "path": "metrics/src/main/java/com/megaease/easeagent/metrics/impl/GaugeImpl.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.metrics.impl;\n\n\nimport com.megaease.easeagent.plugin.api.metric.Gauge;\n\nimport java.util.Objects;\n\npublic class GaugeImpl implements com.codahale.metrics.Gauge {\n    private final Gauge g;\n\n    public GaugeImpl(Gauge g) {\n        this.g = Objects.requireNonNull(g, \"g must not be null\");\n    }\n\n    Gauge getG() {\n        return g;\n    }\n\n    @Override\n    public Object getValue() {\n        return g.getValue();\n    }\n}\n"
  },
  {
    "path": "metrics/src/main/java/com/megaease/easeagent/metrics/impl/HistogramImpl.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.metrics.impl;\n\nimport com.codahale.metrics.Histogram;\nimport com.megaease.easeagent.plugin.api.metric.Snapshot;\nimport com.megaease.easeagent.plugin.bridge.NoOpMetrics;\n\nimport java.util.Objects;\n\npublic class HistogramImpl implements com.megaease.easeagent.plugin.api.metric.Histogram {\n    private final Histogram histogram;\n\n    private HistogramImpl(Histogram histogram) {\n        this.histogram = Objects.requireNonNull(histogram, \"histogram must not be null\");\n    }\n\n    public static com.megaease.easeagent.plugin.api.metric.Histogram build(Histogram histogram) {\n        return histogram == null ? NoOpMetrics.NO_OP_HISTOGRAM : new HistogramImpl(histogram);\n    }\n\n    @Override\n    public void update(int value) {\n        histogram.update(value);\n    }\n\n    @Override\n    public void update(long value) {\n        histogram.update(value);\n    }\n\n    @Override\n    public long getCount() {\n        return histogram.getCount();\n    }\n\n    @Override\n    public Snapshot getSnapshot() {\n        return SnapshotImpl.build(histogram.getSnapshot());\n    }\n\n    @Override\n    public Object unwrap() {\n        return histogram;\n    }\n}\n"
  },
  {
    "path": "metrics/src/main/java/com/megaease/easeagent/metrics/impl/MeterImpl.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.metrics.impl;\n\nimport com.codahale.metrics.Meter;\nimport com.megaease.easeagent.plugin.bridge.NoOpMetrics;\n\nimport java.util.Objects;\n\npublic class MeterImpl implements com.megaease.easeagent.plugin.api.metric.Meter {\n    private final Meter meter;\n\n    private MeterImpl(Meter meter) {\n        this.meter = Objects.requireNonNull(meter, \"meter must not be null\");\n    }\n\n    public static com.megaease.easeagent.plugin.api.metric.Meter build(Meter meter) {\n        return meter == null ? NoOpMetrics.NO_OP_METER : new MeterImpl(meter);\n    }\n\n    @Override\n    public void mark() {\n        meter.mark();\n    }\n\n    @Override\n    public void mark(long n) {\n        meter.mark(n);\n    }\n\n    @Override\n    public long getCount() {\n        return meter.getCount();\n    }\n\n    @Override\n    public double getFifteenMinuteRate() {\n        return meter.getFifteenMinuteRate();\n    }\n\n    @Override\n    public double getFiveMinuteRate() {\n        return meter.getFiveMinuteRate();\n    }\n\n    @Override\n    public double getMeanRate() {\n        return meter.getMeanRate();\n    }\n\n    @Override\n    public double getOneMinuteRate() {\n        return meter.getOneMinuteRate();\n    }\n\n    @Override\n    public Object unwrap() {\n        return meter;\n    }\n}\n"
  },
  {
    "path": "metrics/src/main/java/com/megaease/easeagent/metrics/impl/MetricInstance.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.metrics.impl;\n\nimport com.megaease.easeagent.plugin.api.metric.*;\n\nimport java.lang.reflect.ParameterizedType;\nimport java.lang.reflect.Type;\n\npublic abstract class MetricInstance<T extends Metric> {\n    public static final MetricInstance<Counter> COUNTER = new MetricInstance<Counter>() {\n        @Override\n        protected Counter toInstance(String name, Metric metric) {\n            return (Counter) metric;\n        }\n    };\n\n    public static final MetricInstance<Histogram> HISTOGRAM = new MetricInstance<Histogram>() {\n        @Override\n        protected Histogram toInstance(String name, Metric metric) {\n            return (Histogram) metric;\n        }\n    };\n\n    public static final MetricInstance<Meter> METER = new MetricInstance<Meter>() {\n        @Override\n        protected Meter toInstance(String name, Metric metric) {\n            return (Meter) metric;\n        }\n    };\n\n    public static final MetricInstance<Timer> TIMER = new MetricInstance<Timer>() {\n        @Override\n        protected Timer toInstance(String name, Metric metric) {\n            return (Timer) metric;\n        }\n    };\n\n    public static final MetricInstance<Gauge> GAUGE = new MetricInstance<Gauge>() {\n        @Override\n        protected Gauge toInstance(String name, Metric metric) {\n            return (Gauge) metric;\n        }\n    };\n\n    private final Class<?> type;\n\n    private MetricInstance() {\n        Type superClass = getClass().getGenericSuperclass();\n        if (superClass instanceof Class<?>) { // sanity check, should never happen\n            throw new IllegalArgumentException(\"Internal error: MetricInstance constructed without actual type information\");\n        }\n        Type t = ((ParameterizedType) superClass).getActualTypeArguments()[0];\n        if (!(t instanceof Class)) {\n            throw new IllegalArgumentException(\"Internal error: MetricInstance constructed without actual type information\");\n        }\n        type = (Class<?>) t;\n    }\n\n    protected T to(String name, Metric metric) {\n        if (!type.isInstance(metric)) {\n            throw new IllegalArgumentException(String.format(\"%s is already used for a different type<%s> of metric\", name, metric.getClass().getName()));\n        }\n        return toInstance(name, metric);\n    }\n\n    protected abstract T toInstance(String name, Metric metric);\n}\n"
  },
  {
    "path": "metrics/src/main/java/com/megaease/easeagent/metrics/impl/MetricRegistryImpl.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.metrics.impl;\n\nimport com.codahale.metrics.MetricRegistry;\nimport com.codahale.metrics.MetricRegistryListener;\nimport com.megaease.easeagent.plugin.api.metric.*;\nimport com.megaease.easeagent.plugin.bridge.NoOpMetrics;\nimport com.megaease.easeagent.plugin.utils.NoNull;\n\nimport javax.annotation.Nonnull;\nimport java.util.Collections;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.ConcurrentMap;\n\npublic class MetricRegistryImpl implements com.megaease.easeagent.plugin.api.metric.MetricRegistry {\n    private final ConcurrentMap<String, Metric> metricCache;\n    private final MetricRegistry metricRegistry;\n\n    MetricBuilder<Counter> counters = new MetricBuilder<Counter>() {\n        @Override\n        public Counter newMetric(String name) {\n            return NoNull.of(CounterImpl.build(metricRegistry.counter(name)), NoOpMetrics.NO_OP_COUNTER);\n        }\n    };\n\n    MetricBuilder<Histogram> histograms = new MetricBuilder<Histogram>() {\n        @Override\n        public Histogram newMetric(String name) {\n            return NoNull.of(HistogramImpl.build(metricRegistry.histogram(name)), NoOpMetrics.NO_OP_HISTOGRAM);\n        }\n\n    };\n\n    MetricBuilder<Meter> meters = new MetricBuilder<Meter>() {\n        @Override\n        public Meter newMetric(String name) {\n            return NoNull.of(MeterImpl.build(metricRegistry.meter(name)), NoOpMetrics.NO_OP_METER);\n        }\n\n    };\n\n    MetricBuilder<Timer> timers = new MetricBuilder<Timer>() {\n        @Override\n        public Timer newMetric(String name) {\n            return NoNull.of(TimerImpl.build(metricRegistry.timer(name)), NoOpMetrics.NO_OP_TIMER);\n        }\n    };\n\n    private MetricRegistryImpl(MetricRegistry metricRegistry) {\n        this.metricRegistry = Objects.requireNonNull(metricRegistry, \"metricRegistry must not be null\");\n        this.metricCache = new ConcurrentHashMap<>();\n        this.metricRegistry.addListener(new MetricRemoveListener());\n\n    }\n\n    public static com.megaease.easeagent.plugin.api.metric.MetricRegistry build(MetricRegistry metricRegistry) {\n        return metricRegistry == null ? NoOpMetrics.NO_OP_METRIC : new MetricRegistryImpl(metricRegistry);\n    }\n\n\n    @Override\n    public boolean remove(String name) {\n        synchronized (metricCache) {\n            return metricRegistry.remove(name);\n        }\n    }\n\n    private <T extends Metric> T getOrAdd(String name, MetricInstance<T> instance, MetricBuilder<T> builder) {\n        Metric metric = metricCache.get(name);\n        if (metric != null) {\n            return instance.to(name, metric);\n        }\n        synchronized (metricCache) {\n            metric = metricCache.get(name);\n            if (metric != null) {\n                return instance.to(name, metric);\n            }\n            T t = builder.newMetric(name);\n            metricCache.putIfAbsent(name, t);\n            return t;\n        }\n    }\n\n    @Override\n    public Map<String, Metric> getMetrics() {\n        return Collections.unmodifiableMap(metricCache);\n    }\n\n    @Override\n    public Meter meter(String name) {\n        return getOrAdd(name, MetricInstance.METER, meters);\n    }\n\n    @Override\n    public Counter counter(String name) {\n        return getOrAdd(name, MetricInstance.COUNTER, counters);\n    }\n\n\n    @Override\n    @SuppressWarnings(\"rawtypes\")\n    public Gauge gauge(String name, MetricSupplier<Gauge> supplier) {\n        Metric metric = metricCache.get(name);\n        if (metric != null) {\n            return MetricInstance.GAUGE.to(name, metric);\n        }\n        synchronized (metricCache) {\n            metric = metricCache.get(name);\n            if (metric != null) {\n                return MetricInstance.GAUGE.to(name, metric);\n            }\n            com.codahale.metrics.Gauge result = metricRegistry.gauge(name, new GaugeSupplier(supplier));\n            Gauge g = ((GaugeImpl) result).getG();\n            metricCache.putIfAbsent(name, g);\n            return g;\n        }\n    }\n\n    @Override\n    public Histogram histogram(String name) {\n        return getOrAdd(name, MetricInstance.HISTOGRAM, histograms);\n    }\n\n    @Override\n    public Timer timer(String name) {\n        return getOrAdd(name, MetricInstance.TIMER, timers);\n    }\n\n    public MetricRegistry getMetricRegistry() {\n        return metricRegistry;\n    }\n\n    public static class GaugeSupplier implements MetricRegistry.MetricSupplier<com.codahale.metrics.Gauge> {\n        private final MetricSupplier<Gauge> supplier;\n\n        GaugeSupplier(@Nonnull MetricSupplier<Gauge> supplier) {\n            this.supplier = supplier;\n        }\n\n        @Override\n        public com.codahale.metrics.Gauge newMetric() {\n            Gauge newGauge = supplier.newMetric();\n            return new GaugeImpl(newGauge);\n        }\n    }\n\n    /**\n     * A quick and easy way of capturing the notion of default metrics.\n     */\n    private interface MetricBuilder<T extends Metric> {\n        T newMetric(String name);\n    }\n\n    class MetricRemoveListener implements MetricRegistryListener {\n\n        /**\n         * Do nothing because of added by {@link MetricRegistryImpl#getOrAdd(String, MetricInstance, MetricBuilder)}\n         * @param name\n         * @param gauge\n         */\n        @Override\n        public void onGaugeAdded(String name, com.codahale.metrics.Gauge<?> gauge) {\n            //Do nothing\n        }\n\n        @Override\n        public void onGaugeRemoved(String name) {\n            synchronized (metricCache) {\n                metricCache.remove(name);\n            }\n        }\n\n        /**\n         * Do nothing because of added by {@link MetricRegistryImpl#getOrAdd(String, MetricInstance, MetricBuilder)}\n         * @param name\n         * @param counter\n         */\n        @Override\n        public void onCounterAdded(String name, com.codahale.metrics.Counter counter) {\n            //Do nothing\n        }\n\n        @Override\n        public void onCounterRemoved(String name) {\n            synchronized (metricCache) {\n                metricCache.remove(name);\n            }\n        }\n\n        /**\n         * Do nothing because of added by {@link MetricRegistryImpl#getOrAdd(String, MetricInstance, MetricBuilder)}\n         * @param name\n         * @param histogram\n         */\n        @Override\n        public void onHistogramAdded(String name, com.codahale.metrics.Histogram histogram) {\n            //Do nothing\n        }\n\n        @Override\n        public void onHistogramRemoved(String name) {\n            synchronized (metricCache) {\n                metricCache.remove(name);\n            }\n\n        }\n\n        /**\n         * Do nothing because of added by {@link MetricRegistryImpl#getOrAdd(String, MetricInstance, MetricBuilder)}\n         * @param name\n         * @param meter\n         */\n        @Override\n        public void onMeterAdded(String name, com.codahale.metrics.Meter meter) {\n            //Do nothing\n        }\n\n        @Override\n        public void onMeterRemoved(String name) {\n            synchronized (metricCache) {\n                metricCache.remove(name);\n            }\n\n        }\n\n        /**\n         * Do nothing because of added by {@link MetricRegistryImpl#getOrAdd(String, MetricInstance, MetricBuilder)}\n         *\n         * @param name\n         * @param timer\n         */\n        @Override\n        public void onTimerAdded(String name, com.codahale.metrics.Timer timer) {\n            //Do nothing\n        }\n\n        @Override\n        public void onTimerRemoved(String name) {\n            synchronized (metricCache) {\n                metricCache.remove(name);\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "metrics/src/main/java/com/megaease/easeagent/metrics/impl/SnapshotImpl.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.metrics.impl;\n\nimport com.codahale.metrics.Snapshot;\nimport com.megaease.easeagent.plugin.bridge.NoOpMetrics;\n\nimport java.io.OutputStream;\nimport java.util.Objects;\n\npublic class SnapshotImpl implements com.megaease.easeagent.plugin.api.metric.Snapshot {\n    private final Snapshot snapshot;\n\n    private SnapshotImpl(Snapshot snapshot) {\n        this.snapshot = Objects.requireNonNull(snapshot, \"snapshot must not be null\");\n    }\n\n    public static com.megaease.easeagent.plugin.api.metric.Snapshot build(Snapshot snapshot) {\n        return snapshot == null ? NoOpMetrics.NO_OP_SNAPSHOT : new SnapshotImpl(snapshot);\n    }\n\n\n    @Override\n    public double getValue(double quantile) {\n        return snapshot.getValue(quantile);\n    }\n\n    @Override\n    public long[] getValues() {\n        return snapshot.getValues();\n    }\n\n    @Override\n    public int size() {\n        return snapshot.size();\n    }\n\n    @Override\n    public long getMax() {\n        return snapshot.getMax();\n    }\n\n    @Override\n    public double getMean() {\n        return snapshot.getMean();\n    }\n\n    @Override\n    public long getMin() {\n        return snapshot.getMin();\n    }\n\n    @Override\n    public double getStdDev() {\n        return snapshot.getStdDev();\n    }\n\n    @Override\n    public void dump(OutputStream output) {\n        snapshot.dump(output);\n    }\n\n    @Override\n    public Object unwrap() {\n        return snapshot;\n    }\n}\n"
  },
  {
    "path": "metrics/src/main/java/com/megaease/easeagent/metrics/impl/TimerImpl.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.metrics.impl;\n\nimport com.codahale.metrics.Timer;\nimport com.megaease.easeagent.plugin.api.metric.Snapshot;\nimport com.megaease.easeagent.plugin.bridge.NoOpMetrics;\n\nimport java.time.Duration;\nimport java.util.Objects;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.Supplier;\n\npublic class TimerImpl implements com.megaease.easeagent.plugin.api.metric.Timer {\n    private final Timer timer;\n\n    private TimerImpl(Timer timer) {\n        this.timer = Objects.requireNonNull(timer, \"timer must not be null\");\n    }\n\n    public static com.megaease.easeagent.plugin.api.metric.Timer build(Timer timer) {\n        return timer == null ? NoOpMetrics.NO_OP_TIMER : new TimerImpl(timer);\n    }\n\n    @Override\n    public void update(long duration, TimeUnit unit) {\n        timer.update(duration, unit);\n    }\n\n    @Override\n    public void update(Duration duration) {\n        timer.update(duration);\n    }\n\n    @Override\n    public <T> T time(Callable<T> event) throws Exception {\n        return timer.time(event);\n    }\n\n    @Override\n    public <T> T timeSupplier(Supplier<T> event) {\n        return timer.timeSupplier(event);\n    }\n\n    @Override\n    public void time(Runnable event) {\n        timer.time(event);\n    }\n\n    @Override\n    public Context time() {\n        return new ContextImpl(timer.time());\n    }\n\n    @Override\n    public long getCount() {\n        return timer.getCount();\n    }\n\n    @Override\n    public double getFifteenMinuteRate() {\n        return timer.getFifteenMinuteRate();\n    }\n\n    @Override\n    public double getFiveMinuteRate() {\n        return timer.getFiveMinuteRate();\n    }\n\n    @Override\n    public double getMeanRate() {\n        return timer.getMeanRate();\n    }\n\n    @Override\n    public double getOneMinuteRate() {\n        return timer.getOneMinuteRate();\n    }\n\n    @Override\n    public Snapshot getSnapshot() {\n        return SnapshotImpl.build(timer.getSnapshot());\n    }\n\n    @Override\n    public Object unwrap() {\n        return timer;\n    }\n\n\n    public static class ContextImpl implements Context {\n        private final Timer.Context context;\n\n        ContextImpl(Timer.Context context) {\n            this.context = context;\n        }\n\n        @Override\n        public long stop() {\n            return context.stop();\n        }\n\n        @Override\n        public void close() {\n            context.close();\n        }\n    }\n\n}\n"
  },
  {
    "path": "metrics/src/main/java/com/megaease/easeagent/metrics/jvm/JvmBeanProvider.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.metrics.jvm;\n\nimport com.megaease.easeagent.metrics.MetricProviderImpl;\nimport com.megaease.easeagent.metrics.jvm.gc.JVMGCMetricV2;\nimport com.megaease.easeagent.metrics.jvm.memory.JVMMemoryMetricV2;\nimport com.megaease.easeagent.plugin.bean.AgentInitializingBean;\nimport com.megaease.easeagent.plugin.bean.BeanProvider;\n\npublic class JvmBeanProvider implements BeanProvider, AgentInitializingBean {\n    private final MetricProviderImpl metricProvider = new MetricProviderImpl();\n\n    public void jvmGcMetricV2() {\n        JVMGCMetricV2.getMetric();\n    }\n\n    public void jvmMemoryMetricV2() {\n        JVMMemoryMetricV2.getMetric();\n    }\n\n    @Override\n    public int order() {\n        return BeanOrder.METRIC_REGISTRY.getOrder();\n    }\n\n    @Override\n    public void afterPropertiesSet() {\n        jvmGcMetricV2();\n        jvmMemoryMetricV2();\n    }\n}\n"
  },
  {
    "path": "metrics/src/main/java/com/megaease/easeagent/metrics/jvm/gc/JVMGCMetricV2.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.metrics.jvm.gc;\n\nimport com.megaease.easeagent.plugin.api.config.AutoRefreshPluginConfigRegistry;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.metric.*;\nimport com.megaease.easeagent.plugin.api.metric.name.*;\nimport com.megaease.easeagent.plugin.utils.ImmutableMap;\n\nimport javax.annotation.Nonnull;\nimport javax.management.NotificationEmitter;\nimport javax.management.NotificationListener;\nimport javax.management.openmbean.CompositeData;\nimport java.lang.management.GarbageCollectorMXBean;\nimport java.lang.management.ManagementFactory;\nimport java.util.Map;\n\n\npublic class JVMGCMetricV2 extends ServiceMetric {\n    private static final String NO_GC = \"No GC\";\n\n    public static final ServiceMetricSupplier<JVMGCMetricV2> METRIC_SUPPLIER = new ServiceMetricSupplier<JVMGCMetricV2>() {\n        @Override\n        public NameFactory newNameFactory() {\n            return JVMGCMetricV2.nameFactory();\n        }\n\n        @Override\n        public JVMGCMetricV2 newInstance(MetricRegistry metricRegistry, NameFactory nameFactory) {\n            return new JVMGCMetricV2(metricRegistry, nameFactory);\n        }\n    };\n\n    private static IPluginConfig config;\n\n    public static JVMGCMetricV2 getMetric() {\n        config = AutoRefreshPluginConfigRegistry.getOrCreate(\"observability\", \"jvmGc\", \"metric\");\n        Tags tags = new Tags(\"application\", \"jvm-gc\", \"resource\");\n\n        JVMGCMetricV2 v2 = ServiceMetricRegistry.getOrCreate(config, tags, METRIC_SUPPLIER);\n        v2.collect();\n\n        return v2;\n    }\n\n    public JVMGCMetricV2(@Nonnull MetricRegistry metricRegistry,\n                         @Nonnull NameFactory nameFactory) {\n        super(metricRegistry, nameFactory);\n    }\n\n    static NameFactory nameFactory() {\n        return NameFactory.createBuilder()\n            .meterType(MetricSubType.DEFAULT, ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                .put(MetricField.TIMES, MetricValueFetcher.MeteredCount)\n                .put(MetricField.TIMES_RATE, MetricValueFetcher.MeteredMeanRate)\n                .build())\n            .counterType(MetricSubType.DEFAULT, ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                .put(MetricField.TOTAL_COLLECTION_TIME, MetricValueFetcher.CountingCount)\n                .build())\n            .build();\n    }\n\n    public void collect() {\n        for (GarbageCollectorMXBean mBean : ManagementFactory.getGarbageCollectorMXBeans()) {\n            if (!(mBean instanceof NotificationEmitter)) {\n                continue;\n            }\n            NotificationListener listener = getListener();\n            NotificationEmitter notificationEmitter = (NotificationEmitter) mBean;\n            notificationEmitter.addNotificationListener(listener, null, null);\n        }\n    }\n\n    @SuppressWarnings(\"all\")\n    private NotificationListener getListener() {\n        return (notification, ref) -> {\n            if (!notification.getType().equals(com.sun.management.GarbageCollectionNotificationInfo.GARBAGE_COLLECTION_NOTIFICATION)) {\n                return;\n            }\n\n            if (!config.enabled()) {\n                return;\n            }\n\n            CompositeData cd = (CompositeData) notification.getUserData();\n            com.sun.management.GarbageCollectionNotificationInfo notificationInfo = com.sun.management.GarbageCollectionNotificationInfo.from(cd);\n            String gcCause = notificationInfo.getGcCause();\n            com.sun.management.GcInfo gcInfo = notificationInfo.getGcInfo();\n            long duration = gcInfo.getDuration();\n\n            String gcName = notificationInfo.getGcName();\n            Map<MetricSubType, MetricName> meterNames = nameFactory.meterNames(gcName);\n            meterNames.forEach((type, name) -> {\n                Meter meter = metricRegistry.meter(name.name());\n                if (!NO_GC.equals(gcCause)) {\n                    meter.mark();\n                }\n            });\n\n            Map<MetricSubType, MetricName> counterNames = nameFactory.counterNames(gcName);\n            counterNames.forEach((type, name) -> {\n                Counter count = metricRegistry.counter(name.name());\n                if (!NO_GC.equals(gcCause)) {\n                    count.inc(duration);\n                }\n            });\n        };\n    }\n}\n"
  },
  {
    "path": "metrics/src/main/java/com/megaease/easeagent/metrics/jvm/memory/JVMMemoryMetricV2.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.metrics.jvm.memory;\n\nimport com.megaease.easeagent.metrics.model.JVMMemoryGaugeMetricModel;\nimport com.megaease.easeagent.plugin.api.config.AutoRefreshPluginConfigRegistry;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.metric.*;\nimport com.megaease.easeagent.plugin.api.metric.name.*;\nimport com.megaease.easeagent.plugin.async.ScheduleHelper;\nimport com.megaease.easeagent.plugin.async.ScheduleRunner;\n\nimport javax.annotation.Nonnull;\nimport java.lang.management.ManagementFactory;\nimport java.lang.management.MemoryPoolMXBean;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.regex.Pattern;\n\npublic class JVMMemoryMetricV2 extends ServiceMetric implements ScheduleRunner {\n    private static final ServiceMetricSupplier<JVMMemoryMetricV2> SUPPLIER = new ServiceMetricSupplier<JVMMemoryMetricV2>() {\n        @Override\n        public NameFactory newNameFactory() {\n            return JVMMemoryMetricV2.nameFactory();\n        }\n\n        @Override\n        public JVMMemoryMetricV2 newInstance(MetricRegistry metricRegistry, NameFactory nameFactory) {\n            return new JVMMemoryMetricV2(metricRegistry, nameFactory);\n        }\n    };\n\n    private static final Pattern WHITESPACE = Pattern.compile(\"[\\\\s]+\");\n    private static final String POOLS = \"pools\";\n    private static IPluginConfig config;\n\n    private JVMMemoryMetricV2(@Nonnull MetricRegistry metricRegistry, @Nonnull NameFactory nameFactory) {\n        super(metricRegistry, nameFactory);\n    }\n\n    public static JVMMemoryMetricV2 getMetric() {\n        config = AutoRefreshPluginConfigRegistry.getOrCreate(\"observability\", \"jvmMemory\", \"metric\");\n        Tags tags = new Tags(\"application\", \"jvm-memory\", \"resource\");\n\n        JVMMemoryMetricV2 v2 = ServiceMetricRegistry.getOrCreate(config, tags, SUPPLIER);\n        ScheduleHelper.DEFAULT.nonStopExecute(10, 10, v2::doJob);\n\n        return v2;\n    }\n\n    static NameFactory nameFactory() {\n        return NameFactory.createBuilder()\n            .gaugeType(MetricSubType.DEFAULT, new HashMap<>())\n            .build();\n    }\n\n    @Override\n    public void doJob() {\n        if (!config.enabled()) {\n            return;\n        }\n        List<MemoryPoolMXBean> memoryPoolMXBeans = ManagementFactory.getMemoryPoolMXBeans();\n        for (MemoryPoolMXBean memoryPoolMXBean : memoryPoolMXBeans) {\n            String memoryPoolMXBeanName = memoryPoolMXBean.getName();\n\n            final String poolName = com.codahale.metrics.MetricRegistry\n                .name(POOLS, WHITESPACE.matcher(memoryPoolMXBeanName).replaceAll(\"-\"));\n\n            Map<MetricSubType, MetricName> map = this.nameFactory.gaugeNames(poolName);\n            for (Map.Entry<MetricSubType, MetricName> entry : map.entrySet()) {\n                MetricName metricName = entry.getValue();\n\n                Gauge<JVMMemoryGaugeMetricModel> gauge = () -> new JVMMemoryGaugeMetricModel(\n                    memoryPoolMXBean.getUsage().getInit(),\n                    memoryPoolMXBean.getUsage().getUsed(),\n                    memoryPoolMXBean.getUsage().getCommitted(),\n                    memoryPoolMXBean.getUsage().getMax());\n\n                this.metricRegistry.gauge(metricName.name(), () -> gauge);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "metrics/src/main/java/com/megaease/easeagent/metrics/model/JVMMemoryGaugeMetricModel.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.metrics.model;\n\nimport com.megaease.easeagent.plugin.tools.metrics.GaugeMetricModel;\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n@Data\n@AllArgsConstructor\npublic class JVMMemoryGaugeMetricModel implements GaugeMetricModel {\n    private Long bytesInit;\n    private Long bytesUsed;\n    private Long bytesCommitted;\n    private Long bytesMax;\n\n    @Override\n    public Map<String, Object> toHashMap() {\n        Map<String, Object> result = new HashMap<>();\n        result.put(\"bytes-init\", bytesInit);\n        result.put(\"bytes-used\", bytesUsed);\n        result.put(\"bytes-committed\", bytesCommitted);\n        result.put(\"bytes-max\", bytesMax);\n        return result;\n    }\n}\n"
  },
  {
    "path": "metrics/src/main/resources/META-INF/services/com.megaease.easeagent.plugin.bean.BeanProvider",
    "content": "com.megaease.easeagent.metrics.MetricBeanProviderImpl\ncom.megaease.easeagent.metrics.jvm.JvmBeanProvider\n"
  },
  {
    "path": "metrics/src/test/java/com/megaease/easeagent/metrics/MetricProviderImplTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.metrics;\n\nimport com.megaease.easeagent.mock.config.MockConfig;\nimport com.megaease.easeagent.plugin.api.Reporter;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.logging.AccessLogInfo;\nimport com.megaease.easeagent.plugin.api.metric.MetricRegistrySupplier;\nimport com.megaease.easeagent.plugin.api.otlp.common.AgentLogData;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\nimport com.megaease.easeagent.plugin.report.EncodedData;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport com.megaease.easeagent.plugin.report.AgentReport;\nimport com.megaease.easeagent.plugin.report.metric.MetricReporterFactory;\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\npublic class MetricProviderImplTest {\n    public static MetricProviderImpl METRIC_PROVIDER;\n\n    static {\n        METRIC_PROVIDER = new MetricProviderImpl();\n        METRIC_PROVIDER.setConfig(MockConfig.getCONFIGS());\n        METRIC_PROVIDER.setAgentReport(new AgentReport() {\n            @Override\n            public void report(ReportSpan span) {\n                // skip\n            }\n\n            @Override\n            public void report(AccessLogInfo log) {\n                // skip\n            }\n\n            @Override\n            public void report(AgentLogData log) {\n                // skip\n            }\n\n            @Override\n            public MetricReporterFactory metricReporter() {\n                return new MetricReporterFactory() {\n                    @Override\n                    public Reporter reporter(IPluginConfig config) {\n                        return new Reporter() {\n                            @Override\n                            public void report(String msg) {\n\n                            }\n\n                            @Override\n                            public void report(EncodedData msg) {\n\n                            }\n                        };\n                    }\n                };\n            }\n        });\n    }\n\n    @Test\n    public void setConfig() {\n        assertNotNull(AgentFieldReflectAccessor.getFieldValue(METRIC_PROVIDER, \"config\"));\n    }\n\n    @Test\n    public void setAgentReport() {\n        assertNotNull(AgentFieldReflectAccessor.getFieldValue(METRIC_PROVIDER, \"agentReport\"));\n    }\n\n    @Test\n    public void metricSupplier() {\n        MetricRegistrySupplier metricRegistrySupplier = METRIC_PROVIDER.metricSupplier();\n        assertNotNull(metricRegistrySupplier);\n    }\n}\n"
  },
  {
    "path": "metrics/src/test/java/com/megaease/easeagent/metrics/MetricRegistryServiceTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.metrics;\n\nimport com.codahale.metrics.MetricRegistry;\nimport com.megaease.easeagent.metrics.converter.ConverterAdapter;\nimport com.megaease.easeagent.metrics.converter.EaseAgentPrometheusExports;\nimport com.megaease.easeagent.metrics.converter.KeyType;\nimport com.megaease.easeagent.plugin.api.metric.name.*;\nimport com.megaease.easeagent.plugin.tools.metrics.GaugeMetricModel;\nimport com.megaease.easeagent.plugin.utils.ImmutableMap;\nimport io.prometheus.client.Collector;\nimport io.prometheus.client.CollectorRegistry;\nimport org.junit.Test;\n\nimport java.util.*;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.Supplier;\n\nimport static org.junit.Assert.*;\n\npublic class MetricRegistryServiceTest {\n\n    private final String typeMatch = \"testType_\";\n\n    private MetricRegistry reset(NameFactory nameFactory) {\n        CollectorRegistry.defaultRegistry.clear();\n        List<KeyType> keyTypes = MetricProviderImpl.keyTypes(nameFactory);\n        Tags tags = new Tags(\"testCategory\", \"testType\", \"testName\").put(\"testKey\", \"testValue\");\n        Supplier<Map<String, Object>> additionalAttributes = () -> Collections.singletonMap(\"additionalAttributesKey\", \"additionalAttributesValue\");\n        ConverterAdapter converterAdapter = new ConverterAdapter(nameFactory, keyTypes,\n            additionalAttributes, tags);\n        MetricRegistry metricRegistry = MetricRegistryService.DEFAULT.createMetricRegistry(\n            converterAdapter,\n            additionalAttributes,\n            tags\n        );\n        for (String s : metricRegistry.getNames()) {\n            metricRegistry.remove(s);\n        }\n        return metricRegistry;\n    }\n\n    @Test\n    public void createMetricRegistry() {\n        NameFactory nameFactory = NameFactory.createBuilder().counterType(MetricSubType.NONE,\n            ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                .put(MetricField.EXECUTION_COUNT, MetricValueFetcher.CountingCount).build()\n        ).gaugeType(MetricSubType.DEFAULT, new HashMap<>()).build();\n        String name = nameFactory.counterName(\"GET tt\", MetricSubType.NONE);\n        MetricRegistry metricRegistry = reset(nameFactory);\n\n        metricRegistry.counter(name).inc();\n        Enumeration<Collector.MetricFamilySamples> samples = CollectorRegistry.defaultRegistry.filteredMetricFamilySamples(Collections.emptySet());\n        while (samples.hasMoreElements()) {\n            Collector.MetricFamilySamples s = samples.nextElement();\n            for (Collector.MetricFamilySamples.Sample sample : s.samples) {\n                assertTrue(sample.labelNames.contains(\"additionalAttributesKey\"));\n                assertTrue(sample.labelNames.contains(\"testKey\"));\n                assertTrue(sample.labelValues.contains(\"additionalAttributesValue\"));\n                assertTrue(sample.labelNames.contains(MetricRegistryService.METRIC_TYPE_LABEL_NAME));\n                assertTrue(sample.labelNames.contains(MetricRegistryService.METRIC_SUB_TYPE_LABEL_NAME));\n                assertTrue(sample.labelValues.contains(MetricType.CounterType.name()));\n                assertTrue(sample.labelValues.contains(MetricSubType.NONE.name()));\n                assertTrue(sample.name.contains(\"testCategory_\"));\n                assertTrue(sample.name.contains(typeMatch));\n            }\n        }\n\n        for (String s : metricRegistry.getNames()) {\n            metricRegistry.remove(s);\n        }\n        List<KeyType> keyTypes = MetricProviderImpl.keyTypes(nameFactory);\n        Tags tags = new Tags(\"testCategory\", \"testType\", \"testName\").put(\"testKey\", \"testValue\");\n        Supplier<Map<String, Object>> additionalAttributes = () -> Collections.singletonMap(\"additionalAttributesKey\", \"additionalAttributesValue\");\n        ConverterAdapter converterAdapter = new ConverterAdapter(nameFactory, keyTypes,\n            additionalAttributes, tags);\n        metricRegistry = MetricRegistryService.DEFAULT.createMetricRegistry(\n            converterAdapter,\n            null,\n            tags\n        );\n        metricRegistry.counter(name).inc();\n        samples = CollectorRegistry.defaultRegistry.filteredMetricFamilySamples(Collections.emptySet());\n        while (samples.hasMoreElements()) {\n            Collector.MetricFamilySamples s = samples.nextElement();\n            for (Collector.MetricFamilySamples.Sample sample : s.samples) {\n                assertEquals(4, sample.labelNames.size());\n                assertEquals(4, sample.labelValues.size());\n                assertTrue(sample.labelNames.contains(MetricRegistryService.METRIC_TYPE_LABEL_NAME));\n                assertTrue(sample.labelNames.contains(MetricRegistryService.METRIC_SUB_TYPE_LABEL_NAME));\n                assertTrue(sample.labelValues.contains(MetricType.CounterType.name()));\n                assertTrue(sample.labelValues.contains(MetricSubType.NONE.name()));\n            }\n        }\n\n\n    }\n\n    @Test\n    public void testCounter() {\n        NameFactory nameFactory = NameFactory.createBuilder().counterType(MetricSubType.DEFAULT,\n            ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                .put(MetricField.EXECUTION_COUNT, MetricValueFetcher.CountingCount).build()\n        ).counterType(MetricSubType.ERROR,\n            ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                .put(MetricField.EXECUTION_ERROR_COUNT, MetricValueFetcher.CountingCount).build()\n        ).build();\n        MetricRegistry metricRegistry = reset(nameFactory);\n        String name = nameFactory.counterName(\"GET tt\", MetricSubType.DEFAULT);\n        metricRegistry.counter(name).inc();\n        String errorName = nameFactory.counterName(\"GET tt\", MetricSubType.ERROR);\n        metricRegistry.counter(errorName);\n        Enumeration<Collector.MetricFamilySamples> samplesEnumeration = CollectorRegistry.defaultRegistry.filteredMetricFamilySamples(Collections.emptySet());\n        List<Collector.MetricFamilySamples> samples = new ArrayList<>();\n        while (samplesEnumeration.hasMoreElements()) {\n            Collector.MetricFamilySamples s = samplesEnumeration.nextElement();\n            samples.add(s);\n        }\n        assertEquals(2, samples.size());\n        for (Collector.MetricFamilySamples s : samples) {\n            assertEquals(1, s.samples.size());\n            for (Collector.MetricFamilySamples.Sample sample : s.samples) {\n                assertTrue(sample.labelNames.contains(MetricRegistryService.METRIC_TYPE_LABEL_NAME));\n                assertTrue(sample.labelNames.contains(MetricRegistryService.METRIC_SUB_TYPE_LABEL_NAME));\n                assertTrue(sample.labelValues.contains(MetricType.CounterType.name()));\n\n                assertTrue(sample.name.contains(\"testCategory_\"));\n                assertTrue(sample.name.contains(typeMatch));\n\n//                assertTrue(sample.labelNames.contains(EaseAgentPrometheusExports.VALUE_TYPE_LABEL_NAME));\n\n                if (sample.labelValues.contains(MetricSubType.DEFAULT.name())) {\n                    assertTrue(sample.name.endsWith(MetricField.EXECUTION_COUNT.getField()));\n                    assertEquals(1, (int) sample.value);\n                } else {\n                    assertTrue(sample.labelValues.contains(MetricSubType.ERROR.name()));\n                    assertTrue(sample.name.endsWith(MetricField.EXECUTION_ERROR_COUNT.getField()));\n                    assertEquals(0, (int) sample.value);\n                }\n            }\n        }\n    }\n\n    @Test\n    public void testTimer() {\n        Map<MetricField, MetricValueFetcher> valueFetchers = ImmutableMap.<MetricField, MetricValueFetcher>builder()\n            .put(MetricField.MIN_EXECUTION_TIME, MetricValueFetcher.SnapshotMinValue)\n            .put(MetricField.MAX_EXECUTION_TIME, MetricValueFetcher.SnapshotMaxValue)\n            .put(MetricField.MEAN_EXECUTION_TIME, MetricValueFetcher.SnapshotMeanValue)\n            .put(MetricField.P25_EXECUTION_TIME, MetricValueFetcher.Snapshot25Percentile)\n            .put(MetricField.P50_EXECUTION_TIME, MetricValueFetcher.Snapshot50PercentileValue)\n            .put(MetricField.P75_EXECUTION_TIME, MetricValueFetcher.Snapshot75PercentileValue)\n            .put(MetricField.P95_EXECUTION_TIME, MetricValueFetcher.Snapshot95PercentileValue)\n            .put(MetricField.P98_EXECUTION_TIME, MetricValueFetcher.Snapshot98PercentileValue)\n            .put(MetricField.P99_EXECUTION_TIME, MetricValueFetcher.Snapshot99PercentileValue)\n            .put(MetricField.P999_EXECUTION_TIME, MetricValueFetcher.Snapshot999PercentileValue)\n            .build();\n        NameFactory nameFactory = NameFactory.createBuilder().timerType(MetricSubType.DEFAULT,\n            valueFetchers)\n            .build();\n        List<String> labelValues = labelValues(valueFetchers);\n        MetricRegistry metricRegistry = reset(nameFactory);\n        String name = nameFactory.timerName(\"GET tt\", MetricSubType.DEFAULT);\n        metricRegistry.timer(name).update(1000, TimeUnit.MILLISECONDS);\n        Enumeration<Collector.MetricFamilySamples> samples = CollectorRegistry.defaultRegistry.filteredMetricFamilySamples(Collections.emptySet());\n        int size = 0;\n        while (samples.hasMoreElements()) {\n            size++;\n            Collector.MetricFamilySamples s = samples.nextElement();\n            for (Collector.MetricFamilySamples.Sample sample : s.samples) {\n                assertTrue(sample.labelNames.contains(MetricRegistryService.METRIC_TYPE_LABEL_NAME));\n                assertTrue(sample.labelNames.contains(MetricRegistryService.METRIC_SUB_TYPE_LABEL_NAME));\n                assertTrue(sample.labelValues.contains(MetricType.TimerType.name()));\n                assertTrue(sample.labelValues.contains(MetricSubType.DEFAULT.name()));\n                assertTrue(sample.name.contains(\"testCategory_\"));\n                assertTrue(sample.name.contains(typeMatch));\n\n\n//                assertTrue(sample.labelNames.contains(EaseAgentPrometheusExports.VALUE_TYPE_LABEL_NAME));\n                assertTrue(endWithOnce(sample.name, labelValues));\n                assertEquals(1000, (int) sample.value);\n            }\n        }\n        assertEquals(10, size);\n    }\n\n    public static boolean endWithOnce(String name, List<String> ends) {\n        Iterator<String> iterator = ends.iterator();\n        while (iterator.hasNext()) {\n            String end = iterator.next();\n            if (name.endsWith(end)) {\n                iterator.remove();\n                return true;\n            }\n        }\n        return false;\n    }\n\n    private List<String> labelValues(Map<MetricField, MetricValueFetcher> valueFetchers) {\n        List<String> values = new ArrayList<>();\n        for (MetricField metricField : valueFetchers.keySet()) {\n            values.add(metricField.getField());\n        }\n        return values;\n    }\n\n    @Test\n    public void testMeter() {\n        Map<MetricField, MetricValueFetcher> valueFetchers = ImmutableMap.<MetricField, MetricValueFetcher>builder()\n            .put(MetricField.M1_ERROR_RATE, MetricValueFetcher.MeteredM1Rate)\n            .put(MetricField.M5_ERROR_RATE, MetricValueFetcher.MeteredM5Rate)\n            .put(MetricField.M15_ERROR_RATE, MetricValueFetcher.MeteredM15Rate)\n            .put(MetricField.MEAN_RATE, MetricValueFetcher.MeteredMeanRate)\n            .build();\n        NameFactory nameFactory = NameFactory.createBuilder().meterType(MetricSubType.ERROR,\n            valueFetchers)\n            .build();\n        List<String> labelValues = labelValues(valueFetchers);\n        MetricRegistry metricRegistry = reset(nameFactory);\n        String name = nameFactory.meterName(\"GET tt\", MetricSubType.ERROR);\n        metricRegistry.meter(name).mark();\n        Enumeration<Collector.MetricFamilySamples> samples = CollectorRegistry.defaultRegistry.filteredMetricFamilySamples(Collections.emptySet());\n        int size = 0;\n        while (samples.hasMoreElements()) {\n            size++;\n            Collector.MetricFamilySamples s = samples.nextElement();\n            for (Collector.MetricFamilySamples.Sample sample : s.samples) {\n                assertTrue(sample.labelNames.contains(MetricRegistryService.METRIC_TYPE_LABEL_NAME));\n                assertTrue(sample.labelNames.contains(MetricRegistryService.METRIC_SUB_TYPE_LABEL_NAME));\n                assertTrue(sample.labelValues.contains(MetricType.MeterType.name()));\n                assertTrue(sample.labelValues.contains(MetricSubType.ERROR.name()));\n                assertTrue(sample.name.contains(\"testCategory_\"));\n                assertTrue(sample.name.contains(typeMatch));\n\n\n//                assertTrue(sample.labelNames.contains(EaseAgentPrometheusExports.VALUE_TYPE_LABEL_NAME));\n//                assertTrue(labelValues.contains(sample.labelValues.get(0)));\n                assertTrue(endWithOnce(sample.name, labelValues));\n            }\n        }\n        assertEquals(4, size);\n    }\n\n    @Test\n    public void testHistogram() {\n        // Temporarily unsupported\n        // Please use timer to calculate the time of P95, P99, etc\n    }\n\n\n    @Test\n    public void testGauge() {\n        NameFactory nameFactory = NameFactory.createBuilder().gaugeType(MetricSubType.DEFAULT, new HashMap<>()).build();\n        MetricRegistry metricRegistry = reset(nameFactory);\n\n        String gaugeName1 = \"GETtt1\";\n        String gaugeName = nameFactory.gaugeName(gaugeName1, MetricSubType.DEFAULT);\n        String key = \"testGaugeKey1\";\n        String key2 = \"testGaugeKey2\";\n        int value = 101;\n        int value2 = 102;\n\n        metricRegistry.gauge(gaugeName, () -> () -> new TestGaugeModel().put(key, value).put(key2, value2));\n\n        int value3 = 103;\n        String gaugeName2 = \"GETtt2\";\n        gaugeName = nameFactory.gaugeName(gaugeName2, MetricSubType.DEFAULT);\n        metricRegistry.gauge(gaugeName, () -> () -> value3);\n\n        Enumeration<Collector.MetricFamilySamples> samples = CollectorRegistry.defaultRegistry.filteredMetricFamilySamples(Collections.emptySet());\n        while (samples.hasMoreElements()) {\n            Collector.MetricFamilySamples s = samples.nextElement();\n            assertTrue(s.name.contains(typeMatch));\n            for (Collector.MetricFamilySamples.Sample sample : s.samples) {\n                assertTrue(sample.labelNames.contains(MetricRegistryService.METRIC_TYPE_LABEL_NAME));\n                assertTrue(sample.labelNames.contains(MetricRegistryService.METRIC_SUB_TYPE_LABEL_NAME));\n                assertTrue(sample.labelValues.contains(MetricType.GaugeType.name()));\n                assertTrue(sample.labelValues.contains(MetricSubType.DEFAULT.name()));\n                assertTrue(sample.name.contains(\"testCategory_\"));\n                assertTrue(sample.name.contains(typeMatch));\n\n\n//                    assertTrue(sample.labelNames.contains(EaseAgentPrometheusExports.VALUE_TYPE_LABEL_NAME));\n                int sampleIntValue = (int) sample.value;\n                if (sample.name.endsWith(key)) {\n                    assertEquals(value, sampleIntValue);\n                } else if (sample.name.endsWith(key2)) {\n                    assertEquals(value2, sampleIntValue);\n                } else {\n//                        assertTrue(sample.labelNames.contains(EaseAgentPrometheusExports.VALUE_TYPE_LABEL_NAME));\n//                        assertTrue(sample.labelValues.contains(\"value\"));\n                    assertTrue(sample.name.endsWith(\"value\"));\n                    assertEquals(value3, (int) sample.value);\n                }\n            }\n\n        }\n    }\n\n\n    public static class TestGaugeModel implements GaugeMetricModel {\n        private final Map<String, Object> data = new HashMap<>();\n\n        public TestGaugeModel put(String key, Object value) {\n            data.put(key, value);\n            return this;\n        }\n\n        @Override\n        public Map<String, Object> toHashMap() {\n            return data;\n        }\n    }\n}\n"
  },
  {
    "path": "metrics/src/test/java/com/megaease/easeagent/metrics/PrometheusAgentHttpHandlerTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.metrics;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.assertEquals;\n\npublic class PrometheusAgentHttpHandlerTest {\n\n    @Test\n    public void getPath() {\n        assertEquals(\"/prometheus/metrics\", new PrometheusAgentHttpHandler().getPath());\n    }\n}\n"
  },
  {
    "path": "metrics/src/test/java/com/megaease/easeagent/metrics/TestConst.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.metrics;\n\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\n\npublic class TestConst {\n    public static final String SERVICE_KEY_NAME = \"service\";\n    public static final String SERVICE_NAME = \"test-metric-service\";\n    public static final String SERVICE_SYSTEM = \"test-metric-system\";\n    public static final String NAMESPACE = \"testMetric\";\n    public static final String NAMESPACE2 = \"testMetric2\";\n    public static final String INTERVAL_CONFIG = ConfigConst.join(\n        ConfigConst.PLUGIN,\n        ConfigConst.OBSERVABILITY,\n        NAMESPACE,\n        ConfigConst.PluginID.METRIC,\n        \"interval\"\n    );\n}\n"
  },
  {
    "path": "metrics/src/test/java/com/megaease/easeagent/metrics/config/PluginMetricsConfigTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.metrics.config;\n\nimport com.megaease.easeagent.config.PluginConfig;\nimport com.megaease.easeagent.metrics.TestConst;\nimport com.megaease.easeagent.mock.config.MockConfig;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport org.junit.Before;\nimport org.junit.Test;\n\nimport java.util.Collections;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertTrue;\n\npublic class PluginMetricsConfigTest {\n    PluginConfig pluginConfig;\n    PluginMetricsConfig pluginMetricsConfig;\n\n    @Before\n    public void before() {\n        pluginConfig = MockConfig.getPluginConfigManager().getConfig(ConfigConst.OBSERVABILITY, TestConst.NAMESPACE, ConfigConst.PluginID.METRIC);\n        pluginMetricsConfig = new PluginMetricsConfig(pluginConfig);\n    }\n\n    @Test\n    public void isEnabled() {\n        assertTrue(pluginMetricsConfig.isEnabled());\n\n    }\n\n    @Test\n    public void getInterval() {\n        assertEquals(30, pluginMetricsConfig.getInterval());\n    }\n\n    @Test\n    public void getIntervalUnit() {\n        assertEquals(TimeUnit.SECONDS, pluginMetricsConfig.getIntervalUnit());\n\n        PluginConfig pluginConfig2 = MockConfig.getPluginConfigManager().getConfig(ConfigConst.OBSERVABILITY, TestConst.NAMESPACE2, ConfigConst.PluginID.METRIC);\n        PluginMetricsConfig pluginMetricsConfig2 = new PluginMetricsConfig(pluginConfig2);\n        assertEquals(TimeUnit.MILLISECONDS, pluginMetricsConfig2.getIntervalUnit());\n    }\n\n\n    @Test\n    public void setIntervalChangeCallback() {\n        AtomicBoolean doit = new AtomicBoolean(false);\n        assertEquals(30, pluginMetricsConfig.getInterval());\n        pluginMetricsConfig.setIntervalChangeCallback(() -> {\n            doit.set(true);\n        });\n        MockConfig.getCONFIGS().updateConfigs(Collections.singletonMap(TestConst.INTERVAL_CONFIG, \"40\"));\n        try {\n            Thread.sleep(100);\n        } catch (InterruptedException e) {\n            e.printStackTrace();\n        }\n        assertTrue(doit.get());\n        assertEquals(40, pluginMetricsConfig.getInterval());\n        MockConfig.getCONFIGS().updateConfigs(Collections.singletonMap(TestConst.INTERVAL_CONFIG, \"30\"));\n        assertEquals(30, pluginMetricsConfig.getInterval());\n    }\n}\n"
  },
  {
    "path": "metrics/src/test/java/com/megaease/easeagent/metrics/converter/AbstractConverterTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.metrics.converter;\n\nimport com.codahale.metrics.*;\nimport com.codahale.metrics.Timer;\nimport com.megaease.easeagent.metrics.TestConst;\nimport com.megaease.easeagent.metrics.impl.MetricRegistryMock;\nimport com.megaease.easeagent.mock.config.MockConfig;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.api.metric.name.MetricSubType;\nimport com.megaease.easeagent.plugin.api.metric.name.Tags;\nimport org.junit.Test;\n\nimport java.util.*;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.function.Supplier;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertTrue;\n\npublic class AbstractConverterTest {\n    public static String buildMetricName(String name) {\n        return AbstractConverterTest.class.getName() + \"##\" + name;\n    }\n\n    public static class MockAbstractConverter extends AbstractConverter {\n        AtomicInteger keysFromMetricsCount = new AtomicInteger();\n        AtomicInteger writeGaugesCount = new AtomicInteger();\n        AtomicInteger writeCountersCount = new AtomicInteger();\n        AtomicInteger writeHistogramsCount = new AtomicInteger();\n        AtomicInteger writeMetersCount = new AtomicInteger();\n        AtomicInteger writeTimersCount = new AtomicInteger();\n\n        MockAbstractConverter(Supplier<Map<String, Object>> additionalAttributes, Tags tags) {\n            super(additionalAttributes, tags);\n        }\n\n        @Override\n        protected List<String> keysFromMetrics(SortedMap<String, Gauge> gauges,\n                                               SortedMap<String, Counter> counters,\n                                               SortedMap<String, Histogram> histograms,\n                                               SortedMap<String, Meter> meters,\n                                               SortedMap<String, Timer> timers) {\n            keysFromMetricsCount.incrementAndGet();\n            Set<String> set = new HashSet<>();\n            set.addAll(gauges.keySet());\n            set.addAll(counters.keySet());\n            set.addAll(histograms.keySet());\n            set.addAll(meters.keySet());\n            set.addAll(timers.keySet());\n            return new ArrayList<>(set);\n        }\n\n        @Override\n        protected void writeGauges(String key, MetricSubType metricSubType, SortedMap<String, Gauge> gauges, Map<String, Object> output) {\n            writeGaugesCount.incrementAndGet();\n            output.put(\"writeGaugesKey\", 1);\n        }\n\n        @Override\n        protected void writeCounters(String key, MetricSubType metricSubType, SortedMap<String, Counter> counters, Map<String, Object> output) {\n            writeCountersCount.incrementAndGet();\n            output.put(\"writeCountersKey\", 1);\n        }\n\n        @Override\n        protected void writeHistograms(String key, MetricSubType metricSubType, SortedMap<String, Histogram> histograms, Map<String, Object> output) {\n            writeHistogramsCount.incrementAndGet();\n            output.put(\"writeHistogramsKey\", 1);\n        }\n\n        @Override\n        protected void writeMeters(String key, MetricSubType metricSubType, SortedMap<String, Meter> meters, Map<String, Object> output) {\n            writeMetersCount.incrementAndGet();\n            output.put(\"writeMetersKey\", 1);\n        }\n\n        @Override\n        protected void writeTimers(String key, MetricSubType metricSubType, SortedMap<String, Timer> timers, Map<String, Object> output) {\n            writeTimersCount.incrementAndGet();\n            output.put(\"writeTimersKey\", 1);\n        }\n    }\n\n    @Test\n    public void convertMap() {\n        MockAbstractConverter mockAbstractConverter = new MockAbstractConverter(\n            new MetricsAdditionalAttributes(\n                MockConfig.getCONFIGS()),\n            new Tags(\"testCategory\", \"testType\", \"testKeyFieldName\"));\n        List<Map<String, Object>> result = mockAbstractConverter.convertMap(\n            new TreeMap<>(Collections.singletonMap(\"testConvertMap\",\n                MetricRegistryMock.getCodahaleMetricRegistry().gauge(buildMetricName(\"convertMap#Gauge\"), () -> () -> \"gaugeValue\"))\n            ),\n            new TreeMap<>(Collections.singletonMap(\"testConvertMap\",\n                MetricRegistryMock.getCodahaleMetricRegistry().counter(buildMetricName(\"convertMap#Counter\")))\n            ),\n            new TreeMap<>(Collections.singletonMap(\"testConvertMap\",\n                MetricRegistryMock.getCodahaleMetricRegistry().histogram(buildMetricName(\"convertMap#Histogram\")))\n            ),\n            new TreeMap<>(Collections.singletonMap(\"testConvertMap\",\n                MetricRegistryMock.getCodahaleMetricRegistry().meter(buildMetricName(\"convertMap#Meter\")))\n            ),\n            new TreeMap<>(Collections.singletonMap(\"testConvertMap\",\n                MetricRegistryMock.getCodahaleMetricRegistry().timer(buildMetricName(\"convertMap#Timer\")))\n            )\n        );\n        assertEquals(1, mockAbstractConverter.keysFromMetricsCount.get());\n        assertEquals(1, mockAbstractConverter.writeGaugesCount.get());\n        assertEquals(1, mockAbstractConverter.writeCountersCount.get());\n        assertEquals(1, mockAbstractConverter.writeHistogramsCount.get());\n        assertEquals(1, mockAbstractConverter.writeMetersCount.get());\n        assertEquals(1, mockAbstractConverter.writeTimersCount.get());\n        assertEquals(1, result.size());\n        Map<String, Object> data = result.get(0);\n        assertEquals(\"testCategory\", data.get(Tags.CATEGORY));\n        assertEquals(\"testType\", data.get(Tags.TYPE));\n        assertEquals(\"testConvertMap\", data.get(\"testKeyFieldName\"));\n        assertTrue(data.containsKey(\"timestamp\"));\n        assertEquals(TestConst.SERVICE_NAME, data.get(TestConst.SERVICE_KEY_NAME));\n        assertEquals(TestConst.SERVICE_SYSTEM, data.get(ConfigConst.SYSTEM_NAME));\n        assertEquals(1, data.get(\"writeGaugesKey\"));\n        assertEquals(1, data.get(\"writeCountersKey\"));\n        assertEquals(1, data.get(\"writeHistogramsKey\"));\n        assertEquals(1, data.get(\"writeMetersKey\"));\n        assertEquals(1, data.get(\"writeTimersKey\"));\n\n    }\n\n    @Test\n    public void writeGauges() {\n        convertMap();\n    }\n\n    @Test\n    public void writeCounters() {\n        convertMap();\n    }\n\n    @Test\n    public void writeHistograms() {\n        convertMap();\n    }\n\n    @Test\n    public void writeMeters() {\n        convertMap();\n    }\n\n    @Test\n    public void writeTimers() {\n        convertMap();\n    }\n\n}\n"
  },
  {
    "path": "metrics/src/test/java/com/megaease/easeagent/metrics/converter/ConverterAdapterTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.metrics.converter;\n\nimport com.codahale.metrics.Counter;\nimport com.codahale.metrics.Gauge;\nimport com.codahale.metrics.Histogram;\nimport com.codahale.metrics.Meter;\nimport com.codahale.metrics.Timer;\nimport com.megaease.easeagent.metrics.impl.MetricRegistryMock;\nimport com.megaease.easeagent.metrics.impl.MetricTestUtils;\nimport com.megaease.easeagent.mock.config.MockConfig;\nimport com.megaease.easeagent.plugin.api.metric.name.*;\nimport com.megaease.easeagent.plugin.utils.ImmutableMap;\nimport org.junit.Test;\n\nimport java.util.*;\nimport java.util.concurrent.TimeUnit;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotNull;\nimport static org.junit.Assert.assertTrue;\n\npublic class ConverterAdapterTest {\n    NameFactory nameFactory = NameFactory.createBuilder()\n        .counterType(MetricSubType.DEFAULT, ImmutableMap.<MetricField, MetricValueFetcher>builder()\n            .put(MetricField.EXECUTION_COUNT, MetricValueFetcher.CountingCount)\n            .build())\n        .meterType(MetricSubType.DEFAULT, ImmutableMap.<MetricField, MetricValueFetcher>builder()\n            .put(MetricField.M1_RATE, MetricValueFetcher.MeteredM1RateIgnoreZero)\n            .put(MetricField.M5_RATE, MetricValueFetcher.MeteredM5Rate)\n            .put(MetricField.M15_RATE, MetricValueFetcher.MeteredM15Rate)\n            .put(MetricField.MEAN_RATE, MetricValueFetcher.MeteredMeanRate)\n            .build())\n        .histogramType(MetricSubType.DEFAULT, ImmutableMap.<MetricField, MetricValueFetcher>builder()\n            .put(MetricField.MIN_EXECUTION_TIME, MetricValueFetcher.SnapshotMinValue)\n            .put(MetricField.MAX_EXECUTION_TIME, MetricValueFetcher.SnapshotMaxValue)\n            .put(MetricField.MEAN_EXECUTION_TIME, MetricValueFetcher.SnapshotMeanValue)\n            .put(MetricField.P25_EXECUTION_TIME, MetricValueFetcher.Snapshot25Percentile)\n            .put(MetricField.P50_EXECUTION_TIME, MetricValueFetcher.Snapshot50PercentileValue)\n            .put(MetricField.P75_EXECUTION_TIME, MetricValueFetcher.Snapshot75PercentileValue)\n            .put(MetricField.P95_EXECUTION_TIME, MetricValueFetcher.Snapshot95PercentileValue)\n            .put(MetricField.P98_EXECUTION_TIME, MetricValueFetcher.Snapshot98PercentileValue)\n            .put(MetricField.P99_EXECUTION_TIME, MetricValueFetcher.Snapshot99PercentileValue)\n            .put(MetricField.P999_EXECUTION_TIME, MetricValueFetcher.Snapshot999PercentileValue)\n            .build())\n        .gaugeType(MetricSubType.DEFAULT, new HashMap<>())\n        .timerType(MetricSubType.DEFAULT,\n            ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                .put(MetricField.MIN_EXECUTION_TIME, MetricValueFetcher.SnapshotMinValue)\n                .put(MetricField.MAX_EXECUTION_TIME, MetricValueFetcher.SnapshotMaxValue)\n                .put(MetricField.MEAN_EXECUTION_TIME, MetricValueFetcher.SnapshotMeanValue)\n                .put(MetricField.P25_EXECUTION_TIME, MetricValueFetcher.Snapshot25Percentile)\n                .put(MetricField.P50_EXECUTION_TIME, MetricValueFetcher.Snapshot50PercentileValue)\n                .put(MetricField.P75_EXECUTION_TIME, MetricValueFetcher.Snapshot75PercentileValue)\n                .put(MetricField.P95_EXECUTION_TIME, MetricValueFetcher.Snapshot95PercentileValue)\n                .put(MetricField.P98_EXECUTION_TIME, MetricValueFetcher.Snapshot98PercentileValue)\n                .put(MetricField.P99_EXECUTION_TIME, MetricValueFetcher.Snapshot99PercentileValue)\n                .put(MetricField.P999_EXECUTION_TIME, MetricValueFetcher.Snapshot999PercentileValue)\n                .build())\n        .build();\n\n    public static String buildMetricName(String name) {\n        return ConverterAdapterTest.class.getName() + \"##\" + name;\n    }\n\n    class KeysFromMetricsRun {\n        ConverterAdapter converter;\n        SortedMap<String, Gauge> gauges;\n        SortedMap<String, Counter> counters;\n        SortedMap<String, Histogram> histograms;\n        SortedMap<String, Meter> meters;\n        SortedMap<String, Timer> timer;\n\n        public KeysFromMetricsRun setConverter(ConverterAdapter converter) {\n            this.converter = converter;\n            return this;\n        }\n\n        public KeysFromMetricsRun setGauges(SortedMap<String, Gauge> gauges) {\n            this.gauges = gauges;\n            return this;\n        }\n\n        public KeysFromMetricsRun setCounters(SortedMap<String, Counter> counters) {\n            this.counters = counters;\n            return this;\n        }\n\n        public KeysFromMetricsRun setHistograms(SortedMap<String, Histogram> histograms) {\n            this.histograms = histograms;\n            return this;\n        }\n\n        public KeysFromMetricsRun setMeters(SortedMap<String, Meter> meters) {\n            this.meters = meters;\n            return this;\n        }\n\n        public KeysFromMetricsRun setTimer(SortedMap<String, Timer> timer) {\n            this.timer = timer;\n            return this;\n        }\n\n        public List<String> doit() {\n            return converter.keysFromMetrics(gauges, counters, histograms, meters, timer);\n        }\n    }\n\n    private ConverterAdapter createAllTypeConverterAdapter() {\n        return new ConverterAdapter(\n            nameFactory,\n            Arrays.asList(KeyType.values()),\n            new MetricsAdditionalAttributes(\n                MockConfig.getCONFIGS()),\n            new Tags(\"testCategory\", \"testType\", \"testKeyFieldName\"));\n    }\n\n    @Test\n    public void consumerMetric() {\n        Map<MetricSubType, String> map = new HashMap<>();\n        for (MetricSubType metricSubType : MetricSubType.values()) {\n            map.put(metricSubType, metricSubType.getCode());\n        }\n        List<String> result = new ArrayList<>();\n        ConverterAdapter.consumerMetric(map, null, result::add);\n        assertEquals(map.size(), result.size());\n        for (String s : map.values()) {\n            assertTrue(result.contains(s));\n        }\n        result.clear();\n        ConverterAdapter.consumerMetric(map, MetricSubType.ERROR, result::add);\n        assertEquals(1, result.size());\n        assertEquals(MetricSubType.ERROR.getCode(), result.get(0));\n    }\n\n    @Test\n    public void keysFromMetrics() {\n\n        ConverterAdapter converter = createAllTypeConverterAdapter();\n\n        String key = buildMetricName(\"testConvertMap\");\n        String key2 = buildMetricName(\"testConvertMap2\");\n        String gaugeName = nameFactory.gaugeName(key, MetricSubType.DEFAULT);\n        String counterName = nameFactory.counterName(key, MetricSubType.DEFAULT);\n        String histogramName = nameFactory.histogramName(key, MetricSubType.DEFAULT);\n        String meterName = nameFactory.meterName(key, MetricSubType.DEFAULT);\n        String timerName = nameFactory.timerName(key, MetricSubType.DEFAULT);\n        String timerName2 = nameFactory.timerName(key2, MetricSubType.DEFAULT);\n        KeysFromMetricsRun keysFromMetricsRun = new KeysFromMetricsRun()\n            .setConverter(converter)\n            .setGauges(new TreeMap<>(Collections.singletonMap(gaugeName,\n                MetricRegistryMock.getCodahaleMetricRegistry().gauge(gaugeName, () -> () -> \"gaugeValue\"))\n            ))\n            .setCounters(new TreeMap<>(Collections.singletonMap(counterName,\n                MetricRegistryMock.getCodahaleMetricRegistry().counter(counterName))\n            ))\n            .setHistograms(new TreeMap<>(Collections.singletonMap(histogramName,\n                MetricRegistryMock.getCodahaleMetricRegistry().histogram(histogramName))\n            ))\n            .setMeters(new TreeMap<>(Collections.singletonMap(meterName,\n                MetricRegistryMock.getCodahaleMetricRegistry().meter(meterName))\n            ))\n            .setTimer(new TreeMap<>(Collections.singletonMap(timerName,\n                MetricRegistryMock.getCodahaleMetricRegistry().timer(timerName))\n            ));\n\n        List<String> result = keysFromMetricsRun.doit();\n        assertEquals(1, result.size());\n        assertEquals(key, result.get(0));\n\n        SortedMap<String, Timer> timerMap = new TreeMap<>();\n        timerMap.put(timerName, MetricRegistryMock.getCodahaleMetricRegistry().timer(timerName));\n        timerMap.put(timerName2, MetricRegistryMock.getCodahaleMetricRegistry().timer(timerName2));\n        keysFromMetricsRun.setTimer(timerMap);\n\n        List<String> result2 = keysFromMetricsRun.doit();\n        assertEquals(2, result2.size());\n        assertTrue(result2.contains(key));\n        assertTrue(result2.contains(key2));\n\n        keysFromMetricsRun.setConverter(new ConverterAdapter(\n            nameFactory,\n            Arrays.asList(KeyType.Counter),\n            new MetricsAdditionalAttributes(\n                MockConfig.getCONFIGS()),\n            new Tags(\"testCategory\", \"testType\", \"testKeyFieldName\")));\n\n\n        List<String> result3 = keysFromMetricsRun.doit();\n        assertEquals(1, result3.size());\n        assertEquals(key, result3.get(0));\n\n    }\n\n    @Test\n    public void writeGauges() {\n        ConverterAdapter converter = createAllTypeConverterAdapter();\n\n        String key = buildMetricName(\"writeGauges\");\n        String gaugeName = nameFactory.gaugeName(key, MetricSubType.DEFAULT);\n        String value = \"gaugeValue\";\n        SortedMap<String, Gauge> gauges = new TreeMap<>(Collections.singletonMap(gaugeName,\n            MetricRegistryMock.getCodahaleMetricRegistry().gauge(gaugeName, () -> () -> value))\n        );\n        Map<String, Object> result = new HashMap<>();\n        converter.writeGauges(key, null, gauges, result);\n        assertEquals(value, result.get(\"value\"));\n    }\n\n    @Test\n    public void writeCounters() {\n        ConverterAdapter converter = createAllTypeConverterAdapter();\n\n        String key = buildMetricName(\"writeCounters\");\n        String counterName = nameFactory.counterName(key, MetricSubType.DEFAULT);\n        Counter counter = MetricRegistryMock.getCodahaleMetricRegistry().counter(counterName);\n        SortedMap<String, Counter> counters = new TreeMap<>(Collections.singletonMap(counterName,\n            counter)\n        );\n        counter.inc();\n        Map<String, Object> result = new HashMap<>();\n        converter.writeCounters(key, null, counters, result);\n        assertEquals(1l, result.get(MetricField.EXECUTION_COUNT.getField()));\n        counter.inc();\n\n        converter.writeCounters(key, null, counters, result);\n        assertEquals(2l, result.get(MetricField.EXECUTION_COUNT.getField()));\n    }\n\n    @Test\n    public void writeHistograms() {\n        ConverterAdapter converter = createAllTypeConverterAdapter();\n\n        String key = buildMetricName(\"writeHistograms\");\n        String histogramName = nameFactory.histogramName(key, MetricSubType.DEFAULT);\n        Histogram histogram = MetricRegistryMock.getCodahaleMetricRegistry().histogram(histogramName);\n        for (int i = 0; i < 100; i++) {\n            histogram.update(i);\n        }\n        SortedMap<String, Histogram> histograms = new TreeMap<>(Collections.singletonMap(histogramName,\n            histogram)\n        );\n        Map<String, Object> result = new HashMap<>();\n        converter.writeHistograms(key, null, histograms, result);\n        assertTrue(result.isEmpty());\n    }\n\n    @Test\n    public void writeMeters() {\n        ConverterAdapter converter = createAllTypeConverterAdapter();\n\n        String key = buildMetricName(\"writeMeters\");\n        String meterName = nameFactory.meterName(key, MetricSubType.DEFAULT);\n        Meter meter = MetricRegistryMock.getCodahaleMetricRegistry().meter(meterName);\n        int count = 0;\n        for (int i = 0; i < 15 * 60 / 5; i++) {\n            meter.mark();\n            count++;\n            assertEquals(count, meter.getCount());\n            meter.mark(10 + i);\n            count += 10 + i;\n            assertEquals(count, meter.getCount());\n            MetricTestUtils.nextWindow(meter);\n        }\n\n        SortedMap<String, Meter> meters = new TreeMap<>(Collections.singletonMap(meterName,\n            meter)\n        );\n        Map<String, Object> result = new HashMap<>();\n        converter.writeMeters(key, null, meters, result);\n        Object meanRateO = result.get(MetricField.MEAN_RATE.getField());\n        Object m1O = result.get(MetricField.M1_RATE.getField());\n        Object m5O = result.get(MetricField.M5_RATE.getField());\n        Object m15O = result.get(MetricField.M15_RATE.getField());\n        assertTrue(meanRateO instanceof Double);\n        assertTrue(m1O instanceof Double);\n        assertTrue(m5O instanceof Double);\n        assertTrue(m15O instanceof Double);\n        double meanRate = (double) meanRateO;\n        double m1 = (double) m1O;\n        double m5 = (double) m5O;\n        double m15 = (double) m15O;\n\n        assertTrue(String.format(\"meter.getMeanRate()<%s> must > 0\", meanRate), meanRate > 0);\n        assertTrue(String.format(\"meter.getOneMinuteRate()<%s> must > 0\", m1), m1 > 0);\n        assertTrue(String.format(\"meter.getFiveMinuteRate()<%s> must > 0\", m5), m5 > 0);\n        assertTrue(String.format(\"meter.getFifteenMinuteRate()<%s> must > 0\", m15), m15 > 0);\n        assertTrue(meanRate > m1);\n        assertTrue(m1 > m5);\n        assertTrue(m5 > m15);\n\n    }\n\n    @Test\n    public void writeTimers() {\n        ConverterAdapter converter = createAllTypeConverterAdapter();\n\n        String key = buildMetricName(\"writeTimers\");\n        String timerName = nameFactory.timerName(key, MetricSubType.DEFAULT);\n        Timer timer = MetricRegistryMock.getCodahaleMetricRegistry().timer(timerName);\n        for (int i = 0; i < 100; i++) {\n            timer.update(i, TimeUnit.MILLISECONDS);\n        }\n        SortedMap<String, Timer> timers = new TreeMap<>(Collections.singletonMap(timerName,\n            timer)\n        );\n        Map<String, Object> result = new HashMap<>();\n        converter.writeTimers(key, null, timers, result);\n\n        assertEquals(0d, (double) result.get(MetricField.MIN_EXECUTION_TIME.getField()), 1);\n        assertEquals(49.5d, (double) result.get(MetricField.MEAN_EXECUTION_TIME.getField()), 1);\n        assertEquals(99d, (double) result.get(MetricField.MAX_EXECUTION_TIME.getField()), 1);\n\n        assertEquals(24d, (double) result.get(MetricField.P25_EXECUTION_TIME.getField()), 1);\n        assertEquals(49d, (double) result.get(MetricField.P50_EXECUTION_TIME.getField()), 1);\n        assertEquals(74d, (double) result.get(MetricField.P75_EXECUTION_TIME.getField()), 1);\n        assertEquals(94d, (double) result.get(MetricField.P95_EXECUTION_TIME.getField()), 1);\n        assertEquals(97d, (double) result.get(MetricField.P98_EXECUTION_TIME.getField()), 1);\n        assertEquals(98d, (double) result.get(MetricField.P99_EXECUTION_TIME.getField()), 1);\n        assertEquals(99d, (double) result.get(MetricField.P999_EXECUTION_TIME.getField()), 1);\n\n    }\n}\n"
  },
  {
    "path": "metrics/src/test/java/com/megaease/easeagent/metrics/converter/IgnoreOutputExceptionTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.metrics.converter;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\npublic class IgnoreOutputExceptionTest {\n    @Test(expected = IgnoreOutputException.class)\n    public void constructor() {\n        throw new IgnoreOutputException(\"test\");\n    }\n}\n"
  },
  {
    "path": "metrics/src/test/java/com/megaease/easeagent/metrics/converter/MetricsAdditionalAttributesTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.metrics.converter;\n\nimport com.megaease.easeagent.metrics.TestConst;\nimport com.megaease.easeagent.mock.config.MockConfig;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport org.junit.Test;\n\nimport java.util.Collections;\n\nimport static org.junit.Assert.*;\n\npublic class MetricsAdditionalAttributesTest {\n\n    @Test\n    public void get() {\n        MetricsAdditionalAttributes metricsAdditionalAttributes = new MetricsAdditionalAttributes(MockConfig.getCONFIGS());\n        assertEquals(TestConst.SERVICE_NAME, metricsAdditionalAttributes.get().get(TestConst.SERVICE_KEY_NAME));\n        assertEquals(TestConst.SERVICE_SYSTEM, metricsAdditionalAttributes.get().get(ConfigConst.SYSTEM_NAME));\n        String newServiceName = \"newMetricServiceName\";\n        String newSystemName = \"newMetricSystemName\";\n        MockConfig.getCONFIGS().updateConfigs(Collections.singletonMap(ConfigConst.SERVICE_NAME, newServiceName));\n        MockConfig.getCONFIGS().updateConfigs(Collections.singletonMap(ConfigConst.SYSTEM_NAME, newSystemName));\n        assertEquals(newServiceName, metricsAdditionalAttributes.get().get(TestConst.SERVICE_KEY_NAME));\n        assertEquals(newSystemName, metricsAdditionalAttributes.get().get(ConfigConst.SYSTEM_NAME));\n\n        MockConfig.getCONFIGS().updateConfigs(Collections.singletonMap(ConfigConst.SERVICE_NAME, TestConst.SERVICE_NAME));\n        MockConfig.getCONFIGS().updateConfigs(Collections.singletonMap(ConfigConst.SYSTEM_NAME, TestConst.SERVICE_SYSTEM));\n\n        assertEquals(TestConst.SERVICE_NAME, metricsAdditionalAttributes.get().get(TestConst.SERVICE_KEY_NAME));\n        assertEquals(TestConst.SERVICE_SYSTEM, metricsAdditionalAttributes.get().get(ConfigConst.SYSTEM_NAME));\n\n    }\n}\n"
  },
  {
    "path": "metrics/src/test/java/com/megaease/easeagent/metrics/impl/CounterImplTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.metrics.impl;\n\nimport com.megaease.easeagent.plugin.api.metric.Counter;\nimport com.megaease.easeagent.plugin.bridge.NoOpMetrics;\nimport org.junit.Test;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertTrue;\n\npublic class CounterImplTest {\n\n\n    public static String buildMetricName(String name) {\n        return CounterImplTest.class.getName() + \"##\" + name;\n    }\n\n    public static Counter buildCounter(String name) {\n        com.codahale.metrics.Counter unwrap = MetricRegistryMock.getCodahaleMetricRegistry().counter(buildMetricName(name));\n        return CounterImpl.build(unwrap);\n    }\n\n    @Test\n    public void build() {\n        Counter counter = CounterImpl.build(null);\n        assertTrue(counter instanceof NoOpMetrics.NoopCounter);\n        counter = buildCounter(\"build\");\n        assertTrue(counter instanceof CounterImpl);\n    }\n\n    @Test\n    public void inc() {\n        test(\"inc\");\n    }\n\n    private void test(String name) {\n        Counter counter = buildCounter(name);\n        counter.inc();\n        assertEquals(1, counter.getCount());\n        counter.inc();\n        assertEquals(2, counter.getCount());\n        counter.inc(10);\n        assertEquals(12, counter.getCount());\n        counter.dec();\n        assertEquals(11, counter.getCount());\n        counter.dec();\n        assertEquals(10, counter.getCount());\n        counter.dec(5);\n        assertEquals(5, counter.getCount());\n\n        assertTrue(counter.unwrap() instanceof com.codahale.metrics.Counter);\n    }\n\n    @Test\n    public void inc1() {\n        test(\"inc1\");\n    }\n\n    @Test\n    public void dec() {\n        test(\"dec\");\n    }\n\n    @Test\n    public void dec1() {\n        test(\"dec1\");\n    }\n\n    @Test\n    public void getCount() {\n        test(\"getCount\");\n    }\n}\n"
  },
  {
    "path": "metrics/src/test/java/com/megaease/easeagent/metrics/impl/GaugeImplTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.metrics.impl;\n\nimport com.megaease.easeagent.plugin.api.metric.Gauge;\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\npublic class GaugeImplTest {\n\n    @Test\n    public void getG() {\n        String name = \"getG\";\n        Gauge gauge = () -> name;\n        GaugeImpl gauge1 = new GaugeImpl(gauge);\n        assertEquals(gauge, gauge1.getG());\n        try {\n            new GaugeImpl(null);\n            assertTrue(\"must be error\", false);\n        } catch (Exception e) {\n            assertNotNull(e);\n        }\n    }\n\n    @Test\n    public void getValue() {\n        String name = \"getG\";\n        Gauge gauge = () -> name;\n        GaugeImpl gauge1 = new GaugeImpl(gauge);\n        assertEquals(gauge, gauge1.getG());\n        assertEquals(name, gauge1.getValue());\n    }\n}\n"
  },
  {
    "path": "metrics/src/test/java/com/megaease/easeagent/metrics/impl/HistogramImplTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.metrics.impl;\n\nimport com.megaease.easeagent.plugin.api.metric.Histogram;\nimport com.megaease.easeagent.plugin.api.metric.Snapshot;\nimport com.megaease.easeagent.plugin.bridge.NoOpMetrics;\nimport org.junit.Test;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertTrue;\n\npublic class HistogramImplTest {\n\n    public static String buildMetricName(String name) {\n        return HistogramImplTest.class.getName() + \"##\" + name;\n    }\n\n    public static Histogram buildhistogram(String name) {\n        com.codahale.metrics.Histogram unwrap = MetricRegistryMock.getCodahaleMetricRegistry().histogram(buildMetricName(name));\n        return HistogramImpl.build(unwrap);\n    }\n\n    @Test\n    public void build() {\n        Histogram histogram = HistogramImpl.build(null);\n        assertTrue(histogram instanceof NoOpMetrics.NoopHistogram);\n        histogram = buildhistogram(\"build\");\n        assertTrue(histogram instanceof HistogramImpl);\n    }\n\n    @Test\n    public void update() {\n        test(\"update\");\n    }\n\n    private void test(String name) {\n        Histogram histogram = buildhistogram(name);\n        histogram.update(1);\n        histogram.update(1);\n        assertEquals(2, histogram.getCount());\n        long value = 123456789019l;\n        histogram.update(value);\n        assertEquals(3, histogram.getCount());\n        Snapshot snapshot = histogram.getSnapshot();\n        assertEquals(3, snapshot.size());\n        histogram.update(1);\n        assertEquals(4, histogram.getCount());\n        assertEquals(3, snapshot.size());\n        assertTrue(histogram.unwrap() instanceof com.codahale.metrics.Histogram);\n    }\n\n    @Test\n    public void update1() {\n        test(\"update1\");\n    }\n\n    @Test\n    public void getCount() {\n        test(\"getCount\");\n    }\n\n    @Test\n    public void getSnapshot() {\n        test(\"getSnapshot\");\n    }\n}\n"
  },
  {
    "path": "metrics/src/test/java/com/megaease/easeagent/metrics/impl/MeterImplTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.metrics.impl;\n\nimport com.megaease.easeagent.plugin.api.metric.Meter;\nimport com.megaease.easeagent.plugin.bridge.NoOpMetrics;\nimport com.megaease.easeagent.plugin.utils.Pair;\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\npublic class MeterImplTest {\n\n    public static String buildMetricName(String name) {\n        return MeterImplTest.class.getName() + \"##\" + name;\n    }\n\n    public static Pair<Meter, com.codahale.metrics.Meter> buildMeter(String name) {\n        com.codahale.metrics.Meter unwrap = MetricRegistryMock.getCodahaleMetricRegistry().meter(buildMetricName(name));\n        return new Pair<>(MeterImpl.build(unwrap), unwrap);\n    }\n\n    @Test\n    public void build() {\n        Meter meter = MeterImpl.build(null);\n        assertTrue(meter instanceof NoOpMetrics.NoopMeter);\n        Pair<Meter, com.codahale.metrics.Meter> pair = buildMeter(\"build\");\n        assertTrue(pair.getKey() instanceof MeterImpl);\n    }\n\n\n    private void test(String name) {\n        Pair<Meter, com.codahale.metrics.Meter> pair = buildMeter(name);\n        Meter meter = pair.getKey();\n        int count = 0;\n        for (int i = 0; i < 15 * 60 / 5; i++) {\n            meter.mark();\n            count++;\n            assertEquals(count, meter.getCount());\n            meter.mark(10 + i);\n            count += 10 + i;\n            assertEquals(count, meter.getCount());\n            MetricTestUtils.nextWindow(pair.getValue());\n        }\n        double meanRate = meter.getMeanRate();\n        double oneMeanRate = meter.getOneMinuteRate();\n        double fiveMeanRate = meter.getFiveMinuteRate();\n        double fifteenMeanRate = meter.getFifteenMinuteRate();\n        assertTrue(String.format(\"meter.getMeanRate()<%s> must > 0\", meanRate), meanRate > 0);\n        assertTrue(String.format(\"meter.getOneMinuteRate()<%s> must > 0\", oneMeanRate), oneMeanRate > 0);\n        assertTrue(String.format(\"meter.getFiveMinuteRate()<%s> must > 0\", fiveMeanRate), fiveMeanRate > 0);\n        assertTrue(String.format(\"meter.getFifteenMinuteRate()<%s> must > 0\", fifteenMeanRate), fifteenMeanRate > 0);\n        assertTrue(meanRate > oneMeanRate);\n        assertTrue(oneMeanRate > fiveMeanRate);\n        assertTrue(fiveMeanRate > fifteenMeanRate);\n\n        assertTrue(meter.unwrap() instanceof com.codahale.metrics.Meter);\n    }\n\n    @Test\n    public void mark() {\n        test(\"mark\");\n    }\n\n    @Test\n    public void mark1() {\n        test(\"mark1\");\n    }\n\n    @Test\n    public void getCount() {\n        test(\"getCount\");\n    }\n\n    @Test\n    public void getFifteenMinuteRate() {\n        test(\"getFifteenMinuteRate\");\n    }\n\n    @Test\n    public void getFiveMinuteRate() {\n        test(\"getFiveMinuteRate\");\n    }\n\n    @Test\n    public void getMeanRate() {\n        test(\"getMeanRate\");\n    }\n\n\n    @Test\n    public void getOneMinuteRate() {\n        test(\"getOneMinuteRate\");\n    }\n}\n"
  },
  {
    "path": "metrics/src/test/java/com/megaease/easeagent/metrics/impl/MetricInstanceTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.metrics.impl;\n\nimport com.megaease.easeagent.plugin.api.metric.*;\nimport com.megaease.easeagent.plugin.bridge.NoOpMetrics;\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\npublic class MetricInstanceTest {\n\n    @Test\n    public void to() {\n        Counter counter = CounterImpl.build(null);\n        String testErrorName = \"testErrorName\";\n        counter = MetricInstance.COUNTER.to(testErrorName, counter);\n        assertNotNull(counter);\n\n        Histogram histogram = MetricInstance.HISTOGRAM.to(testErrorName, HistogramImpl.build(null));\n        assertNotNull(histogram);\n        try {\n            MetricInstance.HISTOGRAM.to(testErrorName, counter);\n            assertTrue(\"must be error\", false);\n        } catch (Exception e) {\n            assertTrue(e instanceof IllegalArgumentException);\n        }\n\n        Meter meter = MetricInstance.METER.to(testErrorName, MeterImpl.build(null));\n        assertNotNull(meter);\n        try {\n            MetricInstance.METER.to(testErrorName, counter);\n            assertTrue(\"must be error\", false);\n        } catch (Exception e) {\n            assertTrue(e instanceof IllegalArgumentException);\n        }\n\n        Timer timer = MetricInstance.TIMER.to(testErrorName, TimerImpl.build(null));\n        assertNotNull(timer);\n        try {\n            MetricInstance.TIMER.to(testErrorName, counter);\n            assertTrue(\"must be error\", false);\n        } catch (Exception e) {\n            assertTrue(e instanceof IllegalArgumentException);\n        }\n\n        Gauge gauge = MetricInstance.GAUGE.to(testErrorName, NoOpMetrics.NO_OP_GAUGE);\n        assertNotNull(gauge);\n\n        try {\n            MetricInstance.GAUGE.to(testErrorName, counter);\n            assertTrue(\"must be error\", false);\n        } catch (Exception e) {\n            assertTrue(e instanceof IllegalArgumentException);\n        }\n\n        try {\n            MetricInstance.COUNTER.to(testErrorName, gauge);\n            assertTrue(\"must be error\", false);\n        } catch (Exception e) {\n            assertTrue(e instanceof IllegalArgumentException);\n        }\n    }\n\n    @Test\n    public void toInstance() {\n        to();\n    }\n}\n"
  },
  {
    "path": "metrics/src/test/java/com/megaease/easeagent/metrics/impl/MetricRegistryImplTest.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.metrics.impl;\n\nimport com.megaease.easeagent.metrics.MetricRegistryService;\nimport com.megaease.easeagent.plugin.api.metric.*;\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport java.lang.reflect.ParameterizedType;\nimport java.lang.reflect.Type;\nimport java.util.Map;\nimport java.util.concurrent.TimeUnit;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotNull;\nimport static org.junit.Assert.assertTrue;\n\npublic class MetricRegistryImplTest {\n    String countName = buildMetricName(\"countName\");\n    String meterName = buildMetricName(\"meterName\");\n    String histogramName = buildMetricName(\"histogramName\");\n    String timerName = buildMetricName(\"timerName\");\n    String gaugeName = buildMetricName(\"gauge\");\n    com.codahale.metrics.MetricRegistry mr = MetricRegistryService.DEFAULT.createMetricRegistry(null, null, null);\n    MetricRegistry metricRegistry = MetricRegistryImpl.build(mr);\n\n\n    public static String buildMetricName(String name) {\n        return MetricRegistryImplTest.class.getName() + \"##\" + name;\n    }\n\n    @Test\n    public void build() {\n        Assert.assertNotNull(metricRegistry);\n    }\n\n    @Test\n    public void meter() {\n        Meter meter = metricRegistry.meter(meterName);\n        meter.mark();\n        assertEquals(1, meter.getCount());\n        System.out.println(meter.getFifteenMinuteRate() + \"\");\n    }\n\n    @Test\n    public void remove() {\n        String name = buildMetricName(\"test_remove\");\n        String value1 = \"test_remove_value1\";\n        String value2 = \"test_remove_value2\";\n        Gauge gauge = metricRegistry.gauge(name, () -> new TestGauge(value1));\n        com.codahale.metrics.Gauge g = mr.gauge(name, () -> null);\n        assertNotNull(gauge);\n        assertNotNull(g);\n        assertEquals(value1, gauge.getValue());\n        metricRegistry.remove(name);\n        gauge = metricRegistry.gauge(name, () -> new TestGauge(value2));\n        assertEquals(value2, gauge.getValue());\n    }\n\n    @Test\n    public void getMetrics() {\n        com.codahale.metrics.MetricRegistry mr = MetricRegistryService.DEFAULT.createMetricRegistry(null, null, null);\n        MetricRegistry metricRegistry = MetricRegistryImpl.build(mr);\n        String gaugeName = buildMetricName(\"getMetrics_gaugeName\");\n        String value1 = \"test_remove_value1\";\n        Gauge gauge = metricRegistry.gauge(gaugeName, () -> new TestGauge(value1));\n        assertEquals(value1, gauge.getValue());\n        try {\n            Counter counter = metricRegistry.counter(gaugeName);\n            assertTrue(\"must throw error\", false);\n        } catch (Exception e) {\n            assertNotNull(e);\n        }\n        String countName = buildMetricName(\"getMetrics_countName\");\n        Counter counter = metricRegistry.counter(countName);\n        counter.inc();\n        String meterName = buildMetricName(\"getMetrics_meterName\");\n        Meter meter = metricRegistry.meter(meterName);\n        meter.mark();\n        String histogramName = buildMetricName(\"getMetrics_histogramName\");\n        Histogram histogram = metricRegistry.histogram(histogramName);\n        histogram.update(10);\n        String timerName = buildMetricName(\"getMetrics_timerName\");\n        Timer timer = metricRegistry.timer(timerName);\n        timer.update(1, TimeUnit.MILLISECONDS);\n\n        Map<String, Metric> metricMap = metricRegistry.getMetrics();\n        assertEquals(5, metricMap.size());\n        assertEquals(gauge, metricMap.get(gaugeName));\n        assertEquals(counter, metricMap.get(countName));\n        assertEquals(meter, metricMap.get(meterName));\n        assertEquals(histogram, metricMap.get(histogramName));\n        assertEquals(timer, metricMap.get(timerName));\n\n    }\n\n    @Test\n    public void counter() {\n        Counter counter = metricRegistry.counter(countName);\n        counter.inc();\n        checkCount(1);\n        counter.inc();\n        checkCount(2);\n        counter.dec();\n        checkCount(1);\n        counter.inc(2);\n        checkCount(3);\n        counter.dec(3);\n        checkCount(0);\n        counter = metricRegistry.counter(countName + \"aaa\");\n        counter.inc(100);\n        checkCount(0);\n    }\n\n    public void checkCount(int count) {\n        Counter counter = metricRegistry.counter(countName);\n        assertEquals(count, counter.getCount());\n    }\n\n    @Test\n    public void gauge() {\n        String value = \"test_gauge_value\";\n        String value2 = \"test_gauge_value2\";\n        Gauge gauge = metricRegistry.gauge(gaugeName, () -> new TestGauge(value));\n        assertTrue(gauge instanceof TestGauge);\n        assertEquals(value, gauge.getValue());\n\n        com.codahale.metrics.Gauge g = mr.gauge(gaugeName, () -> null);\n        assertNotNull(g);\n        assertEquals(value, g.getValue());\n\n        TestGauge testGauge = (TestGauge) gauge;\n        testGauge.setValue(value2);\n        assertEquals(value2, g.getValue());\n    }\n\n    @Test\n    public void histogram() {\n        Histogram histogram = metricRegistry.histogram(histogramName);\n        histogram.update(10);\n        assertEquals(1, histogram.getCount());\n        histogram.update(100);\n        assertEquals(2, histogram.getCount());\n        Snapshot snapshot = histogram.getSnapshot();\n        assertEquals(2, snapshot.size());\n        assertEquals(10, snapshot.getMin());\n        assertEquals(100, snapshot.getMax());\n    }\n\n    @Test\n    @SuppressWarnings(\"all\")\n    public void timer() throws InterruptedException {\n        Timer timer = metricRegistry.timer(timerName);\n        timer.update(10, TimeUnit.MILLISECONDS);\n        assertEquals(1, timer.getCount());\n        timer.update(200, TimeUnit.MILLISECONDS);\n        assertEquals(2, timer.getCount());\n        Timer.Context context = timer.time();\n        Thread.sleep(50);\n        context.stop();\n        assertEquals(3, timer.getCount());\n        Snapshot snapshot = timer.getSnapshot();\n\n        assertEquals(3, snapshot.size());\n        assertEquals(TimeUnit.MILLISECONDS.toNanos(10), snapshot.getMin());\n\n        timer = metricRegistry.timer(timerName);\n        timer.update(10, TimeUnit.MILLISECONDS);\n        timer.update(50, TimeUnit.MILLISECONDS);\n        timer.update(200, TimeUnit.MILLISECONDS);\n\n        double median = snapshot.getMedian();\n        String info = \"median = \" + (int) median;\n        Assert.assertTrue(info, median > TimeUnit.MILLISECONDS.toNanos(20));\n        Assert.assertTrue(info, median < TimeUnit.MILLISECONDS.toNanos(120));\n        assertEquals(TimeUnit.MILLISECONDS.toNanos(200), snapshot.getMax());\n    }\n\n\n    class TestGauge implements Gauge<String> {\n        private String name;\n\n        TestGauge(String name) {\n            this.name = name;\n        }\n\n        public void setValue(String name) {\n            this.name = name;\n        }\n\n        @Override\n        public String getValue() {\n            return name;\n        }\n    }\n\n}\n"
  },
  {
    "path": "metrics/src/test/java/com/megaease/easeagent/metrics/impl/MetricRegistryMock.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.metrics.impl;\n\nimport com.codahale.metrics.MetricRegistry;\nimport com.megaease.easeagent.metrics.MetricRegistryService;\n\npublic class MetricRegistryMock {\n    private static final MetricRegistry CODAHALE_METRIC_REGISTRY = MetricRegistryService.DEFAULT.createMetricRegistry(null, null, null);\n\n    public static MetricRegistry getCodahaleMetricRegistry() {\n        return CODAHALE_METRIC_REGISTRY;\n    }\n}\n"
  },
  {
    "path": "metrics/src/test/java/com/megaease/easeagent/metrics/impl/MetricTestUtils.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.metrics.impl;\n\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\n\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicLong;\n\npublic class MetricTestUtils {\n    public static void mockField(Object target, String fieldName, Object fieldValue, Runnable r) {\n        Object oldField = AgentFieldReflectAccessor.getFieldValue(target, fieldName);\n        AgentFieldReflectAccessor.setFieldValue(target, fieldName, fieldValue);\n        try {\n            r.run();\n        } finally {\n            AgentFieldReflectAccessor.setFieldValue(target, fieldName, oldField);\n        }\n    }\n\n    public static void nextWindow(com.codahale.metrics.Meter meter) {\n        Object movingAverages = AgentFieldReflectAccessor.getFieldValue(meter, \"movingAverages\");\n        AtomicLong lastTick = AgentFieldReflectAccessor.getFieldValue(movingAverages, \"lastTick\");\n        lastTick.addAndGet(TimeUnit.SECONDS.toNanos(5) * -1);\n    }\n}\n"
  },
  {
    "path": "metrics/src/test/java/com/megaease/easeagent/metrics/impl/MockClock.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.metrics.impl;\n\nimport com.codahale.metrics.Clock;\n\nimport java.util.Iterator;\nimport java.util.List;\n\npublic class MockClock extends Clock {\n    private final Iterator<Long> ticks;\n\n    public MockClock(List<Long> ticks) {\n        this.ticks = ticks.iterator();\n    }\n\n    @Override\n    public long getTick() {\n        return ticks.next();\n    }\n}\n"
  },
  {
    "path": "metrics/src/test/java/com/megaease/easeagent/metrics/impl/SnapshotImplTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.metrics.impl;\n\nimport com.megaease.easeagent.plugin.api.metric.Histogram;\nimport com.megaease.easeagent.plugin.api.metric.Snapshot;\nimport com.megaease.easeagent.plugin.bridge.NoOpMetrics;\nimport org.junit.Test;\n\nimport java.io.ByteArrayOutputStream;\n\nimport static org.junit.Assert.*;\n\npublic class SnapshotImplTest {\n\n    public static String buildMetricName(String name) {\n        return SnapshotImplTest.class.getName() + \"##\" + name;\n    }\n\n    public static Histogram buildhistogram(String name) {\n        com.codahale.metrics.Histogram unwrap = MetricRegistryMock.getCodahaleMetricRegistry().histogram(buildMetricName(name));\n        return HistogramImpl.build(unwrap);\n    }\n\n    @Test\n    public void build() {\n        Snapshot snapshot = SnapshotImpl.build(null);\n        assertTrue(snapshot instanceof NoOpMetrics.NoopSnapshot);\n        Histogram histogram = buildhistogram(\"build\");\n        histogram.update(10);\n        snapshot = histogram.getSnapshot();\n        assertTrue(snapshot instanceof SnapshotImpl);\n    }\n\n    public void test(String name) {\n        Histogram histogram = buildhistogram(name);\n        for (int i = 0; i < 100; i++) {\n            histogram.update(i);\n        }\n        Snapshot snapshot = histogram.getSnapshot();\n        assertEquals(1, snapshot.getValue(0.01), 0.1);\n\n        long[] values = snapshot.getValues();\n        for (int i = 0; i < values.length; i++) {\n            assertEquals(i, values[i]);\n        }\n\n        assertEquals(100, snapshot.size());\n        assertEquals(0, snapshot.getMin());\n        assertEquals(49.5, snapshot.getMean(), 1);\n        assertEquals(99, snapshot.getMax());\n\n        assertEquals(49, snapshot.getMedian(), 1);\n        assertEquals(74, snapshot.get75thPercentile(), 1);\n        assertEquals(94, snapshot.get95thPercentile(), 1);\n        assertEquals(97, snapshot.get98thPercentile(), 1);\n        assertEquals(98, snapshot.get99thPercentile(), 1);\n        assertEquals(99, snapshot.get999thPercentile(), 1);\n    }\n\n    @Test\n    public void getValue() {\n        test(\"getValue\");\n    }\n\n    @Test\n    public void getValues() {\n        test(\"getValues\");\n    }\n\n    @Test\n    public void size() {\n        test(\"size\");\n    }\n\n    @Test\n    public void getMax() {\n        test(\"getMax\");\n    }\n\n    @Test\n    public void getMean() {\n        test(\"getMean\");\n    }\n\n    @Test\n    public void getMin() {\n        test(\"getMin\");\n    }\n\n    @Test\n    public void getStdDev() {\n        Histogram histogram = buildhistogram(\"getStdDev\");\n        //9、2、5、4、12、7\n        histogram.update(9);\n        histogram.update(2);\n        histogram.update(5);\n        histogram.update(4);\n        histogram.update(12);\n        histogram.update(7);\n        Snapshot snapshot = histogram.getSnapshot();\n        assertEquals(3.3, snapshot.getStdDev(), 0.1);\n    }\n\n    @Test\n    public void dump() {\n        Histogram histogram = buildhistogram(\"dump\");\n        histogram.update(10);\n        Snapshot snapshot = histogram.getSnapshot();\n        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();\n        snapshot.dump(byteArrayOutputStream);\n        assertEquals(\"10\", byteArrayOutputStream.toString().trim());\n    }\n\n    @Test\n    public void unwrap() {\n        Histogram histogram = buildhistogram(\"unwrap\");\n        Snapshot snapshot = histogram.getSnapshot();\n        assertTrue(snapshot.unwrap() instanceof com.codahale.metrics.Snapshot);\n    }\n}\n"
  },
  {
    "path": "metrics/src/test/java/com/megaease/easeagent/metrics/impl/TimerImplTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.metrics.impl;\n\nimport com.codahale.metrics.Meter;\nimport com.megaease.easeagent.plugin.api.metric.Snapshot;\nimport com.megaease.easeagent.plugin.api.metric.Timer;\nimport com.megaease.easeagent.plugin.bridge.NoOpMetrics;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\nimport com.megaease.easeagent.plugin.utils.Pair;\nimport org.junit.Test;\n\nimport java.time.Duration;\nimport java.util.Arrays;\nimport java.util.Random;\nimport java.util.concurrent.TimeUnit;\n\nimport static org.junit.Assert.*;\nimport static org.mockito.Mockito.mock;\n\npublic class TimerImplTest {\n\n    public static String buildMetricName(String name) {\n        return TimerImplTest.class.getName() + \"##\" + name;\n    }\n\n    public static Pair<Timer, com.codahale.metrics.Timer> buildTimer(String name) {\n        com.codahale.metrics.Timer unwrap = MetricRegistryMock.getCodahaleMetricRegistry().timer(buildMetricName(name));\n        return new Pair<>(TimerImpl.build(unwrap), unwrap);\n    }\n\n    @Test\n    public void build() {\n        Timer timer = TimerImpl.build(null);\n        assertTrue(timer instanceof NoOpMetrics.NoopTimer);\n        Pair<Timer, com.codahale.metrics.Timer> pair = buildTimer(\"build\");\n        assertTrue(pair.getKey() instanceof TimerImpl);\n    }\n\n\n    public void test(String name) {\n        Pair<Timer, com.codahale.metrics.Timer> pair = buildTimer(name);\n\n        Timer timer = pair.getKey();\n\n        long duration = 10;\n        timer.update(duration, TimeUnit.MILLISECONDS);\n        assertEquals(1, timer.getCount());\n        assertEquals(TimeUnit.MILLISECONDS.toNanos(duration), timer.getSnapshot().getValues()[0]);\n\n        duration = 100;\n        timer.update(duration, TimeUnit.MILLISECONDS);\n        assertEquals(2, timer.getCount());\n        assertEquals(TimeUnit.MILLISECONDS.toNanos(duration), timer.getSnapshot().getValues()[1]);\n\n        duration = 101;\n        timer.update(Duration.ofMillis(duration));\n        assertEquals(3, timer.getCount());\n        assertEquals(TimeUnit.MILLISECONDS.toNanos(duration), timer.getSnapshot().getValues()[2]);\n\n        duration = 103;\n        long now = System.nanoTime();\n        MockClock mockClock = new MockClock(Arrays.asList(now, now + TimeUnit.MILLISECONDS.toNanos(duration)));\n        MetricTestUtils.mockField(timer.unwrap(), \"clock\", mockClock, () -> {\n            timer.time(() -> {\n            });\n        });\n\n        assertEquals(4, timer.getCount());\n        assertEquals(TimeUnit.MILLISECONDS.toNanos(duration), timer.getSnapshot().getValues()[3]);\n\n\n        duration = 104;\n        now = System.nanoTime();\n        mockClock = new MockClock(Arrays.asList(now, now + TimeUnit.MILLISECONDS.toNanos(duration)));\n        MetricTestUtils.mockField(timer.unwrap(), \"clock\", mockClock, () -> {\n            Timer.Context context = timer.time();\n            context.stop();\n        });\n\n        assertEquals(5, timer.getCount());\n        assertEquals(TimeUnit.MILLISECONDS.toNanos(duration), timer.getSnapshot().getValues()[4]);\n\n        duration = 105;\n        now = System.nanoTime();\n        mockClock = new MockClock(Arrays.asList(now, now + TimeUnit.MILLISECONDS.toNanos(duration)));\n        MetricTestUtils.mockField(timer.unwrap(), \"clock\", mockClock, () -> {\n            String testTimeName = \"testName\";\n            try {\n                String result = timer.time(() -> testTimeName);\n                assertEquals(testTimeName, result);\n            } catch (Exception e) {\n                assertFalse(\"must not throw error\", true);\n            }\n        });\n\n        assertEquals(6, timer.getCount());\n        assertEquals(TimeUnit.MILLISECONDS.toNanos(duration), timer.getSnapshot().getValues()[5]);\n\n        duration = 106;\n        now = System.nanoTime();\n        mockClock = new MockClock(Arrays.asList(now, now + TimeUnit.MILLISECONDS.toNanos(duration)));\n        MetricTestUtils.mockField(timer.unwrap(), \"clock\", mockClock, () -> {\n            String testTimeName = \"timeSupplier\";\n            try {\n                String result = timer.timeSupplier(() -> testTimeName);\n                assertEquals(testTimeName, result);\n            } catch (Exception e) {\n                assertFalse(\"must not throw error\", true);\n            }\n        });\n\n        assertEquals(7, timer.getCount());\n        assertEquals(TimeUnit.MILLISECONDS.toNanos(duration), timer.getSnapshot().getValues()[6]);\n\n\n        Snapshot snapshot = timer.getSnapshot();\n        assertNotNull(snapshot);\n        assertEquals(7, snapshot.size());\n        assertTrue(snapshot.unwrap() instanceof com.codahale.metrics.Snapshot);\n\n\n        duration = 107;\n        now = System.nanoTime();\n        mockClock = new MockClock(Arrays.asList(now, now + TimeUnit.MILLISECONDS.toNanos(duration)));\n        MetricTestUtils.mockField(timer.unwrap(), \"clock\", mockClock, () -> {\n            Timer.Context context = timer.time();\n            context.close();\n        });\n\n        assertEquals(8, timer.getCount());\n        assertEquals(TimeUnit.MILLISECONDS.toNanos(duration), timer.getSnapshot().getValues()[7]);\n\n    }\n\n\n    @Test\n    public void update() {\n        test(\"update\");\n\n    }\n\n    @Test\n    public void update1() {\n        test(\"update1\");\n    }\n\n    @Test\n    public void time() {\n        test(\"time\");\n    }\n\n    @Test\n    public void timeSupplier() {\n        test(\"timeSupplier\");\n    }\n\n    @Test\n    public void time1() {\n        test(\"time1\");\n    }\n\n    @Test\n    public void time2() {\n        test(\"time2\");\n    }\n\n    @Test\n    public void getCount() {\n        test(\"getCount\");\n    }\n\n    @Test\n    public void getFifteenMinuteRate() {\n        testRate(\"getFifteenMinuteRate\");\n    }\n\n    @Test\n    public void getFiveMinuteRate() {\n        testRate(\"getFiveMinuteRate\");\n    }\n\n    @Test\n    public void getMeanRate() {\n        testRate(\"getMeanRate\");\n    }\n\n    @Test\n    public void getOneMinuteRate() {\n        testRate(\"getOneMinuteRate\");\n    }\n\n    @Test\n    public void getSnapshot() {\n        test(\"getSnapshot\");\n    }\n\n    private void testRate(String name) {\n        Pair<Timer, com.codahale.metrics.Timer> pair = buildTimer(name);\n\n        Timer timer = pair.getKey();\n        Meter meter = AgentFieldReflectAccessor.getFieldValue(timer.unwrap(), \"meter\");\n        assertNotNull(meter);\n\n        Random random = new Random();\n        int count = 0;\n        for (int i = 0; i < 15 * 60 / 5; i++) {\n            timer.update(random.nextInt(2000), TimeUnit.MILLISECONDS);\n            count++;\n            assertEquals(count, timer.getCount());\n            for (int j = 0; j < 10 + i; j++) {\n                timer.update(random.nextInt(2000), TimeUnit.MILLISECONDS);\n            }\n            count += 10 + i;\n            assertEquals(count, timer.getCount());\n            MetricTestUtils.nextWindow(meter);\n        }\n        double meanRate = timer.getMeanRate();\n        double oneMeanRate = timer.getOneMinuteRate();\n        double fiveMeanRate = timer.getFiveMinuteRate();\n        double fifteenMeanRate = timer.getFifteenMinuteRate();\n        assertTrue(String.format(\"meter.getMeanRate()<%s> must > 0\", meanRate), meanRate > 0);\n        assertTrue(String.format(\"meter.getOneMinuteRate()<%s> must > 0\", oneMeanRate), oneMeanRate > 0);\n        assertTrue(String.format(\"meter.getFiveMinuteRate()<%s> must > 0\", fiveMeanRate), fiveMeanRate > 0);\n        assertTrue(String.format(\"meter.getFifteenMinuteRate()<%s> must > 0\", fifteenMeanRate), fifteenMeanRate > 0);\n        assertTrue(meanRate > oneMeanRate);\n        assertTrue(oneMeanRate > fiveMeanRate);\n        assertTrue(fiveMeanRate > fifteenMeanRate);\n\n    }\n\n\n}\n"
  },
  {
    "path": "metrics/src/test/java/com/megaease/easeagent/metrics/jvm/memory/JVMMemoryMetricV2Test.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.metrics.jvm.memory;\n\nimport com.megaease.easeagent.metrics.MetricProviderImplTest;\nimport com.megaease.easeagent.mock.config.MockConfig;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.metric.MetricRegistry;\nimport com.megaease.easeagent.plugin.api.metric.name.MetricType;\nimport com.megaease.easeagent.plugin.api.metric.name.NameFactory;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\npublic class JVMMemoryMetricV2Test {\n    private static final JVMMemoryMetricV2 JVM_MEMORY_METRIC_V_2;\n\n    static {\n        EaseAgent.configFactory = MockConfig.getPluginConfigManager();\n        EaseAgent.metricRegistrySupplier = MetricProviderImplTest.METRIC_PROVIDER.metricSupplier();\n        JVM_MEMORY_METRIC_V_2 = JVMMemoryMetricV2.getMetric();\n    }\n\n\n    @Test\n    public void getMetric() {\n        assertNotNull(JVM_MEMORY_METRIC_V_2);\n    }\n\n    @Test\n    public void nameFactory() {\n        NameFactory nameFactory = JVMMemoryMetricV2.nameFactory();\n        assertEquals(1, nameFactory.metricTypes().size());\n        assertTrue(nameFactory.metricTypes().contains(MetricType.GaugeType));\n    }\n\n    @Test\n    public void doJob() {\n        IPluginConfig config = AgentFieldReflectAccessor.getFieldValue(JVM_MEMORY_METRIC_V_2, \"config\");\n        MetricRegistry metricRegistry = AgentFieldReflectAccessor.getFieldValue(JVM_MEMORY_METRIC_V_2, \"metricRegistry\");\n        assertTrue(config.enabled());\n        JVM_MEMORY_METRIC_V_2.doJob();\n        assertTrue(metricRegistry.getMetrics().size() > 0);\n    }\n}\n"
  },
  {
    "path": "metrics/src/test/resources/log4j2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright (c) 2021, MegaEase\n  ~ All rights reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~   http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<Configuration status=\"WARN\">\n    <Appenders>\n        <Console name=\"Console\" target=\"SYSTEM_OUT\">\n            <!--<PatternLayout pattern=\"%d{HH:mm:ss.SSS} [%10.10t] %-p %10.10c{1} - %msg%n\"/>-->\n            <PatternLayout pattern=\"%d{HH:mm:ss.SSS} %-5level %class{36}:%L (%X{testMdc}) - %msg%xEx%n\"/>\n        </Console>\n    </Appenders>\n    <Loggers>\n        <Root level=\"INFO\">\n            <AppenderRef ref=\"Console\"/>\n        </Root>\n    </Loggers>\n</Configuration>\n"
  },
  {
    "path": "metrics/src/test/resources/mock_agent.properties",
    "content": "name=test-metric-service\nsystem=test-metric-system\n\nplugin.observability.global.metric.enabled=true\nplugin.observability.jvmMemory.metric.enabled=true\nplugin.observability.testMetric.metric.enabled=true\nplugin.observability.testMetric.metric.interval=30\nplugin.observability.testMetric.metric.topic=application-meter\nplugin.observability.testMetric.metric.appendType=kafka\n\nplugin.observability.testMetric2.metric.intervalUnit=MILLISECONDS\n"
  },
  {
    "path": "mock/config-mock/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright (c) 2021, MegaEase\n  ~ All rights reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~   http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>mock</artifactId>\n        <groupId>com.megaease.easeagent</groupId>\n        <version>2.3.0</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>config-mock</artifactId>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>config</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>log4j2-mock</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "mock/config-mock/src/main/java/com/megaease/easeagent/config/MockConfigLoader.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.config;\n\nimport java.io.File;\n\npublic class MockConfigLoader {\n    public static GlobalConfigs loadFromFile(File file) {\n        return ConfigLoader.loadFromFile(file);\n    }\n}\n"
  },
  {
    "path": "mock/config-mock/src/main/java/com/megaease/easeagent/mock/config/MockConfig.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.mock.config;\n\nimport com.megaease.easeagent.config.Configs;\nimport com.megaease.easeagent.config.GlobalConfigs;\nimport com.megaease.easeagent.config.MockConfigLoader;\nimport com.megaease.easeagent.config.PluginConfigManager;\n\nimport java.io.File;\nimport java.net.URL;\nimport java.util.HashMap;\nimport java.util.Map;\n\npublic class MockConfig {\n    private static final String MOCK_CONFIG_YAML_FILE = \"mock_agent.yaml\";\n    private static final String MOCK_CONFIG_PROP_FILE = \"mock_agent.properties\";\n    private static final GlobalConfigs CONFIGS;\n    private static final PluginConfigManager PLUGIN_CONFIG_MANAGER;\n\n    static {\n        Map<String, String> initConfigs = new HashMap<>();\n        initConfigs.put(\"name\", \"demo-service\");\n        initConfigs.put(\"system\", \"demo-system\");\n\n        initConfigs.put(\"observability.outputServer.timeout\", \"10000\");\n        initConfigs.put(\"observability.outputServer.enabled\", \"true\");\n        initConfigs.put(\"observability.tracings.output.enabled\", \"true\");\n        initConfigs.put(\"plugin.observability.global.tracing.enabled\", \"true\");\n        initConfigs.put(\"plugin.observability.global.metric.enabled\", \"true\");\n        CONFIGS = new GlobalConfigs(initConfigs);\n        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();\n        URL url = classLoader.getResource(MOCK_CONFIG_YAML_FILE);\n        if (url == null) {\n             url = classLoader.getResource(MOCK_CONFIG_PROP_FILE);\n        }\n        if (url != null) {\n            GlobalConfigs configsFromOuterFile = MockConfigLoader.loadFromFile(new File(url.getFile()));\n            CONFIGS.mergeConfigs(configsFromOuterFile);\n        }\n        PLUGIN_CONFIG_MANAGER = PluginConfigManager.builder(CONFIGS).build();\n    }\n\n    public static Configs getCONFIGS() {\n        return CONFIGS;\n    }\n\n    public static PluginConfigManager getPluginConfigManager() {\n        return PLUGIN_CONFIG_MANAGER;\n    }\n}\n"
  },
  {
    "path": "mock/config-mock/src/test/java/com/megaease/easeagent/mock/config/MockConfigTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.mock.config;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\npublic class MockConfigTest {\n\n    @Test\n    public void getCONFIGS() {\n        assertTrue(MockConfig.getCONFIGS().getBoolean(\"test.mock.key\"));\n        assertEquals(\"testValue\", MockConfig.getCONFIGS().getString(\"test.mock.keyStr\"));\n        assertNull(MockConfig.getCONFIGS().getString(\"test.mock.keyStrAAAAAAAAAA\"));\n    }\n}\n"
  },
  {
    "path": "mock/config-mock/src/test/resources/mock_agent.properties",
    "content": "test.mock.key=true\ntest.mock.keyStr=testValue\n"
  },
  {
    "path": "mock/config-mock/src/test/resources/mock_agent.yaml",
    "content": "test:\n    mock:\n        key: true\n        keyStr: testValue\n"
  },
  {
    "path": "mock/context-mock/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright (c) 2021, MegaEase\n  ~ All rights reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~   http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>mock</artifactId>\n        <groupId>com.megaease.easeagent</groupId>\n        <version>2.3.0</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>context-mock</artifactId>\n\n\n    <dependencies>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>context</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>config-mock</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>utils-mock</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n    </dependencies>\n</project>\n"
  },
  {
    "path": "mock/context-mock/src/main/java/com/megaease/easeagent/mock/context/MockContext.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.mock.context;\n\nimport com.megaease.easeagent.plugin.api.Context;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n@Retention(RetentionPolicy.RUNTIME)\n@Target(ElementType.TYPE)\npublic @interface MockContext {\n    @SuppressWarnings(\"unused\")\n    Context ignored = MockContextManager.getContext();\n}\n"
  },
  {
    "path": "mock/context-mock/src/main/java/com/megaease/easeagent/mock/context/MockContextManager.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.mock.context;\n\nimport com.megaease.easeagent.context.ContextManager;\nimport com.megaease.easeagent.mock.config.MockConfig;\nimport com.megaease.easeagent.mock.utils.MockProvider;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.metric.MetricProvider;\nimport com.megaease.easeagent.plugin.api.trace.TracingProvider;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\n\nimport java.util.Iterator;\nimport java.util.ServiceLoader;\n\npublic class MockContextManager {\n    private static final ContextManager CONTEXT_MANAGER_MOCK = ContextManager.build(MockConfig.getCONFIGS());\n\n    static {\n        ServiceLoader<MockProvider> loader = ServiceLoader.load(MockProvider.class);\n        Iterator<MockProvider> iterator = loader.iterator();\n        while (iterator.hasNext()) {\n            MockProvider mockProvider = iterator.next();\n            Object o = mockProvider.get();\n            if (o == null) {\n                continue;\n            }\n            if (o instanceof TracingProvider) {\n                CONTEXT_MANAGER_MOCK.setTracing((TracingProvider) o);\n            } else if (o instanceof MetricProvider) {\n                CONTEXT_MANAGER_MOCK.setMetric((MetricProvider) o);\n            }\n        }\n    }\n\n    public static ContextManager getContextManagerMock() {\n        return CONTEXT_MANAGER_MOCK;\n    }\n\n    public static Context getContext() {\n        return EaseAgent.getContext();\n    }\n}\n"
  },
  {
    "path": "mock/log4j2-mock/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright (c) 2021, MegaEase\n  ~ All rights reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~   http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>mock</artifactId>\n        <groupId>com.megaease.easeagent</groupId>\n        <version>2.3.0</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>log4j2-mock</artifactId>\n\n\n    <dependencies>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>log4j2-api</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>log4j2-impl</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "mock/log4j2-mock/src/main/java/com/megaease/easeagent/mock/log4j2/AllUrlsSupplier.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.mock.log4j2;\n\nimport com.megaease.easeagent.log4j2.ClassLoaderUtils;\n\nimport java.net.URL;\n\npublic class AllUrlsSupplier implements UrlSupplier {\n    public static final String USE_ENV = \"EASEAGENT-SLF4J2-USE-CURRENT\";\n    private static volatile boolean enabled = false;\n\n    public static void setEnabled(boolean enabled) {\n        AllUrlsSupplier.enabled = enabled;\n    }\n\n    @Override\n    public URL[] get() {\n        if (!enabled()) {\n            return new URL[0];\n        }\n        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();\n        return ClassLoaderUtils.getAllUrls(classLoader);\n    }\n\n    private boolean enabled() {\n        if (enabled) {\n            return true;\n        }\n        String enabledStr = System.getProperty(USE_ENV);\n        if (enabledStr == null) {\n            return false;\n        }\n        try {\n            return Boolean.parseBoolean(enabledStr);\n        } catch (Exception e) {\n            return false;\n        }\n    }\n}\n"
  },
  {
    "path": "mock/log4j2-mock/src/main/java/com/megaease/easeagent/mock/log4j2/DirUrlsSupplier.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.mock.log4j2;\n\nimport java.io.File;\nimport java.net.MalformedURLException;\nimport java.net.URL;\n\npublic class DirUrlsSupplier implements UrlSupplier {\n    public static final String LIB_DIR_ENV = \"EASEAGENT-SLF4J2-LIB-DIR\";\n\n    @Override\n    public URL[] get() {\n        String dir = System.getProperty(LIB_DIR_ENV);\n        if (dir == null) {\n            return new URL[0];\n        }\n        File file = new File(dir);\n        if (!file.isDirectory()) {\n            return new URL[0];\n        }\n        File[] files = file.listFiles();\n        if (files == null) {\n            return new URL[0];\n        }\n        URL[] urls = new URL[files.length];\n        for (int i = 0; i < files.length; i++) {\n            try {\n                urls[i] = files[i].toURI().toURL();\n            } catch (MalformedURLException e) {\n                return new URL[0];\n            }\n        }\n        return urls;\n    }\n}\n"
  },
  {
    "path": "mock/log4j2-mock/src/main/java/com/megaease/easeagent/mock/log4j2/JarPathUrlsSupplier.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.mock.log4j2;\n\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class JarPathUrlsSupplier implements UrlSupplier {\n    public static final String EASEAGENT_SLF4_J2_LIB_JAR_PATHS = \"EASEAGENT-SLF4J2-LIB-JAR-PATHS\";\n\n    @Override\n    public URL[] get() {\n        String dir = System.getProperty(EASEAGENT_SLF4_J2_LIB_JAR_PATHS);\n        if (dir == null) {\n            return new URL[0];\n        }\n        String[] paths = dir.split(\",\");\n        List<URL> urls = new ArrayList<>();\n        for (String path : paths) {\n            if (path.trim().isEmpty()) {\n                continue;\n            }\n            try {\n                urls.add(new URL(path));\n            } catch (MalformedURLException ignored) {\n                //ignored\n            }\n        }\n        URL[] result = new URL[urls.size()];\n        urls.toArray(result);\n        return result;\n    }\n\n\n}\n"
  },
  {
    "path": "mock/log4j2-mock/src/main/java/com/megaease/easeagent/mock/log4j2/JarUrlsSupplier.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.mock.log4j2;\n\nimport javax.annotation.Nonnull;\nimport java.net.URL;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\n\npublic class JarUrlsSupplier implements UrlSupplier {\n    private final UrlSupplier[] suppliers;\n\n    public JarUrlsSupplier(@Nonnull UrlSupplier[] suppliers) {\n        this.suppliers = suppliers;\n    }\n\n\n    @Override\n    public URL[] get() {\n        List<URL> list = new ArrayList<>();\n        for (UrlSupplier supplier : suppliers) {\n            URL[] urls = supplier.get();\n            if (urls != null && urls.length > 0) {\n                list.addAll(Arrays.asList(urls));\n            }\n        }\n        URL[] result = new URL[list.size()];\n        list.toArray(result);\n        return result;\n    }\n}\n"
  },
  {
    "path": "mock/log4j2-mock/src/main/java/com/megaease/easeagent/mock/log4j2/URLClassLoaderSupplier.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.mock.log4j2;\n\nimport com.megaease.easeagent.log4j2.ClassloaderSupplier;\n\nimport java.net.URLClassLoader;\nimport java.util.Objects;\n\npublic class URLClassLoaderSupplier implements ClassloaderSupplier {\n\n    @Override\n    public ClassLoader get() {\n        JarUrlsSupplier jarUrlsSupplier = new JarUrlsSupplier(new UrlSupplier[]{\n            new AllUrlsSupplier(), new DirUrlsSupplier(), new JarPathUrlsSupplier()\n        });\n        return new URLClassLoader(Objects.requireNonNull(jarUrlsSupplier.get(), \"urls must not be null.\"), null);\n    }\n}\n"
  },
  {
    "path": "mock/log4j2-mock/src/main/java/com/megaease/easeagent/mock/log4j2/UrlSupplier.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.mock.log4j2;\n\nimport java.net.URL;\n\npublic interface UrlSupplier {\n    URL[] get();\n}\n"
  },
  {
    "path": "mock/log4j2-mock/src/main/resources/META-INF/services/com.megaease.easeagent.log4j2.ClassloaderSupplier",
    "content": "com.megaease.easeagent.mock.log4j2.URLClassLoaderSupplier\n"
  },
  {
    "path": "mock/log4j2-mock/src/test/java/com/megaease/easeagent/log4j2/impl/AgentLoggerFactoryTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.log4j2.impl;\n\nimport com.megaease.easeagent.log4j2.Logger;\nimport com.megaease.easeagent.log4j2.LoggerFactory;\nimport com.megaease.easeagent.log4j2.MDC;\nimport com.megaease.easeagent.log4j2.api.AgentLogger;\nimport com.megaease.easeagent.log4j2.api.AgentLoggerFactory;\nimport com.megaease.easeagent.mock.log4j2.URLClassLoaderSupplier;\nimport org.junit.Test;\n\nimport java.lang.reflect.InvocationTargetException;\nimport java.util.function.Function;\n\nimport static org.junit.Assert.assertNotNull;\nimport static org.junit.Assert.assertTrue;\n\npublic class AgentLoggerFactoryTest {\n\n    @Test\n    public void builder() throws NoSuchMethodException, IllegalAccessException, InstantiationException, NoSuchFieldException, InvocationTargetException, ClassNotFoundException {\n        try {\n            AgentLoggerFactory.builder(() -> null, AgentLogger.LOGGER_SUPPLIER, AgentLogger.class).build();\n            assertTrue(\"must be err\", false);\n        } catch (Exception e) {\n            assertNotNull(e);\n        }\n        AgentLoggerFactory<?> factory = AgentLoggerFactory.builder(new URLClassLoaderSupplier(), AgentLogger.LOGGER_SUPPLIER, AgentLogger.class).build();\n\n    }\n\n    @Test\n    public void getLogger() {\n        Logger logger = LoggerFactory.getLogger(AgentLoggerFactoryTest.class.getName());\n        logger.info(\"aaaa\");\n        MDC.put(\"testMdc\", \"testMdc_value\");\n        logger.info(\"bbbb\");\n        assertNotNull(MDC.get(\"testMdc\"));\n    }\n\n\n    @Test\n    public void newFactory() {\n        AgentLoggerFactory<TestAgentLogger> factory = LoggerFactory.newFactory(TestAgentLogger.LOGGER_SUPPLIER, TestAgentLogger.class);\n        TestAgentLogger logger = factory.getLogger(AgentLoggerFactoryTest.class.getName());\n        logger.info(\"aaaa\");\n        factory.mdc().put(\"newFactory\", \"newFactory\");\n        assertNotNull(MDC.get(\"newFactory\"));\n\n    }\n\n    static class TestAgentLogger extends AgentLogger {\n        public static final Function<java.util.logging.Logger, TestAgentLogger> LOGGER_SUPPLIER = logger -> new TestAgentLogger(logger);\n\n        public TestAgentLogger(java.util.logging.Logger logger) {\n            super(logger);\n        }\n    }\n}\n"
  },
  {
    "path": "mock/log4j2-mock/src/test/java/com/megaease/easeagent/log4j2/impl/MDCTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.log4j2.impl;\n\nimport com.megaease.easeagent.log4j2.MDC;\nimport com.megaease.easeagent.mock.log4j2.AllUrlsSupplier;\nimport org.junit.Test;\n\nimport static org.junit.Assert.assertNotNull;\nimport static org.junit.Assert.assertNull;\n\npublic class MDCTest {\n    @Test\n    public void put() {\n        MDC.put(\"testA\", \"testB\");\n        assertNull(org.slf4j.MDC.get(\"testA\"));\n        assertNotNull(MDC.get(\"testA\"));\n    }\n\n    @Test\n    public void remove() {\n        MDC.put(\"testA\", \"testB\");\n        assertNotNull(MDC.get(\"testA\"));\n        MDC.remove(\"testA\");\n        assertNull(MDC.get(\"testA\"));\n    }\n\n    @Test\n    public void get() {\n        MDC.put(\"testA\", \"testB\");\n        assertNull(org.slf4j.MDC.get(\"testA\"));\n        assertNotNull(MDC.get(\"testA\"));\n        org.slf4j.MDC.put(\"testB\", \"testB\");\n        assertNotNull(org.slf4j.MDC.get(\"testB\"));\n        assertNull(MDC.get(\"testB\"));\n    }\n}\n"
  },
  {
    "path": "mock/log4j2-mock/src/test/resources/log4j2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Configuration status=\"WARN\">\n    <Appenders>\n        <Console name=\"Console\" target=\"SYSTEM_OUT\">\n            <!--<PatternLayout pattern=\"%d{HH:mm:ss.SSS} [%10.10t] %-p %10.10c{1} - %msg%n\"/>-->\n            <PatternLayout pattern=\"%d{HH:mm:ss.SSS} %-5level %class{36}:%L (%X{testMdc}) - %msg%xEx%n\"/>\n        </Console>\n    </Appenders>\n    <Loggers>\n        <Root level=\"INFO\">\n            <AppenderRef ref=\"Console\"/>\n        </Root>\n    </Loggers>\n</Configuration>\n"
  },
  {
    "path": "mock/metrics-mock/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright (c) 2021, MegaEase\n  ~ All rights reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~   http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>mock</artifactId>\n        <groupId>com.megaease.easeagent</groupId>\n        <version>2.3.0</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>metrics-mock</artifactId>\n\n\n    <dependencies>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>metrics</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>report-mock</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>utils-mock</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n    </dependencies>\n</project>\n"
  },
  {
    "path": "mock/metrics-mock/src/main/java/com/megaease/easeagent/mock/metrics/MetricTestUtils.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.mock.metrics;\n\nimport com.codahale.metrics.MetricRegistry;\nimport com.megaease.easeagent.metrics.impl.MetricRegistryImpl;\nimport com.megaease.easeagent.plugin.api.metric.ServiceMetric;\n\npublic class MetricTestUtils {\n    public static void clear(com.megaease.easeagent.plugin.api.metric.MetricRegistry metricRegistry) {\n        if (!(metricRegistry instanceof MetricRegistryImpl)) {\n            return;\n        }\n        MetricRegistry metricRegistry1 = ((MetricRegistryImpl) metricRegistry).getMetricRegistry();\n        for (String s : metricRegistry1.getNames()) {\n            metricRegistry.remove(s);\n        }\n    }\n\n    public static void clear(ServiceMetric serviceMetric) {\n        MetricTestUtils.clear(serviceMetric.getMetricRegistry());\n    }\n\n}\n"
  },
  {
    "path": "mock/metrics-mock/src/main/java/com/megaease/easeagent/mock/metrics/MockMetricProvider.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.mock.metrics;\n\nimport com.megaease.easeagent.metrics.AutoRefreshReporter;\nimport com.megaease.easeagent.metrics.MetricBeanProviderImpl;\nimport com.megaease.easeagent.metrics.MetricProviderImpl;\nimport com.megaease.easeagent.metrics.jvm.JvmBeanProvider;\nimport com.megaease.easeagent.mock.config.MockConfig;\nimport com.megaease.easeagent.mock.report.MockReport;\nimport com.megaease.easeagent.mock.utils.MockProvider;\n\npublic class MockMetricProvider implements MockProvider {\n    private static final MetricBeanProviderImpl METRIC_PROVIDER = new MetricBeanProviderImpl();\n    private static final JvmBeanProvider JVM_METRIC_PROVIDER = new JvmBeanProvider();\n\n    static {\n        METRIC_PROVIDER.setConfig(MockConfig.getCONFIGS());\n        METRIC_PROVIDER.setAgentReport(MockReport.getAgentReport());\n        JVM_METRIC_PROVIDER.afterPropertiesSet();\n        MockReport.setMetricFlushable(MockMetricProvider::flush);\n    }\n\n    public static MetricBeanProviderImpl getMetricProvider() {\n        return METRIC_PROVIDER;\n    }\n\n    @Override\n    public Object get() {\n        return getMetricProvider();\n    }\n\n    public static void flush() {\n        MetricProviderImpl metricProvider = METRIC_PROVIDER.getMetricProvider();\n        if (metricProvider == null) {\n            return;\n        }\n        for (AutoRefreshReporter autoRefreshReporter : metricProvider.getReporterList()) {\n            autoRefreshReporter.getReporter().report();\n        }\n    }\n\n    public static void clearAll() {\n        MetricProviderImpl metricProvider = METRIC_PROVIDER.getMetricProvider();\n        if (metricProvider == null) {\n            return;\n        }\n        for (com.megaease.easeagent.plugin.api.metric.MetricRegistry metricRegistry : metricProvider.getRegistryList()) {\n            MetricTestUtils.clear(metricRegistry);\n        }\n    }\n}\n"
  },
  {
    "path": "mock/metrics-mock/src/main/resources/META-INF/services/com.megaease.easeagent.mock.utils.MockProvider",
    "content": "com.megaease.easeagent.mock.metrics.MockMetricProvider\n"
  },
  {
    "path": "mock/plugin-api-mock/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright (c) 2021, MegaEase\n  ~ All rights reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~   http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>mock</artifactId>\n        <groupId>com.megaease.easeagent</groupId>\n        <version>2.3.0</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>plugin-api-mock</artifactId>\n\n\n    <dependencies>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>context-mock</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>metrics-mock</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>zipkin-mock</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>utils-mock</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n\n        <dependency>\n            <groupId>junit</groupId>\n            <artifactId>junit</artifactId>\n        </dependency>\n    </dependencies>\n</project>\n"
  },
  {
    "path": "mock/plugin-api-mock/src/main/java/com/megaease/easeagent/mock/plugin/api/MockEaseAgent.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.mock.plugin.api;\n\nimport com.megaease.easeagent.config.Configs;\nimport com.megaease.easeagent.mock.config.MockConfig;\nimport com.megaease.easeagent.mock.metrics.MetricTestUtils;\nimport com.megaease.easeagent.mock.metrics.MockMetricProvider;\nimport com.megaease.easeagent.mock.plugin.api.utils.ContextUtils;\nimport com.megaease.easeagent.mock.report.MockReport;\nimport com.megaease.easeagent.mock.report.MockSpanReport;\nimport com.megaease.easeagent.mock.report.impl.LastJsonReporter;\nimport com.megaease.easeagent.plugin.api.logging.AccessLogInfo;\nimport com.megaease.easeagent.plugin.api.metric.ServiceMetric;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\n\nimport java.util.Map;\nimport java.util.function.Predicate;\n\n/**\n * Mock EaseAgent for unit test\n *\n * <pre>${@code\n * @RunWith(EaseAgentJunit4ClassRunner.class)\n * public class InterceptorTest {\n *     @Test\n *     public void testBeforeAfter() {\n *         assertNull(MockEaseAgent.getLastSpan());\n *         final Object key = new Object();\n *         Interceptor interceptor = new Interceptor() {\n *             @Override\n *             public void before(MethodInfo methodInfo, Context context) {\n *                 Span span = context.nextSpan();\n *                 span.start();\n *                 context.put(key, span);\n *             }\n *\n *             @Override\n *             public void after(MethodInfo methodInfo, Context context) {\n *                 Span span = context.remove(key);\n *                 span.finish();\n *             }\n *         };\n *         Context context = EaseAgent.getContext();\n *         interceptor.before(null, context);\n *         Span span = context.get(key);\n *         assertNotNull(span);\n *         assertNull(MockEaseAgent.getLastSpan());\n *         interceptor.after(null, context);\n *         ReportSpan reportSpan = MockEaseAgent.getLastSpan();\n *         SpanTestUtils.sameId(span, reportSpan);\n *     }\n * }\n * }</pre>\n */\npublic class MockEaseAgent {\n\n    /**\n     * get last Span from Report cache\n     * <p>\n     * see com.megaease.easeagent.mock.plugin.api.demo.MockEaseAgentTest#lastMetricJsonReporter#getLastSpan\n     *\n     * <pre>${@code\n     *         Span span = EaseAgent.getContext().nextSpan();\n     *         span.start().finish();\n     *         assertNotNull(MockEaseAgent.getLastSpan());\n     *         SpanTestUtils.sameId(span, MockEaseAgent.getLastSpan());\n     * }</pre>\n     *\n     * @return Report Span\n     */\n    public static ReportSpan getLastSpan() {\n        return MockReport.getLastSpan();\n    }\n\n    /**\n     * clean last Span cache from Report\n     * <p>\n     * see com.megaease.easeagent.mock.plugin.api.demo.MockEaseAgentTest#lastMetricJsonReporter#cleanLastSpan\n     *\n     * <pre>${@code\n     *         Span span = EaseAgent.getContext().nextSpan();\n     *         span.start().finish();\n     *         assertNotNull(MockEaseAgent.getLastSpan());\n     *         SpanTestUtils.sameId(span, MockEaseAgent.getLastSpan());\n     *         MockEaseAgent.cleanLastSpan();\n     *         assertNull(MockEaseAgent.getLastSpan());\n     * }</pre>\n     */\n    public static void cleanLastSpan() {\n        MockReport.cleanLastSpan();\n    }\n\n    /**\n     * get last Log from Report cache\n     *\n     * <pre>${@code\n     *         EaseAgent.getAgentReport().report(log);\n     *         assertNotNull(MockEaseAgent.getLastLog());\n     * }</pre>\n     * @return Access Log\n     */\n    public static AccessLogInfo getLastLog() {\n        return MockReport.getLastAccessLog();\n    }\n\n    /**\n     * clean last Span cache from Report\n     */\n    public static void clearLastLog() {\n        MockReport.cleanLastAccessLog();\n    }\n\n    /**\n     * set MockSpanReport for receive all finish Span\n     * <p>\n     * see com.megaease.easeagent.mock.plugin.api.demo.MockEaseAgentTest#lastMetricJsonReporter#setMockSpanReport\n     *\n     * <pre>${@code\n     *         List<ReportSpan> spans = new ArrayList<>();\n     *         MockEaseAgent.setMockSpanReport(spans::add);\n     *         Span span1 = EaseAgent.getContext().nextSpan();\n     *         span1.start().finish();\n     *         Span span2 = EaseAgent.getContext().nextSpan();\n     *         span2.start().finish();\n     *         assertEquals(2, spans.size());\n     *         SpanTestUtils.sameId(span1, spans.get(0));\n     *         SpanTestUtils.sameId(span2, spans.get(1));\n     * }</pre>\n     *\n     * @param mockSpanReport {@link MockSpanReport}\n     */\n    public static void setMockSpanReport(MockSpanReport mockSpanReport) {\n        MockReport.setMockSpanReport(mockSpanReport);\n    }\n\n    /**\n     * create and get ${@link LastJsonReporter}  for metric by filter\n     * <p>\n     * see com.megaease.easeagent.mock.plugin.api.demo.MockEaseAgentTest#lastMetricJsonReporter#lastMetricJsonReporter\n     *\n     * <pre>${@code\n     *         //Interceptor init\n     *         IPluginConfig config = EaseAgent.getConfig(\"observability\", \"lastMetricJsonReporter\", ConfigConst.PluginID.METRIC);\n     *         NameFactory nameFactory = NameFactory.createBuilder().counterType(MetricSubType.DEFAULT,\n     *             ImmutableMap.<MetricField, MetricValueFetcher>builder()\n     *                 .put(MetricField.EXECUTION_COUNT, MetricValueFetcher.CountingCount).build()\n     *         ).build();\n     *         String type = \"lastMetricJsonReporterType\";\n     *         Tags tags = new Tags(\"testCategory\", type, \"testName\");\n     *         MetricRegistry metricRegistry = EaseAgent.newMetricRegistry(config, nameFactory, tags);\n     *\n     *         //Interceptor after\n     *         String key = \"lastMetricJsonReporterKey\";\n     *         metricRegistry.counter(nameFactory.counterName(key, MetricSubType.DEFAULT)).inc();\n     *\n     *         //verify metric count==1\n     *         LastJsonReporter lastJsonReporter = MockEaseAgent.lastMetricJsonReporter(TagVerifier.build(tags, key)::verifyAnd);\n     *         Map<String, Object> metric = lastJsonReporter.flushAndOnlyOne();\n     *         assertEquals(1, metric.get(MetricField.EXECUTION_COUNT.getField()));\n     * }</pre>\n     *\n     * @param filter\n     * @return LastJsonReporter\n     */\n    public static LastJsonReporter lastMetricJsonReporter(Predicate<Map<String, Object>> filter) {\n        return MockReport.lastMetricJsonReporter(filter);\n    }\n\n    /**\n     * get all Configs for unit test\n     * <p>\n     * see com.megaease.easeagent.mock.plugin.api.demo.MockEaseAgentTest#lastMetricJsonReporter#getConfigs\n     *\n     * <pre>${@code\n     *         Configs configs = MockEaseAgent.getConfigs();\n     *         String name = configs.getString(\"name\");\n     *         assertNotNull(name);\n     * }</pre>\n     *\n     * @return\n     */\n    public static Configs getConfigs() {\n        return MockConfig.getCONFIGS();\n    }\n\n    /**\n     * reset all of context: metric, tracing, redirect etc.\n     * <p>\n     * see com.megaease.easeagent.mock.plugin.api.demo.MockEaseAgentTest#lastMetricJsonReporter\n     *\n     * <pre>${@code\n     *          Context context = EaseAgent.getContext();\n     *         String key = \"testKey\";\n     *         context.put(key, \"value\");\n     *         assertNotNull(context.get(key));\n     *         MockEaseAgent.resetAll();\n     *         assertNull(context.get(key));\n     *\n     * //        //verify metric count==1\n     * //        LastJsonReporter lastJsonReporter = MockEaseAgent.lastMetricJsonReporter(TagVerifier.build(tags, key)::verifyAnd);\n     * //        Map<String, Object> metric = lastJsonReporter.flushAndOnlyOne();\n     * //        assertEquals(1, metric.get(MetricField.EXECUTION_COUNT.getField()));\n     * //\n     * //        //reset all.\n     * //        MockEaseAgent.resetAll();\n     * //        try {\n     * //            lastJsonReporter.flushAndOnlyOne();\n     * //            fail(\"must throw Exception\");\n     * //        } catch (RuntimeException e) {\n     * //            //throw Exception because it is empty.\n     * //        }\n     *  }</pre>\n     */\n    public static void resetAll() {\n        ContextUtils.resetAll();\n    }\n\n    /**\n     * clean all metric\n     * see com.megaease.easeagent.mock.plugin.api.demo.MockEaseAgentTest#lastMetricJsonReporter\n     *\n     * <pre>${@code\n     *         //inc 1\n     *         metricRegistry.counter(nameFactory.counterName(key, MetricSubType.DEFAULT)).inc();\n     *         metric = lastJsonReporter.flushAndOnlyOne();\n     *         //count == 1\n     *         assertEquals(1, metric.get(MetricField.EXECUTION_COUNT.getField()));\n     *\n     *         //clean all metric\n     *         MockEaseAgent.cleanAllMetric();\n     *         try {\n     *             lastJsonReporter.flushAndOnlyOne();\n     *             fail(\"must throw Exception\");\n     *         } catch (RuntimeException e) {\n     *             //throw Exception because it is empty.\n     *         }\n     * }</pre>\n     */\n    public static void cleanAllMetric() {\n        MockMetricProvider.clearAll();\n    }\n\n    /**\n     * clean MetricRegistry's metrics\n     * <p>\n     * see com.megaease.easeagent.mock.plugin.api.demo.MockEaseAgentTest#lastMetricJsonReporter\n     * <pre>${@code\n     *         //inc 1\n     *         metricRegistry.counter(nameFactory.counterName(key, MetricSubType.DEFAULT)).inc();\n     *         metric = lastJsonReporter.flushAndOnlyOne();\n     *         //count == 1\n     *         assertEquals(1, metric.get(MetricField.EXECUTION_COUNT.getField()));\n     *\n     *         //clean all metric\n     *         MockEaseAgent.cleanMetric(metricRegistry);\n     *         try {\n     *             lastJsonReporter.flushAndOnlyOne();\n     *             fail(\"must throw Exception\");\n     *         } catch (RuntimeException e) {\n     *             //throw Exception because it is empty.\n     *         }\n     * }</pre>\n     *\n     * @param metricRegistry ${@link com.megaease.easeagent.plugin.api.metric.MetricRegistry}\n     */\n    public static void cleanMetric(com.megaease.easeagent.plugin.api.metric.MetricRegistry metricRegistry) {\n        MetricTestUtils.clear(metricRegistry);\n    }\n\n    /**\n     * clean ServiceMetric's metrics\n     * <p>\n     * see com.megaease.easeagent.mock.plugin.api.demo.MockEaseAgentTest#lastMetricJsonReporter\n     *\n     * <pre>${@code\n     *         ServiceMetric serviceMetric = new ServiceMetric(metricRegistry, nameFactory) {\n     *         };\n     *         serviceMetric.counter(key, MetricSubType.DEFAULT).inc();\n     *         metric = lastJsonReporter.flushAndOnlyOne();\n     *         //count == 1\n     *         assertEquals(1, metric.get(MetricField.EXECUTION_COUNT.getField()));\n     *\n     *         //clean all metric\n     *         MockEaseAgent.cleanMetric(serviceMetric);\n     *         try {\n     *             lastJsonReporter.flushAndOnlyOne();\n     *             fail(\"must throw Exception\");\n     *         } catch (RuntimeException e) {\n     *             //throw Exception because it is empty.\n     *         }\n     * }</pre>\n     *\n     * @param serviceMetric ${@link ServiceMetric }\n     */\n    public static void cleanMetric(ServiceMetric serviceMetric) {\n        MetricTestUtils.clear(serviceMetric);\n    }\n}\n"
  },
  {
    "path": "mock/plugin-api-mock/src/main/java/com/megaease/easeagent/mock/plugin/api/junit/AfterStatement.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.mock.plugin.api.junit;\n\nimport com.megaease.easeagent.mock.zipkin.MockTracingProvider;\nimport org.junit.runners.model.FrameworkMethod;\nimport org.junit.runners.model.Statement;\n\npublic class AfterStatement extends Statement {\n    private final FrameworkMethod method;\n    private final Object target;\n    private final Statement previous;\n\n    public AfterStatement(FrameworkMethod method, Object target, Statement previous) {\n        this.method = method;\n        this.target = target;\n        this.previous = previous;\n    }\n\n    @Override\n    public void evaluate() throws Throwable {\n        this.previous.evaluate();\n        checkSpan();\n    }\n\n    private void checkSpan() throws ScopeMustBeCloseException {\n        if (MockTracingProvider.hashCurrentConetxt()) {\n            throw new ScopeMustBeCloseException(String.format(\"The Scope must be close after plugin. Also make sure the scop is closed if you don't test after. \\n\\tat %s.%s(%s.java)\",\n                target.getClass().getName(),\n                method.getName(),\n                target.getClass().getSimpleName()\n            ));\n        }\n        if (MockTracingProvider.hasPendingSpans()) {\n            MockTracingProvider.cleanPendingSpans();\n\n            throw new RuntimeException(String.format(\"The Span must be finish or abandon. \\n\\tat %s.%s(%s.java)\",\n                target.getClass().getName(),\n                method.getName(),\n                target.getClass().getSimpleName()\n            ));\n        }\n\n    }\n}\n"
  },
  {
    "path": "mock/plugin-api-mock/src/main/java/com/megaease/easeagent/mock/plugin/api/junit/BeforeStatement.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.mock.plugin.api.junit;\n\nimport com.megaease.easeagent.mock.plugin.api.utils.ContextUtils;\nimport com.megaease.easeagent.mock.report.MockReport;\nimport org.junit.runners.model.Statement;\n\npublic class BeforeStatement extends Statement {\n    private final Statement next;\n\n    public BeforeStatement(Statement next) {\n        this.next = next;\n    }\n\n    @Override\n    public void evaluate() throws Throwable {\n        cleanAll();\n        next.evaluate();\n    }\n\n    private void cleanAll() {\n        ContextUtils.resetAll();\n        MockReport.cleanReporter();\n    }\n\n}\n"
  },
  {
    "path": "mock/plugin-api-mock/src/main/java/com/megaease/easeagent/mock/plugin/api/junit/EaseAgentJunit4ClassRunner.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.mock.plugin.api.junit;\n\nimport com.megaease.easeagent.context.ContextManager;\nimport com.megaease.easeagent.mock.context.MockContextManager;\nimport org.junit.runners.BlockJUnit4ClassRunner;\nimport org.junit.runners.model.FrameworkMethod;\nimport org.junit.runners.model.InitializationError;\nimport org.junit.runners.model.Statement;\n\npublic final class EaseAgentJunit4ClassRunner extends BlockJUnit4ClassRunner {\n    public static final ContextManager ignored = MockContextManager.getContextManagerMock();\n\n    public EaseAgentJunit4ClassRunner(Class<?> testClass) throws InitializationError {\n        super(testClass);\n    }\n\n    @Override\n    protected Statement withBefores(FrameworkMethod method, Object target, Statement statement) {\n        return new BeforeStatement(super.withBefores(method, target, statement));\n    }\n\n    @Override\n    protected Statement withAfters(FrameworkMethod method, Object target, Statement statement) {\n        return new AfterStatement(method, target, super.withAfters(method, target, statement));\n    }\n}\n"
  },
  {
    "path": "mock/plugin-api-mock/src/main/java/com/megaease/easeagent/mock/plugin/api/junit/ScopeMustBeCloseException.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.mock.plugin.api.junit;\n\npublic class ScopeMustBeCloseException extends RuntimeException{\n    public ScopeMustBeCloseException(String message) {\n        super(message);\n    }\n}\n"
  },
  {
    "path": "mock/plugin-api-mock/src/main/java/com/megaease/easeagent/mock/plugin/api/utils/ConfigTestUtils.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.mock.plugin.api.utils;\n\nimport com.megaease.easeagent.config.ConfigUtils;\nimport com.megaease.easeagent.mock.config.MockConfig;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\n\nimport java.io.Closeable;\nimport java.util.Collections;\n\npublic class ConfigTestUtils {\n\n\n    /**\n     * change boolean ${@code property} to ${@code value } to ${@code iPluginConfig}\n     *\n     * @param iPluginConfig ${@link IPluginConfig} the config change for\n     * @param property      String the config property change for\n     * @param value         boolean the value change for\n     * @return Reset It must be call ${@link Reset#clone()} after your business.\n     */\n    public static Reset changeBoolean(IPluginConfig iPluginConfig, String property, boolean value) {\n        String name = ConfigUtils.buildPluginProperty(iPluginConfig.domain(), iPluginConfig.namespace(), iPluginConfig.id(), property);\n        Boolean oldValue = iPluginConfig.getBoolean(property);\n        MockConfig.getCONFIGS().updateConfigs(Collections.singletonMap(name, String.valueOf(value)));\n        return new Reset(name, String.valueOf(oldValue));\n    }\n\n\n    /**\n     * change String ${@code property} to ${@code value } to ${@code iPluginConfig}\n     *\n     * @param iPluginConfig ${@link IPluginConfig} the config change for\n     * @param property      String the config property change for\n     * @param value         String the value change for\n     * @return Reset It must be call ${@link Reset#clone()} after your business.\n     */\n\n    public static Reset changeString(IPluginConfig iPluginConfig, String property, String value) {\n        String name = ConfigUtils.buildPluginProperty(iPluginConfig.domain(), iPluginConfig.namespace(), iPluginConfig.id(), property);\n        String oldValue = iPluginConfig.getString(property);\n        MockConfig.getCONFIGS().updateConfigs(Collections.singletonMap(name, value));\n        return new Reset(name, oldValue);\n    }\n\n    /**\n     * change String ${@code name} to ${@code value } to global Configs\n     *\n     * @param name  String the config property change for\n     * @param value String the value change for\n     * @return Reset It must be call ${@link Reset#clone()} after your business.\n     */\n\n    public static Reset changeConfig(String name, String value) {\n        String oldValue = MockConfig.getCONFIGS().getString(name);\n        MockConfig.getCONFIGS().updateConfigs(Collections.singletonMap(name, value));\n        return new Reset(name, oldValue);\n    }\n\n    /**\n     * A Reset for Configs\n     * It must be call ${@link Reset#close()} after your business.\n     *\n     * <pre>${@code\n     *  try (ConfigTestUtils.Reset ignored = ConfigTestUtils.changeConfig(key, value)) {\n     *      //do test\n     *  }\n     * }</pre>\n     */\n    public static class Reset implements Closeable {\n        private final String name;\n        private final String value;\n\n        public Reset(String name, String value) {\n            this.name = name;\n            this.value = value;\n        }\n\n        @Override\n        public void close() {\n            MockConfig.getCONFIGS().updateConfigs(Collections.singletonMap(name, value));\n        }\n    }\n}\n"
  },
  {
    "path": "mock/plugin-api-mock/src/main/java/com/megaease/easeagent/mock/plugin/api/utils/ContextUtils.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.mock.plugin.api.utils;\n\nimport com.megaease.easeagent.mock.metrics.MockMetricProvider;\nimport com.megaease.easeagent.mock.report.MockReport;\nimport com.megaease.easeagent.mock.zipkin.MockTracingProvider;\nimport com.megaease.easeagent.plugin.api.middleware.Redirect;\nimport com.megaease.easeagent.plugin.api.middleware.RedirectProcessor;\nimport com.megaease.easeagent.plugin.api.middleware.ResourceConfig;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\npublic class ContextUtils {\n\n    /**\n     * reset all of context\n     */\n    public static void resetAll() {\n        EaseAgent.initializeContextSupplier.getContext().clear();\n        MockMetricProvider.clearAll();\n        OldRedirect.resetRedirect();\n        MockTracingProvider.cleanCurrentSpan();\n        MockTracingProvider.cleanPendingSpans();\n        MockReport.cleanLastSpan();\n        MockReport.cleanLastAccessLog();\n        MockReport.cleanSkipSpan();\n    }\n\n    static class OldRedirect {\n        static final Map<Redirect, ResourceConfig> OLD_CONFIG;\n        static final Map<String, String> OLD_EASEMESH_TAGS = RedirectProcessor.tags();\n\n        static {\n            Map<Redirect, ResourceConfig> oldConfig = new HashMap<>();\n            for (Redirect redirect : Redirect.values()) {\n                oldConfig.put(redirect, redirect.getConfig());\n            }\n            OLD_CONFIG = oldConfig;\n\n        }\n\n        private static void resetRedirect() {\n            for (Map.Entry<Redirect, ResourceConfig> entry : OLD_CONFIG.entrySet()) {\n                if (entry.getKey().getConfig() == entry.getValue()) {\n                    continue;\n                }\n                AgentFieldReflectAccessor.setFieldValue(entry.getKey(), \"config\", entry.getValue());\n            }\n            AgentFieldReflectAccessor.setFieldValue(RedirectProcessor.INSTANCE, \"tags\", OLD_EASEMESH_TAGS);\n        }\n    }\n}\n"
  },
  {
    "path": "mock/plugin-api-mock/src/main/java/com/megaease/easeagent/mock/plugin/api/utils/InterceptorTestUtils.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.mock.plugin.api.utils;\n\nimport com.megaease.easeagent.plugin.AgentPlugin;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\n\npublic class InterceptorTestUtils {\n\n    /**\n     * call ${@link Interceptor#init(IPluginConfig, int)} by AgentPlugin\n     *\n     * @param interceptor ${@link Interceptor}\n     * @param agentPlugin ${@link AgentPlugin}\n     */\n    public static void initByUnique(Interceptor interceptor, AgentPlugin agentPlugin) {\n        IPluginConfig iPluginConfig = EaseAgent.getConfig(agentPlugin.getDomain(), agentPlugin.getNamespace(), interceptor.getType());\n        interceptor.init(iPluginConfig, 0);\n    }\n\n    /**\n     * call ${@link Interceptor#init(IPluginConfig, String, String, String)} by AgentPlugin, className=\"\", methodName=\"\", methodDescriptor=\"\"\n     *\n     * @param interceptor ${@link Interceptor}\n     * @param agentPlugin ${@link AgentPlugin}\n     */\n    public static void init(Interceptor interceptor, AgentPlugin agentPlugin) {\n        init(interceptor, agentPlugin, \"\", \"\", \"\");\n    }\n\n    /**\n     * call ${@link Interceptor#init(IPluginConfig, String, String, String)} by AgentPlugin, className, methodName, methodDescriptor\n     *\n     * @param interceptor      ${@link Interceptor}\n     * @param agentPlugin      ${@link AgentPlugin}\n     * @param className        String\n     * @param methodName       String\n     * @param methodDescriptor String\n     */\n    public static void init(Interceptor interceptor, AgentPlugin agentPlugin, String className, String methodName, String methodDescriptor) {\n        IPluginConfig iPluginConfig = EaseAgent.getConfig(agentPlugin.getDomain(), agentPlugin.getNamespace(), interceptor.getType());\n        interceptor.init(iPluginConfig, className, methodName, methodDescriptor);\n    }\n}\n"
  },
  {
    "path": "mock/plugin-api-mock/src/main/java/com/megaease/easeagent/mock/plugin/api/utils/SpanTestUtils.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.mock.plugin.api.utils;\n\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\n\nimport static org.junit.Assert.assertEquals;\n\npublic class SpanTestUtils {\n    /**\n     * verify Span id and ReportSpan id is same\n     *\n     * @param span     ${@link Span}\n     * @param mockSpan ${@link ReportSpan}\n     */\n    public static void sameId(Span span, ReportSpan mockSpan) {\n        assertEquals(span.traceIdString(), mockSpan.traceId());\n        assertEquals(span.spanIdString(), mockSpan.id());\n        assertEquals(span.parentIdString(), mockSpan.parentId());\n    }\n}\n"
  },
  {
    "path": "mock/plugin-api-mock/src/main/java/com/megaease/easeagent/mock/plugin/api/utils/TagVerifier.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.mock.plugin.api.utils;\n\nimport com.megaease.easeagent.plugin.api.metric.name.Tags;\nimport com.megaease.easeagent.plugin.utils.Pair;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.function.Predicate;\n\n/**\n * a tag verifier for metric filter\n *\n * @see com.megaease.easeagent.mock.plugin.api.MockEaseAgent#lastMetricJsonReporter(Predicate)\n */\npublic class TagVerifier {\n    List<Pair<String, String>> tags = new ArrayList<>();\n\n    public TagVerifier add(String name, String value) {\n        tags.add(new Pair<>(name, value));\n        return this;\n    }\n\n    public boolean verifyAnd(Map<String, Object> map) {\n        for (Pair<String, String> tag : tags) {\n            if (!contains(map, tag.getKey(), tag.getValue())) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n\n    private boolean contains(Map<String, Object> map, String key, String value) {\n        Object v = map.get(key);\n        if (!(v instanceof String)) {\n            return false;\n        }\n        return v.equals(value);\n    }\n\n    /**\n     * build a TagVerifier by Tags and key\n     *\n     * @param tags ${@link Tags}\n     * @param key  tags.getKeyFieldName()'s value\n     * @return\n     */\n    public static TagVerifier build(Tags tags, String key) {\n        return new TagVerifier()\n            .add(Tags.CATEGORY, tags.getCategory())\n            .add(Tags.TYPE, tags.getType())\n            .add(tags.getKeyFieldName(), key);\n    }\n}\n"
  },
  {
    "path": "mock/plugin-api-mock/src/test/java/com/megaease/easeagent/mock/plugin/api/TestContext.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.mock.plugin.api;\n\nimport com.megaease.easeagent.mock.context.MockContextManager;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport org.junit.Test;\n\nimport static junit.framework.TestCase.assertFalse;\nimport static junit.framework.TestCase.assertNotNull;\n\npublic class TestContext {\n    @Test\n    public void testSpan() throws InterruptedException {\n        Context context = MockContextManager.getContext();\n        assertNotNull(context);\n        assertNotNull(context.currentTracing());\n        assertFalse(context.isNoop());\n        assertFalse(context.currentTracing().isNoop());\n        final Span span = context.nextSpan();\n        assertNotNull(span);\n        assertFalse(span.isNoop());\n        span.cacheScope();\n        span.start();\n        span.finish();\n//        Thread.sleep(10000);\n    }\n}\n"
  },
  {
    "path": "mock/plugin-api-mock/src/test/java/com/megaease/easeagent/mock/plugin/api/TestEaseAgent.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.mock.plugin.api;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.mock.report.MockReport;\nimport com.megaease.easeagent.plugin.api.Reporter;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.logging.Logger;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.report.EncodedData;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport java.util.concurrent.atomic.AtomicReference;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class TestEaseAgent {\n    @Test\n    public void testEaseAgent() {\n        assertNotNull(EaseAgent.initializeContextSupplier);\n        assertNotNull(EaseAgent.getContext());\n        assertFalse(EaseAgent.getContext().isNoop());\n        assertNotNull(EaseAgent.configFactory);\n        assertNotNull(EaseAgent.configFactory.getConfig(\"name\"));\n        IPluginConfig config = EaseAgent.configFactory.getConfig(\"test1\", \"test2\", \"test3\");\n        assertNotNull(config);\n        assertNotNull(EaseAgent.metricRegistrySupplier);\n        assertNotNull(EaseAgent.metricRegistrySupplier.reporter(config));\n        AtomicReference<String> message = new AtomicReference<>(\"\");\n        MockReport.setMockMetricReport(new Reporter() {\n            @Override\n            public void report(String msg) {\n                message.set(msg);\n            }\n\n            @Override\n            public void report(EncodedData msg) {\n                message.set(new String(msg.getData()));\n            }\n        });\n        EaseAgent.metricRegistrySupplier.reporter(config).report(\"test\");\n        assertEquals(\"test\",message.get());\n        Logger logger = EaseAgent.getLogger(TestEaseAgent.class);\n        logger.info(\"-------------------- easeagent test\");\n    }\n}\n"
  },
  {
    "path": "mock/plugin-api-mock/src/test/java/com/megaease/easeagent/mock/plugin/api/demo/InterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.mock.plugin.api.demo;\n\nimport com.megaease.easeagent.mock.plugin.api.MockEaseAgent;\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.mock.plugin.api.utils.SpanTestUtils;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static org.junit.Assert.assertNotNull;\nimport static org.junit.Assert.assertNull;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class InterceptorTest {\n    @Test\n    public void testBeforeAfter() {\n        assertNull(MockEaseAgent.getLastSpan());\n        final Object key = new Object();\n        Interceptor interceptor = new Interceptor() {\n            @Override\n            public int order() {\n                return Order.HIGH.getOrder();\n            }\n\n            @Override\n            public void before(MethodInfo methodInfo, Context context) {\n                Span span = context.nextSpan();\n                span.start();\n                context.put(key, span);\n            }\n\n            @Override\n            public void after(MethodInfo methodInfo, Context context) {\n                Span span = context.remove(key);\n                span.finish();\n            }\n        };\n        Context context = EaseAgent.getContext();\n        interceptor.before(null, context);\n        Span span = context.get(key);\n        assertNotNull(span);\n        assertNull(MockEaseAgent.getLastSpan());\n        interceptor.after(null, context);\n        ReportSpan reportSpan = MockEaseAgent.getLastSpan();\n        SpanTestUtils.sameId(span, reportSpan);\n    }\n}\n"
  },
  {
    "path": "mock/plugin-api-mock/src/test/java/com/megaease/easeagent/mock/plugin/api/demo/M1MetricCollect.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.mock.plugin.api.demo;\n\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.metric.Meter;\nimport com.megaease.easeagent.plugin.api.metric.MetricRegistry;\nimport com.megaease.easeagent.plugin.api.metric.ServiceMetric;\nimport com.megaease.easeagent.plugin.api.metric.ServiceMetricSupplier;\nimport com.megaease.easeagent.plugin.api.metric.name.*;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.utils.ImmutableMap;\n\nimport javax.annotation.Nonnull;\n\npublic class M1MetricCollect {\n    static final M1Metric m1Metric;\n\n    static {\n        IPluginConfig config = EaseAgent.getConfig(\"observability\", \"collectM1\", ConfigConst.PluginID.METRIC);\n        Tags tags = new Tags(\"application\", \"http-request\", \"url\").put(\"city\", \"beijing\");\n        ServiceMetricSupplier<M1Metric> m1MetricSupplier = new ServiceMetricSupplier<M1Metric>() {\n            @Override\n            public NameFactory newNameFactory() {\n                return NameFactory.createBuilder()\n                    .meterType(MetricSubType.DEFAULT, ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                        .put(MetricField.M1_RATE, MetricValueFetcher.MeteredM1RateIgnoreZero)\n                        .build())\n                    .build();\n            }\n\n            @Override\n            public M1Metric newInstance(MetricRegistry metricRegistry, NameFactory nameFactory) {\n                return new M1Metric(metricRegistry, nameFactory);\n            }\n        };\n\n        m1Metric = EaseAgent.getOrCreateServiceMetric(config, tags, m1MetricSupplier);\n    }\n\n    public void collectM1() {\n        String url = \"GET /web_client\";\n        for (int i = 0; i < 100; i++) {\n            m1Metric.collectMetric(url);\n        }\n    }\n\n    public static class M1Metric extends ServiceMetric {\n\n        public M1Metric(@Nonnull MetricRegistry metricRegistry, @Nonnull NameFactory nameFactory) {\n            super(metricRegistry, nameFactory);\n        }\n\n        public void collectMetric(String key) {\n            final Meter meter = meter(key, MetricSubType.DEFAULT);\n            meter.mark();\n        }\n    }\n}\n"
  },
  {
    "path": "mock/plugin-api-mock/src/test/java/com/megaease/easeagent/mock/plugin/api/demo/MockEaseAgentTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.mock.plugin.api.demo;\n\nimport com.megaease.easeagent.config.Configs;\nimport com.megaease.easeagent.mock.plugin.api.MockEaseAgent;\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.mock.plugin.api.utils.SpanTestUtils;\nimport com.megaease.easeagent.mock.plugin.api.utils.TagVerifier;\nimport com.megaease.easeagent.mock.report.impl.LastJsonReporter;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.metric.MetricRegistry;\nimport com.megaease.easeagent.plugin.api.metric.ServiceMetric;\nimport com.megaease.easeagent.plugin.api.metric.name.*;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport com.megaease.easeagent.plugin.utils.ImmutableMap;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class MockEaseAgentTest {\n\n    @Test\n    public void getLastSpan() {\n        Span span = EaseAgent.getContext().nextSpan();\n        span.start().finish();\n        assertNotNull(MockEaseAgent.getLastSpan());\n        SpanTestUtils.sameId(span, MockEaseAgent.getLastSpan());\n    }\n\n    @Test\n    public void cleanLastSpan() {\n        Span span = EaseAgent.getContext().nextSpan();\n        span.start().finish();\n        assertNotNull(MockEaseAgent.getLastSpan());\n        SpanTestUtils.sameId(span, MockEaseAgent.getLastSpan());\n        MockEaseAgent.cleanLastSpan();\n        assertNull(MockEaseAgent.getLastSpan());\n    }\n\n    @Test\n    public void setMockSpanReport() {\n        List<ReportSpan> spans = new ArrayList<>();\n        MockEaseAgent.setMockSpanReport(spans::add);\n        Span span1 = EaseAgent.getContext().nextSpan();\n        span1.start().finish();\n        Span span2 = EaseAgent.getContext().nextSpan();\n        span2.start().finish();\n        assertEquals(2, spans.size());\n        SpanTestUtils.sameId(span1, spans.get(0));\n        SpanTestUtils.sameId(span2, spans.get(1));\n    }\n\n\n    @Test\n    public void lastMetricJsonReporter() {\n        //Interceptor init\n        IPluginConfig config = EaseAgent.getConfig(\"observability\", \"lastMetricJsonReporter\", ConfigConst.PluginID.METRIC);\n        NameFactory nameFactory = NameFactory.createBuilder().counterType(MetricSubType.DEFAULT,\n            ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                .put(MetricField.EXECUTION_COUNT, MetricValueFetcher.CountingCount).build()\n        ).build();\n        String type = \"lastMetricJsonReporterType\";\n        Tags tags = new Tags(\"testCategory\", type, \"testName\");\n        MetricRegistry metricRegistry = EaseAgent.newMetricRegistry(config, nameFactory, tags);\n\n        //Interceptor after\n        String key = \"lastMetricJsonReporterKey\";\n        metricRegistry.counter(nameFactory.counterName(key, MetricSubType.DEFAULT)).inc();\n\n        //verify metric count==1\n        LastJsonReporter lastJsonReporter = MockEaseAgent.lastMetricJsonReporter(TagVerifier.build(tags, key)::verifyAnd);\n        Map<String, Object> metric = lastJsonReporter.flushAndOnlyOne();\n        assertEquals(1, metric.get(MetricField.EXECUTION_COUNT.getField()));\n\n        //reset all to empty.\n        MockEaseAgent.resetAll();\n        try {\n            lastJsonReporter.flushAndOnlyOne();\n            fail(\"must throw Exception\");\n        } catch (RuntimeException e) {\n            //throw Exception because it is empty.\n        }\n\n        //inc 1\n        metricRegistry.counter(nameFactory.counterName(key, MetricSubType.DEFAULT)).inc();\n        metric = lastJsonReporter.flushAndOnlyOne();\n        //count == 1\n        assertEquals(1, metric.get(MetricField.EXECUTION_COUNT.getField()));\n\n        //clean all metric\n        MockEaseAgent.cleanAllMetric();\n        try {\n            lastJsonReporter.flushAndOnlyOne();\n            fail(\"must throw Exception\");\n        } catch (RuntimeException e) {\n            //throw Exception because it is empty.\n        }\n\n        //inc 1\n        metricRegistry.counter(nameFactory.counterName(key, MetricSubType.DEFAULT)).inc();\n        metric = lastJsonReporter.flushAndOnlyOne();\n        //count == 1\n        assertEquals(1, metric.get(MetricField.EXECUTION_COUNT.getField()));\n\n        //clean all metric\n        MockEaseAgent.cleanMetric(metricRegistry);\n        try {\n            lastJsonReporter.flushAndOnlyOne();\n            fail(\"must throw Exception\");\n        } catch (RuntimeException e) {\n            //throw Exception because it is empty.\n        }\n\n        ServiceMetric serviceMetric = new ServiceMetric(metricRegistry, nameFactory) {\n        };\n        serviceMetric.counter(key, MetricSubType.DEFAULT).inc();\n        metric = lastJsonReporter.flushAndOnlyOne();\n        //count == 1\n        assertEquals(1, metric.get(MetricField.EXECUTION_COUNT.getField()));\n\n        //clean all metric\n        MockEaseAgent.cleanMetric(serviceMetric);\n        try {\n            lastJsonReporter.flushAndOnlyOne();\n            fail(\"must throw Exception\");\n        } catch (RuntimeException e) {\n            //throw Exception because it is empty.\n        }\n\n    }\n\n    @Test\n    public void getConfigs() {\n        Configs configs = MockEaseAgent.getConfigs();\n        String name = configs.getString(\"name\");\n        assertNotNull(name);\n    }\n\n    @Test\n    public void resetAll() {\n        Context context = EaseAgent.getContext();\n        String key = \"testKey\";\n        context.put(key, \"value\");\n        assertNotNull(context.get(key));\n        MockEaseAgent.resetAll();\n        assertNull(context.get(key));\n\n    }\n\n    @Test\n    public void cleanAllMetric() {\n        lastMetricJsonReporter();\n    }\n\n    @Test\n    public void cleanMetric() {\n        lastMetricJsonReporter();\n    }\n\n    @Test\n    public void clearMetric() {\n        lastMetricJsonReporter();\n    }\n}\n"
  },
  {
    "path": "mock/plugin-api-mock/src/test/java/com/megaease/easeagent/mock/plugin/api/utils/ConfigTestUtilsTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.mock.plugin.api.utils;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class ConfigTestUtilsTest {\n\n    @Test\n    public void changeBoolean() {\n        IPluginConfig config = EaseAgent.getOrCreateAutoRefreshConfig(\"observability\", \"changeBoolean\", ConfigConst.PluginID.METRIC);\n        assertTrue(config.enabled());\n        try (ConfigTestUtils.Reset ignored = ConfigTestUtils.changeBoolean(config, \"enabled\", false)) {\n            assertFalse(config.enabled());\n        }\n        assertTrue(config.enabled());\n    }\n\n    @Test\n    public void changeString() {\n        IPluginConfig config = EaseAgent.getOrCreateAutoRefreshConfig(\"observability\", \"changeString\", ConfigConst.PluginID.METRIC);\n        String key = \"testKey\";\n        String value = \"testValue\";\n        assertNull(config.getString(key));\n        try (ConfigTestUtils.Reset ignored = ConfigTestUtils.changeString(config, key, value)) {\n            assertEquals(value, config.getString(key));\n        }\n        assertNull(config.getString(key));\n    }\n\n    @Test\n    public void changeConfig() {\n        String key = \"testChangeConfigKey\";\n        String value = \"testChangeConfigValue\";\n        assertNull(EaseAgent.getConfig(key));\n        try (ConfigTestUtils.Reset ignored = ConfigTestUtils.changeConfig(key, value)) {\n            assertEquals(value, EaseAgent.getConfig(key));\n        }\n        assertNull(EaseAgent.getConfig(key));\n    }\n}\n"
  },
  {
    "path": "mock/plugin-api-mock/src/test/resources/log4j2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Configuration status=\"WARN\">\n    <Appenders>\n        <Console name=\"Console\" target=\"SYSTEM_OUT\">\n            <!--<PatternLayout pattern=\"%d{HH:mm:ss.SSS} [%10.10t] %-p %10.10c{1} - %msg%n\"/>-->\n            <PatternLayout pattern=\"%d{HH:mm:ss.SSS} %-5level %class{36}:%L (%X{testMdc}) - %msg%xEx%n\"/>\n        </Console>\n    </Appenders>\n    <Loggers>\n        <Root level=\"INFO\">\n            <AppenderRef ref=\"Console\"/>\n        </Root>\n    </Loggers>\n</Configuration>\n"
  },
  {
    "path": "mock/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright (c) 2021, MegaEase\n  ~ All rights reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~   http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>easeagent</artifactId>\n        <groupId>com.megaease.easeagent</groupId>\n        <version>2.3.0</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>mock</artifactId>\n    <packaging>pom</packaging>\n    <modules>\n        <module>log4j2-mock</module>\n        <module>config-mock</module>\n        <module>context-mock</module>\n        <module>report-mock</module>\n        <module>metrics-mock</module>\n        <module>utils-mock</module>\n        <module>zipkin-mock</module>\n        <module>plugin-api-mock</module>\n    </modules>\n\n\n</project>\n"
  },
  {
    "path": "mock/report-mock/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright (c) 2021, MegaEase\n  ~ All rights reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~   http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>mock</artifactId>\n        <groupId>com.megaease.easeagent</groupId>\n        <version>2.3.0</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>report-mock</artifactId>\n\n\n    <dependencies>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>report</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>config-mock</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>com.google.guava</groupId>\n            <artifactId>guava</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>log4j2-mock</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n    </dependencies>\n</project>\n"
  },
  {
    "path": "mock/report-mock/src/main/java/com/megaease/easeagent/mock/report/JsonReporter.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.mock.report;\n\nimport java.util.List;\nimport java.util.Map;\n\npublic interface JsonReporter {\n    void report(List<Map<String, Object>> json);\n}\n"
  },
  {
    "path": "mock/report-mock/src/main/java/com/megaease/easeagent/mock/report/MetricFlushable.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.mock.report;\n\npublic interface MetricFlushable {\n    void flush();\n}\n"
  },
  {
    "path": "mock/report-mock/src/main/java/com/megaease/easeagent/mock/report/MockAtomicReferenceReportSpanReport.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.mock.report;\n\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\n\nimport java.util.concurrent.atomic.AtomicReference;\n\npublic class MockAtomicReferenceReportSpanReport implements MockSpanReport {\n    AtomicReference<ReportSpan> spanAtomicReference = new AtomicReference<>();\n\n    @Override\n    public void report(ReportSpan span) {\n        spanAtomicReference.set(span);\n    }\n\n    public ReportSpan get() {\n        return spanAtomicReference.get();\n    }\n}\n"
  },
  {
    "path": "mock/report-mock/src/main/java/com/megaease/easeagent/mock/report/MockReport.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.mock.report;\n\nimport com.megaease.easeagent.log4j2.Logger;\nimport com.megaease.easeagent.log4j2.LoggerFactory;\nimport com.megaease.easeagent.mock.config.MockConfig;\nimport com.megaease.easeagent.mock.report.impl.LastJsonReporter;\nimport com.megaease.easeagent.plugin.api.Reporter;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.logging.AccessLogInfo;\nimport com.megaease.easeagent.plugin.api.otlp.common.AgentLogData;\nimport com.megaease.easeagent.plugin.report.EncodedData;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport com.megaease.easeagent.plugin.utils.common.JsonUtil;\nimport com.megaease.easeagent.plugin.report.AgentReport;\nimport com.megaease.easeagent.report.DefaultAgentReport;\nimport com.megaease.easeagent.plugin.report.metric.MetricReporterFactory;\nimport com.megaease.easeagent.report.util.SpanUtils;\n\nimport javax.annotation.Nonnull;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.function.Predicate;\n\npublic class MockReport {\n    private static final Logger LOGGER = LoggerFactory.getLogger(MockReport.class);\n    private static final AgentReport AGENT_REPORT = new MockAgentReport(DefaultAgentReport.create(MockConfig.getCONFIGS()));\n\n    private static final AtomicReference<AccessLogInfo> LAST_ACCESS_LOG = new AtomicReference<>();\n    private static final AtomicReference<io.opentelemetry.sdk.logs.data.LogData> LAST_APP_LOG = new AtomicReference<>();\n    private static final AtomicReference<ReportSpan> LAST_SPAN = new AtomicReference<>();\n    private static final AtomicReference<ReportSpan> LAST_SKIP_SPAN = new AtomicReference<>();\n    private static volatile MetricFlushable metricFlushable;\n    private static volatile MockSpanReport mockSpanReport = null;\n    private static volatile Reporter metricReportMock = null;\n    private static volatile JsonReporter metricJsonReport = null;\n\n    public static AgentReport getAgentReport() {\n        return AGENT_REPORT;\n    }\n\n    public static void setMetricFlushable(MetricFlushable metricFlushable) {\n        MockReport.metricFlushable = metricFlushable;\n    }\n\n    public static void setMockSpanReport(MockSpanReport mockSpanReport) {\n        MockReport.mockSpanReport = mockSpanReport;\n    }\n\n    public static void setMockMetricReport(Reporter metricReportMock) {\n        MockReport.metricReportMock = metricReportMock;\n    }\n\n    public static LastJsonReporter lastMetricJsonReporter(Predicate<Map<String, Object>> filter) {\n        LastJsonReporter lastJsonReporter = new LastJsonReporter(filter, metricFlushable);\n        metricJsonReport = lastJsonReporter;\n        return lastJsonReporter;\n    }\n\n    public static void cleanReporter() {\n        mockSpanReport = null;\n        metricReportMock = null;\n        metricJsonReport = null;\n    }\n\n\n    private static void reportMetricToJson(String text) {\n        if (metricJsonReport == null) {\n            return;\n        }\n        try {\n            List<Map<String, Object>> json;\n            if (text.trim().startsWith(\"{\")) {\n                Map<String, Object> jsonMap = JsonUtil.toMap(text);\n                json = Collections.singletonList(jsonMap);\n            } else {\n                json = JsonUtil.toList(text);\n            }\n            metricJsonReport.report(json);\n        } catch (Exception e) {\n            LOGGER.error(\"string to List<Map<String, Object>> fail: {}\", e.getMessage());\n        }\n    }\n\n    static class MockAgentReport implements AgentReport {\n        private final AgentReport agentReport;\n        private final MockMetricReporterFactory pluginMetricReporter;\n\n        MockAgentReport(AgentReport agentReport) {\n            this.agentReport = agentReport;\n            this.pluginMetricReporter = new MockMetricReporterFactory(agentReport.metricReporter());\n        }\n\n        @Override\n        public void report(ReportSpan span) {\n            agentReport.report(span);\n            if (!SpanUtils.isValidSpan(span)) {\n                LOGGER.warn(\"span<traceId({}), id({}), name({}), kind({})> not start(), skip it.\", span.traceId(), span.id(), span.name(), span.kind());\n                LAST_SKIP_SPAN.set(span);\n                return;\n            }\n            if (span.duration() == 0) {\n                LOGGER.warn(String.format(\"span<traceId(%s), id(%s), name(%s), kind(%s), timestamp(%s) duration(%s) not finish, skip it.\", span.traceId(), span.id(), span.name(), span.kind(), span.timestamp(), span.duration()));\n                LAST_SKIP_SPAN.set(span);\n                return;\n            }\n            // MockSpan mockSpan = new ZipkinMockSpanImpl(span);\n            LAST_SPAN.set(span);\n            try {\n                MockSpanReport mockSpanReport = MockReport.mockSpanReport;\n                if (mockSpanReport != null) {\n                    mockSpanReport.report(span);\n                }\n            } catch (Exception e) {\n                LOGGER.error(\"mock span report : {}\", e);\n            }\n        }\n\n        @Override\n        public void report(AccessLogInfo log) {\n            // this.agentReport.report(log);\n            LAST_ACCESS_LOG.set(log);\n        }\n\n        @Override\n        public void report(AgentLogData log) {\n            LAST_APP_LOG.set(log);\n        }\n\n        @Override\n        public MetricReporterFactory metricReporter() {\n            return pluginMetricReporter;\n        }\n    }\n\n    static class MockMetricReporterFactory implements MetricReporterFactory {\n        private final MetricReporterFactory metricReporterFactory;\n\n        MockMetricReporterFactory(@Nonnull MetricReporterFactory metricReporterFactory) {\n            this.metricReporterFactory = metricReporterFactory;\n        }\n\n        @Override\n        public Reporter reporter(IPluginConfig config) {\n            return new MockReporter(metricReporterFactory.reporter(config));\n        }\n    }\n\n    static class MockReporter implements Reporter {\n        private final Reporter reporter;\n\n        MockReporter(@Nonnull Reporter reporter) {\n            this.reporter = reporter;\n        }\n\n        @Override\n        public void report(String msg) {\n            reporter.report(msg);\n            try {\n                Reporter reportMock = metricReportMock;\n                if (reportMock != null) {\n                    reportMock.report(msg);\n                }\n            } catch (Exception e) {\n                LOGGER.error(\"mock metric report fail: {}\", e);\n            }\n            reportMetricToJson(msg);\n        }\n\n        @Override\n        public void report(EncodedData msg) {\n            this.report(new String(msg.getData()));\n        }\n    }\n\n    public static ReportSpan getLastSpan() {\n        return LAST_SPAN.get();\n    }\n\n    public static void cleanLastSpan() {\n        LAST_SPAN.set(null);\n    }\n\n    public static AccessLogInfo getLastAccessLog() {\n        return LAST_ACCESS_LOG.get();\n    }\n\n    public static void cleanLastAccessLog() {\n        LAST_ACCESS_LOG.set(null);\n    }\n\n    public static io.opentelemetry.sdk.logs.data.LogData getLastAppLog() {\n        return LAST_APP_LOG.get();\n    }\n\n    public static void cleanLastAppLog() {\n        LAST_APP_LOG.set(null);\n    }\n\n    public static ReportSpan getLastSkipSpan() {\n        return LAST_SKIP_SPAN.get();\n    }\n\n    public static void cleanSkipSpan() {\n        LAST_SKIP_SPAN.set(null);\n    }\n}\n"
  },
  {
    "path": "mock/report-mock/src/main/java/com/megaease/easeagent/mock/report/MockSpan.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.mock.report;\n\nimport com.megaease.easeagent.plugin.api.trace.Span;\n\nimport java.util.Map;\n\npublic interface MockSpan {\n    Span.Kind kind();\n\n    String traceId();\n\n    String spanId();\n\n    String parentId();\n\n    String tag(String key);\n\n    Map<String,String> tags();\n\n    String remoteServiceName();\n\n    String annotationValueAt(int i);\n\n    long timestamp();\n\n    Long duration();\n\n    int annotationCount();\n\n    int remotePort();\n\n    int localPort();\n\n    String remoteIp();\n\n    String localIp();\n\n    String name();\n\n    String localServiceName();\n\n    Boolean shared();\n\n    int tagCount();\n\n    boolean hasError();\n\n    String errorInfo();\n}\n"
  },
  {
    "path": "mock/report-mock/src/main/java/com/megaease/easeagent/mock/report/MockSpanReport.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.mock.report;\n\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\n\npublic interface MockSpanReport {\n    void report(ReportSpan span);\n}\n"
  },
  {
    "path": "mock/report-mock/src/main/java/com/megaease/easeagent/mock/report/impl/LastJsonReporter.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.mock.report.impl;\n\nimport com.megaease.easeagent.mock.report.JsonReporter;\nimport com.megaease.easeagent.mock.report.MetricFlushable;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.function.Predicate;\n\npublic class LastJsonReporter implements JsonReporter {\n    private final MetricFlushable metricFlushable;\n    private final AtomicReference<List<Map<String, Object>>> reference = new AtomicReference<>();\n    private final Predicate<Map<String, Object>> filter;\n\n    public LastJsonReporter(Predicate<Map<String, Object>> filter, MetricFlushable metricFlushable) {\n        this.filter = filter;\n        this.metricFlushable = metricFlushable;\n    }\n\n    @Override\n    public void report(List<Map<String, Object>> json) {\n        if (filter == null) {\n            reference.set(json);\n        }\n        List<Map<String, Object>> result = new ArrayList<>();\n        for (Map<String, Object> stringObjectMap : json) {\n            if (filter.test(stringObjectMap)) {\n                result.add(stringObjectMap);\n            }\n        }\n        if (!result.isEmpty()) {\n            reference.set(result);\n        }\n    }\n\n    /**\n     * get only one metrics and verify it is only one metrics.\n     *\n     * @return Map of metrics\n     * @throws RuntimeException if metrics is null or empty or metricSize!=1.\n     */\n    public Map<String, Object> getLastOnlyOne() {\n        List<Map<String, Object>> metrics = getLast();\n        if (metrics.size() != 1) {\n            throw new RuntimeException(\"metrics size is not 1 \");\n        }\n        return metrics.get(0);\n    }\n\n\n    /**\n     * get last metrics and verify it is not null or empty.\n     *\n     * @return list of metric\n     * @throws RuntimeException if metric is null or empty.\n     */\n    public List<Map<String, Object>> getLast() {\n        List<Map<String, Object>> metric = reference.get();\n        if (metric == null || metric.isEmpty()) {\n            throw new RuntimeException(\"metric must not be null and empty.\");\n        }\n        return metric;\n    }\n\n    /**\n     * clean then flush and get only one metrics and verify it is only one metric.\n     *\n     * @return Map of metrics\n     * @throws RuntimeException if metrics is null or empty or metricSize!=1.\n     */\n    public Map<String, Object> flushAndOnlyOne() {\n        List<Map<String, Object>> metrics = flushAndGet();\n        if (metrics.size() != 1) {\n            throw new RuntimeException(\"metrics size is not 1 \");\n        }\n        return metrics.get(0);\n    }\n\n    /**\n     * clean then flush and get metrics and verify it is not null or empty.\n     *\n     * @return list of metrics\n     * @throws RuntimeException if metrics is null or empty.\n     */\n    public List<Map<String, Object>> flushAndGet() {\n        clean();\n        metricFlushable.flush();\n        return getLast();\n    }\n\n    public void clean() {\n        reference.set(null);\n    }\n}\n"
  },
  {
    "path": "mock/report-mock/src/main/java/com/megaease/easeagent/mock/report/impl/ZipkinMockSpanImpl.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.mock.report.impl;\n\nimport com.megaease.easeagent.mock.report.MockSpan;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport zipkin2.Span;\nimport com.megaease.easeagent.plugin.report.tracing.Annotation;\n\nimport javax.annotation.Nonnull;\nimport java.util.*;\n\npublic class ZipkinMockSpanImpl implements MockSpan {\n    private static final Map<String, com.megaease.easeagent.plugin.api.trace.Span.Kind> KINDS;\n\n    static {\n        Map<String, com.megaease.easeagent.plugin.api.trace.Span.Kind> kinds = new HashMap<>();\n        kinds.put(Span.Kind.CLIENT.name(), com.megaease.easeagent.plugin.api.trace.Span.Kind.CLIENT);\n        kinds.put(Span.Kind.SERVER.name(), com.megaease.easeagent.plugin.api.trace.Span.Kind.SERVER);\n        kinds.put(Span.Kind.PRODUCER.name(), com.megaease.easeagent.plugin.api.trace.Span.Kind.PRODUCER);\n        kinds.put(Span.Kind.CONSUMER.name(), com.megaease.easeagent.plugin.api.trace.Span.Kind.CONSUMER);\n        KINDS = Collections.unmodifiableMap(kinds);\n    }\n\n\n    private final ReportSpan span;\n\n    public ZipkinMockSpanImpl(@Nonnull ReportSpan span) {\n        this.span = span;\n    }\n\n    @Override\n    public com.megaease.easeagent.plugin.api.trace.Span.Kind kind() {\n        return KINDS.get(span.kind());\n    }\n\n    @Override\n    public String traceId() {\n        return span.traceId();\n    }\n\n    @Override\n    public String spanId() {\n        return span.id();\n    }\n\n    @Override\n    public String parentId() {\n        return span.parentId();\n    }\n\n    @Override\n    public String tag(String key) {\n        Map<String, String> tags = span.tags();\n        if (tags == null) {\n            return null;\n        }\n        return tags.get(key);\n    }\n\n    @Override\n    public Map<String, String> tags() {\n        return span.tags();\n    }\n\n    @Override\n    public int tagCount() {\n        Map<String, String> tags = span.tags();\n        if (tags == null) {\n            return 0;\n        }\n        return tags.size();\n    }\n\n    @Override\n    public boolean hasError() {\n        return span.tags().containsKey(\"error\");\n    }\n\n    @Override\n    public String errorInfo() {\n        return span.tags().get(\"error\");\n    }\n\n    @Override\n    public String remoteServiceName() {\n        return span.remoteEndpoint().serviceName();\n    }\n\n    @Override\n    public String annotationValueAt(int i) {\n        List<Annotation> annotations = span.annotations();\n        if (annotations == null || annotations.size() <= i) {\n            return null;\n        }\n        return annotations.get(i).value();\n    }\n\n    @Override\n    public long timestamp() {\n        return span.timestamp();\n    }\n\n    @Override\n    public Long duration() {\n        return span.duration();\n    }\n\n    @Override\n    public int annotationCount() {\n        List<Annotation> annotations = span.annotations();\n        if (annotations == null) {\n            return 0;\n        }\n        return annotations.size();\n    }\n\n    @Override\n    public int remotePort() {\n        return span.remoteEndpoint().port();\n    }\n\n\n    @Override\n    public int localPort() {\n        return span.localEndpoint().port();\n    }\n\n    @Override\n    public String remoteIp() {\n        return span.remoteEndpoint().ipv4();\n    }\n\n    @Override\n    public String localIp() {\n        return span.localEndpoint().ipv4();\n    }\n\n    @Override\n    public String name() {\n        return span.name();\n    }\n\n    @Override\n    public String localServiceName() {\n        return span.localServiceName();\n    }\n\n    @Override\n    public Boolean shared() {\n        return span.shared();\n    }\n\n}\n"
  },
  {
    "path": "mock/report-mock/src/test/java/com/megaease/easeagent/mock/report/MockReportTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.mock.report;\n\nimport com.megaease.easeagent.plugin.report.AgentReport;\nimport org.junit.Test;\n\nimport static org.junit.Assert.assertNotNull;\n\npublic class MockReportTest {\n\n    @Test\n    public void getAgentReport() {\n        AgentReport agentReport = MockReport.getAgentReport();\n        assertNotNull(agentReport);\n    }\n}\n"
  },
  {
    "path": "mock/report-mock/src/test/resources/log4j2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Configuration status=\"WARN\">\n    <Appenders>\n        <Console name=\"Console\" target=\"SYSTEM_OUT\">\n            <!--<PatternLayout pattern=\"%d{HH:mm:ss.SSS} [%10.10t] %-p %10.10c{1} - %msg%n\"/>-->\n            <PatternLayout pattern=\"%d{HH:mm:ss.SSS} %-5level %class{36}:%L (%X{testMdc}) - %msg%xEx%n\"/>\n        </Console>\n    </Appenders>\n    <Loggers>\n        <Root level=\"INFO\">\n            <AppenderRef ref=\"Console\"/>\n        </Root>\n    </Loggers>\n</Configuration>\n"
  },
  {
    "path": "mock/utils-mock/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright (c) 2021, MegaEase\n  ~ All rights reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~   http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>mock</artifactId>\n        <groupId>com.megaease.easeagent</groupId>\n        <version>2.3.0</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>utils-mock</artifactId>\n\n\n    <dependencies>\n        <dependency>\n            <groupId>org.apache.logging.log4j</groupId>\n            <artifactId>log4j-core</artifactId>\n        </dependency>\n    </dependencies>\n</project>\n"
  },
  {
    "path": "mock/utils-mock/src/main/java/com/megaease/easeagent/mock/utils/JdkHttpServer.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.mock.utils;\n\nimport com.sun.net.httpserver.Headers;\nimport com.sun.net.httpserver.HttpContext;\nimport com.sun.net.httpserver.HttpExchange;\nimport com.sun.net.httpserver.HttpServer;\n\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.net.DatagramSocket;\nimport java.net.InetSocketAddress;\nimport java.net.URI;\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.function.Consumer;\n\npublic class JdkHttpServer {\n    public static final JdkHttpServer INSTANCE;\n\n    static {\n        try {\n            INSTANCE = JdkHttpServer.builder().build();\n        } catch (IOException e) {\n            throw new RuntimeException(\"build JdkHttpServer fail.\", e);\n        }\n    }\n\n    private final int port;\n    private final HttpServer server;\n    private final String path;\n    private final String url;\n    private AtomicReference<Headers> lastHeaders = new AtomicReference<>();\n    private AtomicReference<HttpExchange> lastHttpExchange = new AtomicReference<>();\n    private Consumer<Headers> headersConsumer;\n    private Consumer<HttpExchange> exchangeConsumer;\n\n    public JdkHttpServer(int port, HttpServer server, String path) {\n        this.port = port;\n        this.server = server;\n        this.path = path;\n        this.url = String.format(\"http://127.0.0.1:%s%s\", port, path);\n    }\n\n    public JdkHttpServer start() {\n        HttpContext context = server.createContext(path);\n        context.setHandler(JdkHttpServer.this::handleRequest);\n        server.start();\n        return this;\n    }\n\n    public int getPort() {\n        return port;\n    }\n\n    public String getPath() {\n        return path;\n    }\n\n    public String getUrl() {\n        return url;\n    }\n\n    public void stop() {\n        server.stop(0);\n    }\n\n    public void setHeadersConsumer(Consumer<Headers> headersConsumer) {\n        this.headersConsumer = headersConsumer;\n    }\n\n    public void setExchangeConsumer(Consumer<HttpExchange> exchangeConsumer) {\n        this.exchangeConsumer = exchangeConsumer;\n    }\n\n    public Headers getLastHeaders() {\n        return lastHeaders.get();\n    }\n\n    public HttpExchange getLastHttpExchange() {\n        return lastHttpExchange.get();\n    }\n\n    public void handleRequest(HttpExchange exchange) throws IOException {\n        URI requestURI = exchange.getRequestURI();\n        lastHeaders.set(exchange.getRequestHeaders());\n        lastHttpExchange.set(exchange);\n        if (this.headersConsumer != null) {\n            this.headersConsumer.accept(exchange.getRequestHeaders());\n        }\n        if (this.exchangeConsumer != null) {\n            this.exchangeConsumer.accept(exchange);\n        }\n        String response = String.format(\"This is the response at %s port: %s\", requestURI, port);\n        exchange.sendResponseHeaders(200, response.getBytes().length);\n        OutputStream os = exchange.getResponseBody();\n        os.write(response.getBytes());\n        os.close();\n    }\n\n    public static Builder builder() {\n        return new Builder();\n    }\n\n    public static class Builder {\n        private int tryPortNum = 4;\n        private int port = 0;\n        private String path;\n        private Consumer<Headers> headersConsumer;\n        private Consumer<HttpExchange> exchangeConsumer;\n\n        public Builder setPort(int port) {\n            this.port = port;\n            return this;\n        }\n\n        public Builder setHeadersConsumer(Consumer<Headers> headersConsumer) {\n            this.headersConsumer = headersConsumer;\n            return this;\n        }\n\n        public Builder setExchangeConsumer(Consumer<HttpExchange> exchangeConsumer) {\n            this.exchangeConsumer = exchangeConsumer;\n            return this;\n        }\n\n        public Builder setPath(String path) {\n            this.path = path;\n            return this;\n        }\n\n        public void setTryPortNum(int tryPortNum) {\n            this.tryPortNum = tryPortNum;\n        }\n\n        private HttpServer buildHttpServer() throws IOException {\n            if (0 < port) {\n                return HttpServer.create(new InetSocketAddress(port), 0);\n            }\n            IOException ioException = null;\n            for (int i = 0; i < tryPortNum; i++) {\n                try {\n                    DatagramSocket s = new DatagramSocket(0);\n                    int p = s.getLocalPort();\n                    return HttpServer.create(new InetSocketAddress(p), 0);\n                } catch (IOException e) {\n                    ioException = e;\n                }\n            }\n            throw ioException;\n        }\n\n        public JdkHttpServer build() throws IOException {\n            HttpServer httpServer = buildHttpServer();\n            int p = httpServer.getAddress().getPort();\n            String httpPath = path == null ? \"/example\" : path;\n            JdkHttpServer jdkHttpServer = new JdkHttpServer(p, httpServer, httpPath);\n            jdkHttpServer.setHeadersConsumer(headersConsumer);\n            jdkHttpServer.setExchangeConsumer(exchangeConsumer);\n            return jdkHttpServer;\n        }\n\n    }\n}\n"
  },
  {
    "path": "mock/utils-mock/src/main/java/com/megaease/easeagent/mock/utils/MockProvider.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.mock.utils;\n\npublic interface MockProvider {\n    Object get();\n}\n"
  },
  {
    "path": "mock/utils-mock/src/main/java/com/megaease/easeagent/mock/utils/MockSystemEnv.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.mock.utils;\n\nimport java.lang.reflect.Field;\nimport java.util.Map;\n\npublic class MockSystemEnv {\n    private static final Map<String, String> SETTER = getEnvironments();\n\n\n    @SuppressWarnings(\"all\")\n    private static Map<String, String> getEnvironments() {\n        try {\n            Class systemEnv = Thread.currentThread().getContextClassLoader().loadClass(\"com.megaease.easeagent.plugin.utils.SystemEnv\");\n            Field environments = systemEnv.getDeclaredField(\"ENVIRONMENTS\");\n            environments.setAccessible(true);\n            return (Map<String, String>) environments.get(null);\n        } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException e) {\n            e.printStackTrace();\n        }\n        return null;\n    }\n\n    public static void set(String name, String value) {\n        if (SETTER != null) {\n            SETTER.put(name, value);\n        }\n    }\n\n    public static void remove(String name) {\n        if (SETTER != null) {\n            SETTER.remove(name);\n        }\n    }\n}\n"
  },
  {
    "path": "mock/zipkin-mock/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright (c) 2021, MegaEase\n  ~ All rights reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~   http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>mock</artifactId>\n        <groupId>com.megaease.easeagent</groupId>\n        <version>2.3.0</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>zipkin-mock</artifactId>\n\n\n    <dependencies>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>report</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>zipkin</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>report-mock</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>utils-mock</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n    </dependencies>\n</project>\n"
  },
  {
    "path": "mock/zipkin-mock/src/main/java/brave/TracerTestUtils.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage brave;\n\nimport brave.internal.recorder.PendingSpan;\nimport brave.internal.recorder.PendingSpans;\nimport brave.propagation.TraceContext;\n\nimport java.util.Iterator;\nimport java.util.Map;\n\npublic class TracerTestUtils {\n    public static void clean(Tracer tracer) {\n        PendingSpans pendingSpans = tracer.pendingSpans;\n        Iterator<Map.Entry<TraceContext, PendingSpan>> iterator = pendingSpans.iterator();\n        while (iterator.hasNext()) {\n            pendingSpans.remove(iterator.next().getKey());\n        }\n    }\n\n    public static boolean hashPendingSpans(Tracer tracer) {\n        return tracer.pendingSpans.iterator().hasNext();\n    }\n}\n"
  },
  {
    "path": "mock/zipkin-mock/src/main/java/brave/internal/collect/WeakConcurrentMapTestUtils.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage brave.internal.collect;\n\npublic class WeakConcurrentMapTestUtils {\n    public static void runExpungeStaleEntries(WeakConcurrentMap weakConcurrentMap) {\n        weakConcurrentMap.expungeStaleEntries();\n    }\n}\n"
  },
  {
    "path": "mock/zipkin-mock/src/main/java/com/megaease/easeagent/mock/zipkin/MockTracingProvider.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.mock.zipkin;\n\nimport brave.TracerTestUtils;\nimport brave.Tracing;\nimport brave.propagation.CurrentTraceContext;\nimport brave.propagation.ThreadLocalCurrentTraceContext;\nimport com.megaease.easeagent.mock.config.MockConfig;\nimport com.megaease.easeagent.mock.report.MockReport;\nimport com.megaease.easeagent.mock.utils.MockProvider;\nimport com.megaease.easeagent.zipkin.TracingProviderImpl;\n\npublic class MockTracingProvider implements MockProvider {\n    private static final TracingProviderImpl TRACING_PROVIDER = new TracingProviderImpl();\n    private static final Tracing TRACING;\n\n    static {\n        TRACING_PROVIDER.setConfig(MockConfig.getCONFIGS());\n        TRACING_PROVIDER.setAgentReport(MockReport.getAgentReport());\n        TRACING_PROVIDER.afterPropertiesSet();\n        TRACING = TRACING_PROVIDER.tracing();\n    }\n\n    public static TracingProviderImpl getTracingProvider() {\n        return TRACING_PROVIDER;\n    }\n\n    public static Tracing getTRACING() {\n        return TRACING;\n    }\n\n    @Override\n    public Object get() {\n        return getTracingProvider();\n    }\n\n    public static synchronized boolean hasPendingSpans() {\n        return TracerTestUtils.hashPendingSpans(TRACING.tracer());\n    }\n\n    public static synchronized void cleanPendingSpans() {\n        TracerTestUtils.clean(TRACING.tracer());\n    }\n\n    public static synchronized void cleanCurrentSpan() {\n        CurrentTraceContext currentContext = TRACING.currentTraceContext();\n        if (currentContext instanceof ThreadLocalCurrentTraceContext) {\n            ((ThreadLocalCurrentTraceContext) currentContext).clear();\n        }\n    }\n\n    public static synchronized boolean hashCurrentConetxt() {\n        return TRACING.currentTraceContext().get() != null;\n    }\n}\n"
  },
  {
    "path": "mock/zipkin-mock/src/main/resources/META-INF/services/com.megaease.easeagent.mock.utils.MockProvider",
    "content": "com.megaease.easeagent.mock.zipkin.MockTracingProvider\n"
  },
  {
    "path": "mock/zipkin-mock/src/test/java/com/megaease/easeagent/mock/zipkin/MockTracingProviderTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.mock.zipkin;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.assertFalse;\nimport static org.junit.Assert.assertNotNull;\n\npublic class MockTracingProviderTest {\n\n    @Test\n    public void getTracingProvider() {\n        assertNotNull(MockTracingProvider.getTracingProvider());\n    }\n\n    @Test\n    public void getTRACING() {\n        assertNotNull(MockTracingProvider.getTRACING());\n    }\n\n    @Test\n    public void get() {\n        assertNotNull(MockTracingProvider.getTRACING());\n    }\n\n    @Test\n    public void testSpan() {\n        assertNotNull(MockTracingProvider.getTRACING().tracer().nextSpan());\n        assertFalse(MockTracingProvider.getTRACING().tracer().nextSpan().isNoop());\n    }\n}\n"
  },
  {
    "path": "plugin-api/README.md",
    "content": "# easeagent plugin api\n\n* It only serves plugin\n* It is a bridge between easeagent and plugin\n* It must be very clean\n* It tries to have only interfaces\n* It should not be dependent on other packages\n* Its interface is only related to plugin\n"
  },
  {
    "path": "plugin-api/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright (c) 2021, MegaEase\n  ~ All rights reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~   http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <parent>\n        <artifactId>easeagent</artifactId>\n        <groupId>com.megaease.easeagent</groupId>\n        <version>2.3.0</version>\n    </parent>\n    <artifactId>plugin-api</artifactId>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.squareup</groupId>\n            <artifactId>javapoet</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>com.fasterxml.jackson.core</groupId>\n            <artifactId>jackson-core</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>com.fasterxml.jackson.core</groupId>\n            <artifactId>jackson-databind</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>com.google.auto.service</groupId>\n            <artifactId>auto-service</artifactId>\n            <scope>provided</scope>\n        </dependency>\n\n        <dependency>\n            <groupId>com.google.auto.value</groupId>\n            <artifactId>auto-value-annotations</artifactId>\n            <version>1.9</version>\n            <scope>provided</scope>\n        </dependency>\n\n        <dependency>\n            <groupId>com.google.code.findbugs</groupId>\n            <artifactId>jsr305</artifactId>\n            <scope>compile</scope>\n        </dependency>\n        <dependency>\n            <groupId>io.opentelemetry</groupId>\n            <artifactId>opentelemetry-sdk</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>io.opentelemetry</groupId>\n            <artifactId>opentelemetry-sdk-common</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>io.opentelemetry</groupId>\n            <artifactId>opentelemetry-semconv</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>io.opentelemetry</groupId>\n            <artifactId>opentelemetry-sdk-logs</artifactId>\n        </dependency>\n    </dependencies>\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-shade-plugin</artifactId>\n                <version>${version.maven-shade-plugin}</version>\n                <configuration>\n                    <minimizeJar>true</minimizeJar>\n                    <relocations>\n                        <relocation>\n                            <pattern>com.fasterxml</pattern>\n                            <shadedPattern>com.megaease.easeagent.plugin.utils</shadedPattern>\n                        </relocation>\n                        <!--\n                        <relocation>\n                            <pattern>com.squareup.javapoet</pattern>\n                            <shadedPattern>easeagent.plugin.javapoet</shadedPattern>\n                        </relocation>\n                        -->\n                    </relocations>\n\n                    <!--\n                    <artifactSet>\n                        <excludes>\n                            <exclude>com.squareup:javapoet</exclude>\n                        </excludes>\n                    </artifactSet>\n                    -->\n                </configuration>\n                <executions>\n                    <execution>\n                        <phase>package</phase>\n                        <goals>\n                            <goal>shade</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n\n        </plugins>\n    </build>\n</project>\n\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/AgentPlugin.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin;\n\nimport com.megaease.easeagent.plugin.enums.Order;\n\npublic interface AgentPlugin extends Ordered {\n    /**\n     * define the plugin name, avoiding conflicts with others\n     * it will be used as namespace when get configuration.\n     */\n    String getNamespace();\n\n    /**\n     * define the plugin domain,\n     * it will be used to get configuration when loaded:\n     */\n    String getDomain();\n\n    default int order() {\n        return Order.HIGH.getOrder();\n    }\n}\n\n\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/AppendBootstrapLoader.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.plugin;\n\npublic interface AppendBootstrapLoader {\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/CodeVersion.java",
    "content": "package com.megaease.easeagent.plugin;\n\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.Set;\n\n/**\n * code of versions for control whether to load.\n * <p>\n * If CodeVersion.builder().build() is returned, it means it will load forever\n * <p>\n * The configuration format is as follows: runtime.code.version.points.{key}={version}\n * <p>\n * If CodeVersion.builder().key(\"jdk\").add(\"default\").build(), it means it will load by default,\n * but not load by specified like runtime.code.version.points.jdk=jdk10\n * <p>\n * When multiple versions are specified, it means that it can be loaded by multiple versions:\n * CodeVersion.builder().key(\"jdk\").add(\"jdk10\").add(\"jdk11\").build()\n * The following two configurations are load:\n * runtime.code.version.points.jdk=jdk10\n * runtime.code.version.points.jdk=jdk11\n */\npublic class CodeVersion {\n    private final String key;\n    private final Set<String> versions;\n\n    public CodeVersion(String key, Set<String> versions) {\n        this.key = key;\n        this.versions = versions;\n    }\n\n    public boolean isEmpty() {\n        return key == null || key.trim().isEmpty() || versions == null || versions.isEmpty();\n    }\n\n    public String getKey() {\n        return key;\n    }\n\n    public Set<String> getVersions() {\n        return versions;\n    }\n\n    public static Builder builder() {\n        return new Builder();\n    }\n\n    public static class Builder {\n        String key = \"\";\n        Set<String> versions = new HashSet<>();\n\n        public Builder key(String key) {\n            this.key = key;\n            return this;\n        }\n\n        public Builder add(String version) {\n            this.versions.add(version);\n            return this;\n        }\n\n        public CodeVersion build() {\n            if (this.versions.isEmpty()) {\n                return new CodeVersion(this.key, Collections.emptySet());\n            } else if (this.versions.size() == 1) {\n                return new CodeVersion(this.key, Collections.singleton(this.versions.iterator().next()));\n            } else {\n                return new CodeVersion(this.key, Collections.unmodifiableSet(this.versions));\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/Ordered.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin;\n\npublic interface Ordered {\n    /**\n     * Higher values operate later\n     * For example: an interceptor with order=1 will be called after an interceptor with order=0.\n     *\n     * {@link com.megaease.easeagent.plugin.enums.Order} has predefined kinds of priorities\n     *\n     */\n    int order();\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/Points.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin;\n\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.loader.ClassLoaderMatcher;\nimport com.megaease.easeagent.plugin.matcher.loader.IClassLoaderMatcher;\n\nimport java.util.Collections;\nimport java.util.Set;\n\n/**\n * Pointcut can be defined by ProbeDefine implementation\n * and also can be defined through @OnClass and @OnMethod annotation\n */\npublic interface Points {\n    String DEFAULT_VERSION = \"default\";\n    Set<String> DEFAULT_VERSIONS = Collections.singleton(DEFAULT_VERSION);\n\n    CodeVersion EMPTY_VERSION = CodeVersion.builder().build();\n\n\n    /**\n     * eg.\n     * versions=CodeVersion.builder().key(\"jdk\").add(\"default\").add(\"jdk8\").build()\n     * do not set or set the following value to load: runtime.code.version.points.jdk=jdk8\n     * <p>\n     * when set for not load: runtime.code.version.points.jdk=jdk17\n     * but load from Points: versions=CodeVersion.builder().key(\"jdk\").add(\"jdk17\").build()\n     *\n     * @see CodeVersion\n     * @return CodeVersion code of versions for control whether to load, If EMPTY_VERSIONS is returned, it means it will load forever\n     */\n    default CodeVersion codeVersions() {\n        return EMPTY_VERSION;\n    }\n\n    /**\n     * return the defined class matcher matching a class or a group of classes\n     * eg.\n     * ClassMatcher.builder()\n     * .hadInterface(A)\n     * .isPublic()\n     * .isAbstract()\n     * .or()\n     * .hasSuperClass(B)\n     * .isPublic()\n     * .build()\n     */\n    IClassMatcher getClassMatcher();\n\n    /**\n     * return the defined method matcher\n     * eg.\n     * MethodMatcher.builder().named(\"execute\")\n     * .isPublic()\n     * .argNum(2)\n     * .arg(1, \"java.lang.String\")\n     * .build().toSet()\n     * or\n     * MethodMatcher.multiBuilder()\n     * .match(MethodMatcher.builder().named(\"<init>\")\n     * .argsLength(3)\n     * .arg(0, \"org.apache.kafka.clients.consumer.ConsumerConfig\")\n     * .qualifier(\"constructor\")\n     * .build())\n     * .match(MethodMatcher.builder().named(\"poll\")\n     * .argsLength(1)\n     * .arg(0, \"java.time.Duration\")\n     * .qualifier(\"poll\")\n     * .build())\n     * .build();\n     */\n    Set<IMethodMatcher> getMethodMatcher();\n\n    /**\n     * when return true, the transformer will add a Object field and a accessor\n     * The dynamically added member can be accessed by AgentDynamicFieldAccessor:\n     * <p>\n     * AgentDynamicFieldAccessor.setDynamicFieldValue(instance, value)\n     * value = AgentDynamicFieldAccessor.getDynamicFieldValue(instance)\n     */\n    default boolean isAddDynamicField() {\n        return false;\n    }\n\n    /**\n     * When a non-null string is returned, the converter will add an accessor to get the member variables inside the class.\n     * Get method: value = TypeFieldGetter.get(instance)\n     * @see com.megaease.easeagent.plugin.field.TypeFieldGetter#get(Object)\n     * @return String field name\n     */\n    default String getTypeFieldAccessor() {\n        return null;\n    }\n\n    /**\n     * Only match classes loaded by the ClassLoaderMatcher\n     * default as all classloader\n     *\n     * @return classloader matcher\n     */\n    default IClassLoaderMatcher getClassLoaderMatcher() {\n        return ClassLoaderMatcher.ALL;\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/annotation/AdviceTo.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.annotation;\n\nimport com.megaease.easeagent.plugin.AgentPlugin;\nimport com.megaease.easeagent.plugin.Points;\n\nimport java.lang.annotation.*;\n\n/**\n * use to annotate Interceptor implementation,\n * to link Interceptor to Points and AgentPlugin\n */\n@Retention(RetentionPolicy.RUNTIME)\n@Target(ElementType.TYPE)\n@Repeatable(AdvicesTo.class)\npublic @interface AdviceTo {\n    Class<? extends Points> value();\n    Class<? extends AgentPlugin> plugin() default AgentPlugin.class;\n    String qualifier() default \"default\";\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/annotation/AdvicesTo.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.annotation;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n@Retention(RetentionPolicy.RUNTIME)\n@Target(ElementType.TYPE)\npublic @interface AdvicesTo {\n    AdviceTo[] value();\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/annotation/DynamicField.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.annotation;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n@SuppressWarnings(\"unused\")\n@Retention(RetentionPolicy.RUNTIME)\n@Target(ElementType.TYPE)\npublic @interface DynamicField {\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/annotation/Injection.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n package com.megaease.easeagent.plugin.annotation;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\nimport java.lang.reflect.Constructor;\nimport java.util.function.Predicate;\n\npublic interface Injection {\n    @Retention(RetentionPolicy.RUNTIME)\n    @Target(ElementType.TYPE)\n    @interface Provider {\n        Class<?> value();\n    }\n\n    @Retention(RetentionPolicy.RUNTIME)\n    @Target(ElementType.CONSTRUCTOR)\n    @interface Autowire {\n        Predicate<Constructor<?>> AUTOWIRED_CONS = input -> input.getAnnotation(Autowire.class) != null;\n    }\n\n    @Retention(RetentionPolicy.RUNTIME)\n    @Target(ElementType.METHOD)\n    @interface Bean {\n        String value() default \"\";\n    }\n\n    @Retention(RetentionPolicy.RUNTIME)\n    @Target(ElementType.PARAMETER)\n    @interface Qualifier {\n        String value();\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/Cleaner.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api;\n\nimport java.io.Closeable;\n\n/**\n * A Cleaner for Context\n * It must be call after your business.\n * <p>\n * example 1:\n * <pre>${@code\n *    Cleaner cleaner = context.importAsync(snapshot);\n *    try{\n *       //do business\n *    }finally{\n *        cleaner.close();\n *    }\n * }</pre>\n * <p>\n * example 2:\n * <pre>${@code\n *    void before(...){\n *       Cleaner cleaner = context.importForwardedHeaders(getter);\n *    }\n *    void after(...){\n *      try{\n *         //do business\n *      }finally{\n *          cleaner.close();\n *      }\n *    }\n * }</pre>\n * <p>\n * example 3:\n * <pre>${@code\n *    void callback(AsyncContext ac){\n *       try (Cleaner cleaner = ac.importToCurrent()) {\n *          //do business\n *       }\n *    }\n * }</pre>\n */\npublic interface Cleaner extends Closeable {\n    /**\n     * No exceptions are thrown when unbinding a Context.\n     * It must be call after your business.\n     * <pre>{@code\n     *  try{\n     *      ......\n     *  }finally{\n     *      cleaner.close();\n     *  }\n     * }</pre>\n     */\n    @Override\n    void close();\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/Context.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api;\n\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.context.AsyncContext;\nimport com.megaease.easeagent.plugin.api.context.RequestContext;\nimport com.megaease.easeagent.plugin.api.trace.*;\nimport com.megaease.easeagent.plugin.bridge.NoOpIPluginConfig;\n\n/**\n * A Context remains in the session it was bound to until business finish.\n */\n@SuppressWarnings(\"unused\")\npublic interface Context {\n    /**\n     * When true, do nothing and nothing is reported . However, this Context should\n     * still be injected into outgoing requests. Use this flag to avoid performing expensive\n     * computation.\n     */\n    boolean isNoop();\n\n    /**\n     * Returns the most recently created tracing component iff it hasn't been closed. null otherwise.\n     *\n     * <p>This object should not be cached.\n     */\n    Tracing currentTracing();\n\n    /**\n     * Returns the value to which the specified key is mapped,\n     * or {@code null} if this context contains no mapping for the key.\n     *\n     * <p>More formally, if this context contains a mapping from a key\n     * {@code k} to a value {@code v} such that {@code (key==null ? k==null :\n     * key.equals(k))}, then this method returns {@code v}; otherwise\n     * it returns {@code null}.  (There can be at most one such mapping.)\n     *\n     * <p>If this context permits null values, then a return value of\n     * {@code null} does not <i>necessarily</i> indicate that the context\n     * contains no mapping for the key; it's also possible that the context\n     * explicitly maps the key to {@code null}.\n     *\n     * @param key the key whose associated value is to be returned\n     * @return the value to which the specified key is mapped, or\n     * {@code null} if this context contains no mapping for the key\n     * @throws ClassCastException if the key is of an inappropriate type for\n     *                            this context\n     *                            (<a href=\"{@docRoot}/java/util/Collection.html#optional-restrictions\">optional</a>)\n     */\n    <V> V get(Object key);\n\n    /**\n     * Removes the mapping for a key from this Context if it is present\n     * (optional operation).   More formally, if this context contains a mapping\n     * from key <tt>k</tt> to value <tt>v</tt> such that\n     * <code>(key==null ?  k==null : key.equals(k))</code>, that mapping\n     * is removed.  (The context can contain at most one such mapping.)\n     *\n     * <p>Returns the value to which this context previously associated the key,\n     * or <tt>null</tt> if the context contained no mapping for the key.\n     *\n     * <p>If this context permits null values, then a return value of\n     * <tt>null</tt> does not <i>necessarily</i> indicate that the context\n     * contained no mapping for the key; it's also possible that the context\n     * explicitly mapped the key to <tt>null</tt>.\n     *\n     * <p>The context will not contain a mapping for the specified key once the\n     * call returns.\n     *\n     * @param key key whose mapping is to be removed from the Context\n     * @return the previous value associated with <tt>key</tt>, or\n     * <tt>null</tt> if there was no mapping for <tt>key</tt>.\n     * @throws ClassCastException if the key is of an inappropriate type for\n     *                            this context\n     *                            (<a href=\"{@docRoot}/java/util/Collection.html#optional-restrictions\">optional</a>)\n     */\n    <V> V remove(Object key);\n\n    /**\n     * Associates the specified value with the specified key in this context\n     * (optional operation).  If the context previously contained a mapping for\n     * the key, the old value is replaced by the specified value.  (A context\n     * <tt>m</tt> is said to contain a mapping for a key <tt>k</tt>\n     *\n     * @param key   key with which the specified value is to be associated\n     * @param value value to be associated with the specified key\n     * @return the previous value associated with <tt>key</tt>, or\n     * <tt>null</tt> if there was no mapping for <tt>key</tt>.\n     * (A <tt>null</tt> return can also indicate that the context\n     * previously associated <tt>null</tt> with <tt>key</tt>,\n     * if the implementation supports <tt>null</tt> values.)\n     * @throws ClassCastException if the class of the specified key or value\n     *                            prevents it from being stored in this context\n     */\n    <V> V put(Object key, V value);\n\n    /**\n     * Looks at the config at the current without removing it\n     * from the stack.\n     *\n     * @return The config at the top of this stack (the last config of the <tt>Config</tt> object).\n     * return {@link NoOpIPluginConfig#INSTANCE} if this stack is empty.\n     */\n    IPluginConfig getConfig();\n\n    /**\n     * Record and return the stacking sequence of Object{@code key}'s Session\n     * It needs to be used together with the {@link #exit(Object)} to be effective\n     * for example 1:\n     * <pre>{@code\n     *      fun1(){\n     *          try{\n     *              if (context.enter(obj)!=1){\n     *                 return;\n     *              }\n     *              //do something1\n     *          }finally{\n     *              if (context.exit(obj)!=1){\n     *                 return;\n     *              }\n     *              //do something2\n     *          }\n     *      }\n     *      fun2(){\n     *          try{\n     *              if (context.enter(obj)!=1){\n     *                 return;\n     *              }\n     *              // call fun1();\n     *              //do something3\n     *          }finally{\n     *              if (context.exit(obj)!=1){\n     *                 return;\n     *              }\n     *              //do something4\n     *          }\n     *      }\n     * }</pre>\n     * if call fun2(), something1 and something2 will no longer execute\n     * <p>\n     * for example 2:\n     *\n     * <pre>{@code\n     *      fun1(){\n     *          try{\n     *              if (context.enter(obj)>2){\n     *                 return;\n     *              }\n     *              //do something1\n     *          }finally{\n     *              if (context.exit(obj)>2){\n     *                 return;\n     *              }\n     *              //do something2\n     *          }\n     *      }\n     *      fun2(){\n     *          try{\n     *              if (context.enter(obj)>2){\n     *                 return;\n     *              }\n     *              // call fun1();\n     *              //do something3\n     *          }finally{\n     *              if (context.exit(obj)>2){\n     *                 return;\n     *              }\n     *              //do something4\n     *          }\n     *      }\n     *      fun3(){\n     *          try{\n     *              if (context.enter(obj)>2){\n     *                 return;\n     *              }\n     *              // call fun2();\n     *              //do something5\n     *          }finally{\n     *              if (context.exit(obj)>2){\n     *                 return;\n     *              }\n     *              //do something6\n     *          }\n     *      }\n     * }</pre>\n     * if call fun3(), something1 and something2 will no longer execute\n     *\n     * @param key the Object of stacking sequence\n     * @return stacking sequence\n     * @see #exit(Object)\n     */\n    int enter(Object key);\n\n    /**\n     * Record and verify the stacking sequence of Object{@code key}'s Session\n     * It needs to be used together with the {@link #exit(Object, int)} to be effective\n     *\n     * @param key   the Object of stacking sequence\n     * @param times the verify of stacking sequence\n     * @return true if stacking sequence is {@code times} else false\n     * @see #enter(Object)\n     */\n    default boolean enter(Object key, int times) {\n        return enter(key) == times;\n    }\n\n    /**\n     * Release and return the stacking sequence of Object{@code key}'s Session\n     * It needs to be used together with the {@link #enter(Object)} to be effective\n     *\n     * @param key the Object of stacking sequence\n     * @return stacking sequence\n     * @see #enter(Object)\n     */\n    int exit(Object key);\n\n    /**\n     * Release and verify the stacking sequence of Object's Session\n     * It needs to be used together with the {@link #enter(Object, int)} to be effective\n     *\n     * @param key   the Object of stacking sequence\n     * @param times the verify of stacking sequence\n     * @return true if stacking sequence is {@code times} else false\n     * @see #exit(Object)\n     */\n    default boolean exit(Object key, int times) {\n        return exit(key) == times;\n    }\n\n\n    //---------------------------------- 1. async context begin ------------------------------------------\n    //---------------------------------- 1. Cross-thread ------------------------------------------\n    // When you import and export the AsyncContext, you will also import and export the Tracing context for Thread.\n\n    /**\n     * Export a {@link AsyncContext} for async\n     * It will copy all the key:value in the current Context\n     *\n     * @return {@link AsyncContext}\n     */\n    AsyncContext exportAsync();\n\n    /**\n     * Import a {@link AsyncContext} for async\n     * It will copy all the key: value to the current Context\n     * <p>\n     * If you don’t want to get the Context, you can use the {@link AsyncContext#importToCurrent()} proxy call\n     * <p>\n     * The Cleaner must be close after business:\n     * <p>\n     * example:\n     * <pre>{@code\n     *    void callback(Context context, AsyncContext ac){\n     *       try (Cleaner cleaner = context.importAsync(ac)) {\n     *          //do business\n     *       }\n     *    }\n     * }</pre>\n     *\n     * @param snapshot the AsyncContext from {@link #exportAsync()} called\n     * @return {@link Cleaner} for tracing\n     */\n    Cleaner importAsync(AsyncContext snapshot);\n\n    /**\n     * Wraps the input so that it executes with the same context as now.\n     */\n    Runnable wrap(Runnable task);\n\n    /**\n     * Check task is wrapped.\n     *\n     * @param task Runnable\n     * @return true if task is warpped.\n     */\n    boolean isWrapped(Runnable task);\n    //---------------------------------- 1. async context end ------------------------------------------\n\n\n    //----------------------------------2. Cross-server ------------------------------------------\n\n    /**\n     * Create a RequestContext for the next Server\n     * It will pass multiple key:value values required by Trace and EaseAgent through\n     * {@link Request#setHeader(String, String)}, And set the Span's kind, name and\n     * cached scope through {@link Request#kind()}, {@link Request#name()} and {@link Request#cacheScope()}.\n     * <p>\n     * When you want to call the next Server, you can pass the necessary key:value to the next Server\n     * by implementing {@link Request#setHeader(String, String)}, or you can get the {@link RequestContext} of return,\n     * call {@link RequestContext#getHeaders()} to get it and pass it on.\n     * <p>\n     * It is usually called on the client request when collaboration between multiple server is required.\n     * {@code client.clientRequest(Request.setHeader<spanId,root-source...>) --> server }\n     * or\n     * {@code client.clientRequest(Request).getHeaders<spanId,root-source...> --> server }\n     * <p>\n     * The Scope must be close after plugin:\n     *\n     * <pre>{@code\n     *    void after(...){\n     *       RequestContext rc = context.get(...)\n     *       try{\n     *\n     *       }finally{\n     *           rc.scope().close();\n     *       }\n     *    }\n     * }</pre>\n     *\n     * @param request {@link Request}\n     * @return {@link RequestContext}\n     */\n    RequestContext clientRequest(Request request);\n\n\n    /**\n     * Obtain key:value from the request passed by a parent Server and create a RequestContext\n     * <p>\n     * It will not only obtain the key:value required by Trace from the {@link Request#header(String)},\n     * but also other necessary key:value of EaseAgent, such as the key configured in the configuration file:\n     * {@link ProgressFields#EASEAGENT_PROGRESS_FORWARDED_HEADERS_CONFIG}\n     * <p>\n     * If there is no Tracing Header, it will create a Root Span\n     * <p>\n     * It will set the Span's kind, name and cached scope through {@link Request#kind()}, {@link Request#name()}\n     * and {@link Request#cacheScope()}.\n     * <p>\n     * It is usually called on the server receives a request when collaboration between multiple server is required.\n     * {@code client --> server.serverReceive(Request<spanId,root-source...>) }\n     * <p>\n     * The Scope must be close after plugin:\n     *\n     * <pre>{@code\n     *    void after(...){\n     *       RequestContext rc = context.get(...)\n     *       try{\n     *\n     *       }finally{\n     *           rc.scope().close();\n     *       }\n     *    }\n     * }</pre>\n     *\n     * @param request {@link Request}\n     * @return {@link RequestContext}\n     */\n    RequestContext serverReceive(Request request);\n\n\n    //---------------------------------- 3. Message Tracing ------------------------------------------\n\n    /**\n     * Obtain key:value from the message request and create a Span, Examples: kafka consumer, rabbitMq consumer\n     * <p>\n     * It will set the Span's kind, name and cached scope through {@link Request#kind()}, {@link Request#name()}\n     * and {@link Request#cacheScope()}.\n     *\n     * <p>\n     * It will set the Span's tags \"messaging.operation\", \"messaging.channel_kind\" and \"messaging.channel_name\" from request\n     * {@link MessagingRequest#operation()} {@link MessagingRequest#channelKind()} {@link MessagingRequest#channelName()}\n     *\n     * <p>\n     * It is usually called on the consumer.\n     * {@code Kafka Server --> consumer.consumerSpan(Record<spanId,X-EG-Circuit-Breaker...>) }\n     *\n     * @param request {@link MessagingRequest}\n     * @return {@link Span}\n     */\n    Span consumerSpan(MessagingRequest request);\n\n\n    /**\n     * Create a Span for message producer. Examples: kafka producer, rabbitMq producer\n     * <p>\n     * It will set the Span's tags \"messaging.operation\", \"messaging.channel_kind\", \"messaging.channel_name\" from request\n     * {@link MessagingRequest#operation()} {@link MessagingRequest#channelKind()} {@link MessagingRequest#channelName()}\n     * And set the Span's kind, name and cached scope through {@link Request#kind()}, {@link Request#name()} and\n     * {@link Request#cacheScope()}.\n     *\n     * <p>\n     * It will not only pass multiple key:value values required by Trace through {@link Request#setHeader(String, String)},\n     * but also other necessary key:value of EaseAgent, such as the key configured in the configuration file:\n     * {@link ProgressFields#EASEAGENT_PROGRESS_FORWARDED_HEADERS_CONFIG}\n     * <p>\n     * <p>\n     * It is usually called on the producer.\n     * {@code producer.producerSpan(Record) -- Record<spanId,root-source...> --> Message Server}\n     *\n     * @param request {@link MessagingRequest}\n     * @return {@link Span}\n     */\n    Span producerSpan(MessagingRequest request);\n\n    /**\n     * Inject Consumer's Span key:value and Forwarded Headers to Request {@link MessagingRequest#setHeader(String, String)}.\n     *\n     * @param span    key:value from\n     * @param request key:value to\n     * @see Request#setHeader(String, String)\n     */\n    void consumerInject(Span span, MessagingRequest request);\n\n    /**\n     * Inject Producer's Span and Forwarded Headers key:value to Request {@link MessagingRequest#setHeader(String, String)}.\n     *\n     * @param span    key:value from\n     * @param request key:value to\n     * @see Request#setHeader(String, String)\n     */\n    void producerInject(Span span, MessagingRequest request);\n\n\n    //---------------------------------- 4. Span ------------------------------------------\n\n    /**\n     * Returns a new child span if there's a {@link Tracing#currentSpan()} or a new trace if there isn't.\n     *\n     * @return {@link Span}\n     */\n    Span nextSpan();\n\n    /**\n     * @return true if the key is necessary for EaseAgent\n     */\n    boolean isNecessaryKeys(String key);\n\n    /**\n     * Inject Forwarded Headers key:value to Setter {@link Setter#setHeader(String, String)}.\n     *\n     * @param setter key:value to\n     * @see Request#setHeader(String, String)\n     */\n    void injectForwardedHeaders(Setter setter);\n\n    /**\n     * Import Forwarded Headers key:value to Context {@link Getter#header(String)}.\n     * <p>\n     * The Cleaner must be close after plugin:\n     *\n     * <pre>{@code\n     *    void after(...){\n     *       Cleaner c = context.remove(...)\n     *       try{\n     *\n     *       }finally{\n     *           c.close();\n     *       }\n     *    }\n     * }</pre>\n     *\n     * @param getter name from\n     * @return {@link Scope} for current session\n     * @see Request#header(String)\n     */\n    Cleaner importForwardedHeaders(Getter getter);\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/InitializeContext.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api;\n\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.trace.TracingContext;\nimport com.megaease.easeagent.plugin.bridge.NoOpIPluginConfig;\n\n/**\n * Subtype of {@link Context} and {@link TracingContext} which can push and pop Config.\n */\n@SuppressWarnings(\"unused\")\npublic interface InitializeContext extends TracingContext {\n\n    /**\n     * Pushes a Config onto the top of session context config stack.\n     *\n     * @param config the config to be pushed onto this stack.\n     */\n    void pushConfig(IPluginConfig config);\n\n    /**\n     * Removes the Config at the top of this session context config stack\n     * and returns that config as the value of this function.\n     *\n     * @return The config at the top of this stack (the last config of the <tt>Config</tt> object).\n     * return {@link NoOpIPluginConfig#INSTANCE} if this stack is empty.\n     */\n    IPluginConfig popConfig();\n\n    /**\n     * Unlike get/put method transfer cross different interceptors and even cross the whole session,\n     * putLocal/getLocal can only transfer data in current interceptor instance.\n     * eg. when putLocal is called to put a Span in an interceptor's 'before' method,\n     * it can only be accessed in current interceptor by 'getLocal', and can't accessed or modify by other interceptors.\n     *\n     * @param key   the key whose associated value is to be returned\n     * @param value the value to which the specified key is mapped, or\n     *              {@code null} if this context contains no mapping for the key\n     * @return the value\n     */\n    <V> V putLocal(String key, V value);\n\n    <V> V getLocal(String key);\n\n    /**\n     * Push/pop/peek a object onto the top of session context retStack.\n     * usages: push an Span to context when an interceptor's 'before' called,\n     * and pop the Span in 'after' procession\n     */\n    <T> void push(T obj);\n\n    <T> T pop();\n\n    <T> T peek();\n\n    /**\n     * called by framework to maintain stack\n     */\n    void pushRetBound();\n\n    /**\n     * called by framework to maintain stack\n     */\n    void popRetBound();\n\n    /**\n     * called by framework to maintain stack\n     */\n    void popToBound();\n\n    /**\n     * clear the context\n     */\n    void clear();\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/ProgressFields.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api;\n\nimport com.megaease.easeagent.plugin.utils.common.StringUtils;\n\nimport javax.annotation.Nonnull;\nimport java.util.*;\nimport java.util.function.Consumer;\n\npublic class ProgressFields {\n    public static final String EASEAGENT_PROGRESS_FORWARDED_HEADERS_CONFIG = \"easeagent.progress.forwarded.headers\";\n    public static final String OBSERVABILITY_TRACINGS_TAG_RESPONSE_HEADERS_CONFIG = \"observability.tracings.tag.response.headers.\";\n    public static final String OBSERVABILITY_TRACINGS_SERVICE_TAGS_CONFIG = \"observability.tracings.service.tags.\";\n    private static volatile Fields responseHoldTagFields = build(OBSERVABILITY_TRACINGS_TAG_RESPONSE_HEADERS_CONFIG, Collections.emptyMap());\n    private static volatile Fields serviceTags = build(OBSERVABILITY_TRACINGS_SERVICE_TAGS_CONFIG, Collections.emptyMap());\n    private static final Set<String> forwardHeaderSet = new HashSet<>();\n\n\n    public static Consumer<Map<String, String>> changeListener() {\n        return values -> new Change().putAll(values).flush();\n    }\n\n    public static boolean isProgressFields(String key) {\n        return isForwardedHeader(key) || isResponseHoldTagKey(key) || isServerTags(key);\n    }\n\n    private static boolean isForwardedHeader(String key) {\n        return key.equals(EASEAGENT_PROGRESS_FORWARDED_HEADERS_CONFIG);\n    }\n\n    private static boolean isResponseHoldTagKey(String key) {\n        return key.startsWith(OBSERVABILITY_TRACINGS_TAG_RESPONSE_HEADERS_CONFIG);\n    }\n\n    private static boolean isServerTags(String key) {\n        return key.startsWith(OBSERVABILITY_TRACINGS_SERVICE_TAGS_CONFIG);\n    }\n\n    private static void buildForwardedHeaderSet(String value) {\n        String[] split = StringUtils.split(value, \",\");\n        forwardHeaderSet.clear();\n        if (split == null || split.length == 0) {\n            return;\n        }\n        forwardHeaderSet.addAll(Arrays.asList(split));\n    }\n\n    @SuppressWarnings(\"all\")\n    private static void setResponseHoldTagFields(Map<String, String> fields) {\n        responseHoldTagFields = responseHoldTagFields.rebuild(fields);\n    }\n\n    @SuppressWarnings(\"all\")\n    private static void setServiceTags(Map<String, String> tags) {\n        serviceTags = serviceTags.rebuild(tags);\n    }\n\n    public static boolean isEmpty(String[] fields) {\n        return fields == null || fields.length == 0;\n    }\n\n    public static Set<String> getForwardedHeaders() {\n        return forwardHeaderSet;\n    }\n\n    public static String[] getResponseHoldTagFields() {\n        return responseHoldTagFields.values;\n    }\n\n    public static Map<String, String> getServiceTags() {\n        return serviceTags.keyValues;\n    }\n\n\n    private static Fields build(@Nonnull String keyPrefix, @Nonnull Map<String, String> map) {\n        if (map.isEmpty()) {\n            return new Fields(keyPrefix, Collections.emptySet(), Collections.emptyMap(), Collections.emptyMap());\n        }\n        Map<String, String> keyValues = new HashMap<>();\n        for (Map.Entry<String, String> entry : map.entrySet()) {\n            String key = entry.getKey().replace(keyPrefix, \"\");\n            keyValues.put(key, entry.getValue());\n        }\n        return new Fields(keyPrefix, Collections.unmodifiableSet(new HashSet<>(map.values())), keyValues, map);\n    }\n\n    public static class Fields {\n        private final String keyPrefix;\n        private final String[] values;\n        private final Map<String, String> keyValues;\n        private final Map<String, String> map;\n\n        private Fields(@Nonnull String keyPrefix, @Nonnull Set<String> fieldSet, Map<String, String> keyValues, @Nonnull Map<String, String> map) {\n            this.keyPrefix = keyPrefix;\n            this.values = fieldSet.toArray(new String[0]);\n            this.keyValues = keyValues;\n            this.map = map;\n        }\n\n        Fields rebuild(@Nonnull Map<String, String> map) {\n            if (this.map.isEmpty()) {\n                map.entrySet().removeIf(stringStringEntry -> StringUtils.isEmpty(stringStringEntry.getValue()));\n                return build(keyPrefix, map);\n            }\n            Map<String, String> newMap = new HashMap<>(this.map);\n            for (Map.Entry<String, String> entry : map.entrySet()) {\n                if (StringUtils.isEmpty(entry.getValue())) {\n                    newMap.remove(entry.getKey());\n                } else {\n                    newMap.put(entry.getKey(), entry.getValue());\n                }\n            }\n            return build(keyPrefix, Collections.unmodifiableMap(newMap));\n        }\n    }\n\n    static class Change {\n        private final Map<String, String> responseHoldTags = new HashMap<>();\n        private final Map<String, String> serverTags = new HashMap<>();\n\n        public Change putAll(Map<String, String> map) {\n            for (Map.Entry<String, String> entry : map.entrySet()) {\n                put(entry.getKey(), entry.getValue());\n            }\n            return this;\n        }\n\n        public void put(String key, String value) {\n            if (ProgressFields.isForwardedHeader(key)) {\n                buildForwardedHeaderSet(value);\n            } else if (ProgressFields.isResponseHoldTagKey(key)) {\n                responseHoldTags.put(key, value);\n            } else if (ProgressFields.isServerTags(key)) {\n                serverTags.put(key, value);\n            }\n        }\n\n        private void flush() {\n            if (!responseHoldTags.isEmpty()) {\n                setResponseHoldTagFields(responseHoldTags);\n            }\n            if (!serverTags.isEmpty()) {\n                setServiceTags(serverTags);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/Reporter.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api;\n\nimport com.megaease.easeagent.plugin.report.EncodedData;\n\n/**\n * a reporter for message like metric or trace\n */\npublic interface Reporter {\n    /**\n     * out put the metric\n     *\n     * @param msg metric string like json\n     */\n    void report(String msg);\n\n    void report(EncodedData msg);\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/config/AutoRefreshConfigSupplier.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.config;\n\nimport java.lang.reflect.ParameterizedType;\nimport java.lang.reflect.Type;\n\n\n/**\n * a AutoRefreshConfig Supplier\n *\n * @param <T> the type of Config by this Supplier\n */\npublic abstract class AutoRefreshConfigSupplier<T extends AutoRefreshPluginConfig> {\n    private final Type type;\n\n    public AutoRefreshConfigSupplier() {\n        Type superClass = getClass().getGenericSuperclass();\n        if (superClass instanceof Class<?>) { // sanity check, should never happen\n            throw new IllegalArgumentException(\"Internal error: TypeReference constructed without actual type information\");\n        }\n        type = ((ParameterizedType) superClass).getActualTypeArguments()[0];\n    }\n\n    /**\n     * the type of AutoRefreshConfig\n     *\n     * @return {@link Type}\n     */\n    public Type getType() {\n        return type;\n    }\n\n    /**\n     * new a AutoRefreshConfig\n     *\n     * @return AutoRefreshConfig\n     */\n    public abstract T newInstance();\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/config/AutoRefreshPluginConfig.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.config;\n\npublic interface AutoRefreshPluginConfig extends PluginConfigChangeListener {\n\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/config/AutoRefreshPluginConfigImpl.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.config;\n\nimport java.util.List;\nimport java.util.Set;\n\n/**\n * a base AutoRefreshConfig\n */\npublic class AutoRefreshPluginConfigImpl implements IPluginConfig, AutoRefreshPluginConfig {\n    protected volatile IPluginConfig config;\n\n    @Override\n    public String domain() {\n        return config.domain();\n    }\n\n    @Override\n    public String namespace() {\n        return config.namespace();\n    }\n\n    @Override\n    public String id() {\n        return config.id();\n    }\n\n    @Override\n    public boolean hasProperty(String property) {\n        return config.hasProperty(property);\n    }\n\n    @Override\n    public String getString(String property) {\n        return config.getString(property);\n    }\n\n    @Override\n    public Integer getInt(String property) {\n        return config.getInt(property);\n    }\n\n    @Override\n    public Boolean getBoolean(String property) {\n        return config.getBoolean(property);\n    }\n\n    @Override\n    public Double getDouble(String property) {\n        return config.getDouble(property);\n    }\n\n    @Override\n    public Long getLong(String property) {\n        return config.getLong(property);\n    }\n\n    @Override\n    public List<String> getStringList(String property) {\n        return config.getStringList(property);\n    }\n\n    @Override\n    public IPluginConfig getGlobal() {\n        return config.getGlobal();\n    }\n\n    @Override\n    public Set<String> keySet() {\n        return config.keySet();\n    }\n\n    @Override\n    public void addChangeListener(PluginConfigChangeListener listener) {\n        config.addChangeListener(listener);\n    }\n\n    @Override\n    public void onChange(IPluginConfig oldConfig, IPluginConfig newConfig) {\n        this.config = newConfig;\n    }\n\n    public IPluginConfig getConfig() {\n        return config;\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/config/AutoRefreshPluginConfigRegistry.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.config;\n\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\n\nimport java.lang.reflect.Type;\nimport java.util.Objects;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.ConcurrentMap;\n\npublic class AutoRefreshPluginConfigRegistry {\n    private static final AutoRefreshConfigSupplier<AutoRefreshPluginConfigImpl> AUTO_REFRESH_CONFIG_IMPL_SUPPLIER\n        = new AutoRefreshConfigSupplier<AutoRefreshPluginConfigImpl>() {\n        @Override\n        public AutoRefreshPluginConfigImpl newInstance() {\n            return new AutoRefreshPluginConfigImpl();\n        }\n    };\n\n    private static final ConcurrentMap<Key, AutoRefreshPluginConfig> configs = new ConcurrentHashMap<>();\n\n    /**\n     * Obtain an AutoRefreshPluginConfigImpl when it is already registered. If you have not registered, create one and return\n     * The registered {@link Key} is domain, namespace, id.\n     *\n     * @param domain    String\n     * @param namespace String\n     * @param id        String\n     * @return AutoRefreshPluginConfigImpl\n     */\n    public static AutoRefreshPluginConfigImpl getOrCreate(String domain, String namespace, String id) {\n        return getOrCreate(domain, namespace, id, AUTO_REFRESH_CONFIG_IMPL_SUPPLIER);\n    }\n\n    /**\n     * Obtain an AutoRefreshConfig when it is already registered. If you have not registered, create one and return\n     * The registered {@link Key} is domain, namespace, id and the type by the supplier.\n     *\n     * @param domain    String\n     * @param namespace String\n     * @param id        String\n     * @param supplier  {@link AutoRefreshConfigSupplier} Instance Supplier\n     * @param <C>       the type of AutoRefreshConfig by the Supplier\n     * @return the type of AutoRefreshConfig by the Supplier\n     */\n    @SuppressWarnings(\"unchecked\")\n    public static <C extends AutoRefreshPluginConfig> C getOrCreate(String domain, String namespace,\n                                                                    String id, AutoRefreshConfigSupplier<C> supplier) {\n        Key key = new Key(domain, namespace, id, supplier.getType());\n        AutoRefreshPluginConfig autoRefreshConfig = configs.get(key);\n        if (autoRefreshConfig != null) {\n            triggerChange(domain,namespace,id,autoRefreshConfig);\n            return (C) autoRefreshConfig;\n        }\n        synchronized (configs) {\n            autoRefreshConfig = configs.get(key);\n            if (autoRefreshConfig != null) {\n                triggerChange(domain,namespace,id,autoRefreshConfig);\n                return (C) autoRefreshConfig;\n            }\n            C newConfig = supplier.newInstance();\n            triggerChange(domain, namespace, id, newConfig);\n            configs.put(key, newConfig);\n            return newConfig;\n        }\n    }\n\n    private static <C extends AutoRefreshPluginConfig> void triggerChange(String domain, String namespace, String id, C newConfig) {\n        IPluginConfig config = EaseAgent.getConfig(domain, namespace, id);\n        newConfig.onChange(null, config);\n        config.addChangeListener(newConfig);\n    }\n\n    static class Key {\n        private final String domain;\n\n        private final String namespace;\n\n        private final String id;\n\n        private final Type type;\n\n        public Key(String domain, String namespace, String id, Type type) {\n            this.domain = domain;\n            this.namespace = namespace;\n            this.id = id;\n            this.type = type;\n        }\n\n        @Override\n        public boolean equals(Object o) {\n            if (this == o) return true;\n            if (o == null || getClass() != o.getClass()) return false;\n            Key key = (Key) o;\n            return Objects.equals(domain, key.domain) &&\n                Objects.equals(namespace, key.namespace) &&\n                Objects.equals(id, key.id) &&\n                Objects.equals(type, key.type);\n        }\n\n        @Override\n        public int hashCode() {\n\n            return Objects.hash(domain, namespace, id, type);\n        }\n\n        @Override\n        public String toString() {\n            return \"Key{\" +\n                \"domain='\" + domain + '\\'' +\n                \", namespace='\" + namespace + '\\'' +\n                \", id='\" + id + '\\'' +\n                \", type=\" + type +\n                '}';\n        }\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/config/ChangeItem.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.config;\n\npublic class ChangeItem {\n    private final String name;\n    private final String fullName;\n    private final String oldValue;\n    private final String newValue;\n\n    public ChangeItem(String name, String fullName, String oldValue, String newValue) {\n        this.name = name;\n        this.fullName = fullName;\n        this.oldValue = oldValue;\n        this.newValue = newValue;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public String getFullName() {\n        return fullName;\n    }\n\n    public String getOldValue() {\n        return oldValue;\n    }\n\n    public String getNewValue() {\n        return newValue;\n    }\n\n    @Override\n    public String toString() {\n        return \"{\" +\n                \"fullName='\" + fullName + '\\'' +\n                \", oldValue='\" + oldValue + '\\'' +\n                \", newValue='\" + newValue + '\\'' +\n                '}';\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/config/Config.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.config;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\n@SuppressWarnings(\"unused\")\npublic interface Config {\n    boolean hasPath(String path);\n\n    String getString(String name);\n\n    String getString(String name, String defVal);\n\n    Integer getInt(String name);\n\n    Integer getInt(String name, int defValue);\n\n    Boolean getBoolean(String name);\n\n    Boolean getBoolean(String name, boolean defValue);\n\n    Boolean getBooleanNullForUnset(String name);\n\n    Double getDouble(String name);\n\n    Double getDouble(String name, double defValue);\n\n    Long getLong(String name);\n\n    Long getLong(String name, long defValue);\n\n    List<String> getStringList(String name);\n\n    Runnable addChangeListener(ConfigChangeListener listener);\n\n    Set<String> keySet();\n\n    Map<String, String> getConfigs();\n\n    void updateConfigs(Map<String, String> changes);\n\n    void updateConfigsNotNotify(Map<String, String> changes);\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/config/ConfigChangeListener.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.config;\n\nimport java.util.List;\n\npublic interface ConfigChangeListener {\n    void onChange(List<ChangeItem> list);\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/config/ConfigConst.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.config;\n\npublic interface ConfigConst {\n    String AGENT_JAR_PATH = \"easeagent.jar.path\";\n    String RUNTIME_CODE_VERSION_POINTS_PREFIX = \"runtime.code.version.points.\";\n    String PLUGIN = \"plugin\";\n    String PLUGIN_GLOBAL = \"global\";\n    String DELIMITER = \".\";\n    String PLUGIN_PREFIX = PLUGIN + DELIMITER;\n    String PLUGIN_FORMAT = join(PLUGIN, \"%s\", \"%s\", \"%s\", \"%s\");//plugin.<Domain>.<Namespace>.<ServiceId>.<Properties>\n    String SERVICE_NAME = \"name\";\n    String SYSTEM_NAME = \"system\";\n\n    String VERSION_NAME = \"version\";\n\n    // domain\n    String OBSERVABILITY = \"observability\";\n    String INTEGRABILITY = \"integrability\";\n    String GLOBAL_CANARY_LABELS = \"globalCanaryHeaders\";\n\n    // ServiceId\n    String METRIC_SERVICE_ID = \"metric\";\n    String LOG_SERVICE_ID = \"log\";\n    String TRACING_SERVICE_ID = \"tracing\";\n    String SERVICE_ID_ENABLED_KEY = \"enabled\";\n\n    static String join(String... texts) {\n        return String.join(DELIMITER, texts);\n    }\n\n    static String[] split(String text) {\n        return text.split(\"\\\\\" + DELIMITER);\n    }\n\n    interface GlobalCanaryLabels {\n        String SERVICE_HEADERS = join(GLOBAL_CANARY_LABELS, \"serviceHeaders\");\n\n        static String extractHeaderName(String full) {\n            String prefix = SERVICE_HEADERS + DELIMITER;\n            int idx = full.indexOf(prefix);\n            if (idx < 0) {\n                return null;\n            }\n            String remain = full.substring(idx + prefix.length());\n            String[] arr = remain.split(\"\\\\\" + DELIMITER);\n            if (arr.length < 3) {\n                return null;\n            }\n            return arr[2];\n        }\n    }\n\n    interface Observability {\n        String KEY_COMM_ENABLED = \"enabled\";\n        String KEY_COMM_SAMPLED_TYPE = \"sampledType\";\n        String KEY_COMM_SAMPLED = \"sampled\";\n        String KEY_COMM_OUTPUT = \"output\";\n        String KEY_COMM_TAG = \"tag\";\n        String KEY_COMM_SERVICE_PREFIX = \"servicePrefix\";\n        String KEY_COMM_INTERVAL = \"interval\";\n        String KEY_COMM_INTERVAL_UNIT = \"intervalUnit\";\n        String KEY_COMM_TOPIC = \"topic\";\n        String KEY_COMM_APPEND_TYPE = \"appendType\";\n\n        String OUTPUT = join(OBSERVABILITY, \"outputServer\");\n\n        String OUTPUT_SERVERS = join(OUTPUT, \"bootstrapServer\");\n        String OUTPUT_TIMEOUT = join(OUTPUT, \"timeout\");\n        String OUTPUT_ENABLED = join(OUTPUT, \"enabled\");\n\n        String OUTPUT_SECURITY_PROTOCOL = join(OUTPUT, \"security.protocol\");\n        String OUTPUT_SSL_KEYSTORE_TYPE = join(OUTPUT, \"ssl.keystore.type\");\n        String OUTPUT_KEY = join(OUTPUT, \"ssl.keystore.key\");\n        String OUTPUT_CERT = join(OUTPUT, \"ssl.keystore.certificate.chain\");\n        String OUTPUT_TRUST_CERT = join(OUTPUT, \"ssl.truststore.certificates\");\n        String OUTPUT_TRUST_CERT_TYPE = join(OUTPUT, \"ssl.truststore.type\");\n        String OUTPUT_ENDPOINT_IDENTIFICATION_ALGORITHM = join(OUTPUT, \"ssl.endpoint.identification.algorithm\");\n\n        String METRICS = join(OBSERVABILITY, \"metrics\");\n        String TRACE = join(OBSERVABILITY, \"tracings\");\n\n        String METRICS_ENABLED = join(METRICS, \"enabled\");\n\n        String TRACE_ENABLED = join(TRACE, \"enabled\");\n        String TRACE_SAMPLED_TYPE = join(TRACE, KEY_COMM_SAMPLED_TYPE);\n        String TRACE_SAMPLED = join(TRACE, KEY_COMM_SAMPLED);\n        String TRACE_OUTPUT = join(TRACE, KEY_COMM_OUTPUT);\n        String TRACE_OUTPUT_ENABLED = join(TRACE_OUTPUT, \"enabled\");\n        String TRACE_OUTPUT_TOPIC = join(TRACE_OUTPUT, \"topic\");\n        String TRACE_OUTPUT_REPORT_THREAD = join(TRACE_OUTPUT, \"reportThread\");\n        String TRACE_OUTPUT_MESSAGE_MAX_BYTES = join(TRACE_OUTPUT, \"messageMaxBytes\");\n        String TRACE_OUTPUT_MESSAGE_TIMEOUT = join(TRACE_OUTPUT, \"messageTimeout\");\n        String TRACE_OUTPUT_QUEUED_MAX_SPANS = join(TRACE_OUTPUT, \"queuedMaxSpans\");\n        String TRACE_OUTPUT_QUEUED_MAX_SIZE = join(TRACE_OUTPUT, \"queuedMaxSize\");\n\n        String KEY_METRICS_ACCESS = \"access\";\n        String KEY_METRICS_REQUEST = \"request\";\n        String KEY_METRICS_JDBC_STATEMENT = \"jdbcStatement\";\n        String KEY_METRICS_JDBC_CONNECTION = \"jdbcConnection\";\n        String KEY_METRICS_RABBIT = \"rabbit\";\n        String KEY_METRICS_KAFKA = \"kafka\";\n        String KEY_METRICS_CACHE = \"redis\";\n        String KEY_METRICS_JVM_GC = \"jvmGc\";\n        String KEY_METRICS_JVM_MEMORY = \"jvmMemory\";\n        String KEY_METRICS_MD5_DICTIONARY = \"md5Dictionary\";\n\n        String KEY_TRACE_REQUEST = \"request\";\n        String KEY_TRACE_REMOTE_INVOKE = \"remoteInvoke\";\n        String KEY_TRACE_KAFKA = \"kafka\";\n        String KEY_TRACE_JDBC = \"jdbc\";\n        String KEY_TRACE_CACHE = \"redis\";\n        String KEY_TRACE_RABBIT = \"rabbit\";\n\n    }\n\n    interface Plugin {\n        String OBSERVABILITY_GLOBAL_METRIC_ENABLED = join(PLUGIN, OBSERVABILITY, PLUGIN_GLOBAL, METRIC_SERVICE_ID, SERVICE_ID_ENABLED_KEY);\n        String OBSERVABILITY_GLOBAL_TRACING_ENABLED = join(PLUGIN, OBSERVABILITY, PLUGIN_GLOBAL, TRACING_SERVICE_ID, SERVICE_ID_ENABLED_KEY);\n    }\n\n    interface Namespace {\n        String ASYNC = \"async\";\n        String ELASTICSEARCH = \"elasticsearch\";\n        String HTTP_SERVLET = \"httpServlet\";\n\n        String TOMCAT = \"tomcat\";\n        String JDBC = \"jdbc\";\n        String JDBC_CONNECTION = \"jdbcConnection\";\n        String JDBC_STATEMENT = \"jdbcStatement\";\n        String KAFKA = \"kafka\";\n        String RABBITMQ = \"rabbitmq\";\n        String REDIS = \"redis\";\n        String SERVICE_NAME = \"serviceName\";\n        String HEALTH = \"health\";\n        String ACCESS = \"access\";\n        String SPRING_GATEWAY = \"springGateway\";\n        String MD5_DICTIONARY = \"md5Dictionary\";\n\n        // ------------ rpc ----------------------\n        String DUBBO = \"dubbo\";\n        String MOTAN = \"motan\";\n        String SOFARPC = \"sofarpc\";\n\n\n        // -------------  request  ------------------\n        String HTTPCLIENT = \"httpclient\";\n        String OK_HTTP = \"okHttp\";\n        String WEB_CLIENT = \"webclient\";\n        String FEIGN_CLIENT = \"feignClient\";\n        String REST_TEMPLATE = \"resTemplate\";\n        String HTTPURLCONNECTION = \"httpURLConnection\";\n\n        String FORWARDED = \"forwarded\";\n    }\n\n    interface PluginID {\n        String TRACING = \"tracing\";\n        String METRIC = \"metric\";\n        String LOG = \"log\";\n        String INIT = \"init\";\n        String REDIRECT = \"redirect\";\n        String FORWARDED = \"forwarded\";\n    }\n\n    interface CodeVersion{\n        String KEY_JDK = \"jdk\";\n        String KEY_SPRING_BOOT = \"spring-boot\";\n\n        String VERSION_JDK8 = \"jdk8\";\n        String VERSION_JDK17 = \"jdk17\";\n        String VERSION_SPRING_BOOT2 = \"2.x.x\";\n        String VERSION_SPRING_BOOT3 = \"3.x.x\";\n    }\n\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/config/Const.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.config;\n\npublic interface Const {\n    int MAX_PLUGIN_STACK = 10000;\n    String ENABLED_CONFIG = \"enabled\";\n\n    int METRIC_DEFAULT_INTERVAL = 30;\n    String METRIC_DEFAULT_INTERVAL_UNIT = \"SECONDS\";\n    String METRIC_DEFAULT_TOPIC = \"application-meter\";\n\n    String DEFAULT_APPEND_TYPE = \"console\";\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/config/IConfigFactory.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.config;\n\npublic interface IConfigFactory {\n    /**\n     * Returns the global configuration of this Java agent.\n     *\n     * @return The global configuration of this Java agent.\n     */\n    Config getConfig();\n\n    /**\n     * Returns a configuration property from the agent's all configuration.\n     *\n     * @return The configuration of this Java agent.\n     */\n    String getConfig(String property);\n\n    String getConfig(String property, String defaultValue);\n\n    /**\n     * Returns the agent's plugin configuration.\n     *\n     * @return The configuration of a special plugin of Java agent.\n     */\n    IPluginConfig getConfig(String domain, String namespace, String name);\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/config/IPluginConfig.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.config;\n\nimport java.util.List;\nimport java.util.Set;\n\npublic interface IPluginConfig {\n    String domain();\n\n    String namespace();\n\n    String id();\n\n    boolean hasProperty(String property);\n\n    String getString(String property);\n\n    Integer getInt(String property);\n\n    Boolean getBoolean(String property);\n\n    default boolean enabled() {\n        Boolean b = getBoolean(Const.ENABLED_CONFIG);\n        if (b == null) {\n            return false;\n        }\n        return b;\n    }\n\n    default Boolean getBoolean(String property, Boolean defaultValue) {\n        Boolean ret;\n        if (!hasProperty(property)) {\n            return defaultValue;\n        } else {\n            ret = getBoolean(property);\n            return ret != null ? ret : defaultValue;\n        }\n    }\n\n    Double getDouble(String property);\n\n    Long getLong(String property);\n\n    List<String> getStringList(String property);\n\n    IPluginConfig getGlobal();\n\n    Set<String> keySet();\n\n    void addChangeListener(PluginConfigChangeListener listener);\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/config/PluginConfigChangeListener.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.config;\n\npublic interface PluginConfigChangeListener {\n    void onChange(IPluginConfig oldConfig, IPluginConfig newConfig);\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/context/AsyncContext.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.context;\n\nimport com.megaease.easeagent.plugin.api.Cleaner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.trace.SpanContext;\n\nimport java.util.Map;\n\n/**\n * An asynchronous thread snapshot context\n * code example:\n * <pre>{@code\n *  AsyncContext asyncContext = context.exportAsync();\n *  class Run implements Runnable{\n *      void run(){\n *          try (Cleaner cleaner = asyncContext.importToCurrent()) {\n *               //do something\n *               //or EaseAgent.getContext().nextSpan();\n *          }\n *      }\n *  }\n *  }</pre>\n */\npublic interface AsyncContext {\n    /**\n     * When true, do nothing and nothing is reported . However, this AsyncContext should\n     * still be injected into outgoing requests. Use this flag to avoid performing expensive\n     * computation.\n     */\n    boolean isNoop();\n\n\n    /**\n     * get Span Context for Tracing\n     *\n     * @return SpanContext\n     */\n    SpanContext getSpanContext();\n\n    /**\n     * Import this AsyncContext to current {@link Context} and return a {@link com.megaease.easeagent.plugin.api.Cleaner}\n     * <p>\n     * The Cleaner must be close after business:\n     * <p>\n     * example:\n     * <pre>{@code\n     *    void callback(AsyncContext ac){\n     *       try (Cleaner cleaner = ac.importAsync()) {\n     *          //do business\n     *       }\n     *    }\n     * }</pre>\n     *\n     * @return {@link com.megaease.easeagent.plugin.api.Cleaner}\n     */\n    Cleaner importToCurrent();\n\n    /**\n     * @return all async snapshot context key:value\n     */\n    Map<Object, Object> getAll();\n\n    /**\n     * Returns the value to which the specified key is mapped,\n     * or {@code null} if this context contains no mapping for the key.\n     *\n     * <p>More formally, if this context contains a mapping from a key\n     * {@code k} to a value {@code v} such that {@code (key==null ? k==null :\n     * key.equals(k))}, then this method returns {@code v}; otherwise\n     * it returns {@code null}.  (There can be at most one such mapping.)\n     *\n     * <p>If this context permits null values, then a return value of\n     * {@code null} does not <i>necessarily</i> indicate that the context\n     * contains no mapping for the key; it's also possible that the context\n     * explicitly maps the key to {@code null}.\n     *\n     * @param key the key whose associated value is to be returned\n     * @return the value to which the specified key is mapped, or\n     * {@code null} if this context contains no mapping for the key\n     * @throws ClassCastException if the key is of an inappropriate type for\n     *                            this context\n     *                            (<a href=\"{@docRoot}/java/util/Collection.html#optional-restrictions\">optional</a>)\n     */\n    <T> T get(Object key);\n\n    /**\n     * Associates the specified value with the specified key in this context\n     * (optional operation).  If the context previously contained a mapping for\n     * the key, the old value is replaced by the specified value.  (A context\n     * <tt>m</tt> is said to contain a mapping for a key <tt>k</tt>\n     *\n     * @param key   key with which the specified value is to be associated\n     * @param value value to be associated with the specified key\n     * @return the previous value associated with <tt>key</tt>, or\n     * <tt>null</tt> if there was no mapping for <tt>key</tt>.\n     * (A <tt>null</tt> return can also indicate that the context\n     * previously associated <tt>null</tt> with <tt>key</tt>,\n     * if the implementation supports <tt>null</tt> values.)\n     * @throws ClassCastException if the class of the specified key or value\n     *                            prevents it from being stored in this context\n     */\n    <V> V put(Object key, V value);\n\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/context/ContextCons.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.context;\n\n@SuppressWarnings(\"unused\")\npublic interface ContextCons {\n    String CACHE_CMD = ContextCons.class.getName() + \".cache_cmd\";\n    String CACHE_URI = ContextCons.class.getName() + \".cache_uri\";\n    String MQ_URI = ContextCons.class.getName() + \".mq_uri\";\n    String ASYNC_FLAG = ContextCons.class.getName() + \".async\";\n    String SPAN = ContextCons.class.getName() + \".Span\";\n    String PROCESSED_BEFORE = ContextCons.class.getName() + \".Processed-Before\";\n    String PROCESSED_AFTER = ContextCons.class.getName() + \".Processed-After\";\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/context/ContextUtils.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.context;\n\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.utils.SystemClock;\n\npublic class ContextUtils {\n    private static final String BEGIN_TIME = ContextUtils.class.getSimpleName() + \".beginTime\";\n    private static final String END_TIME = ContextUtils.class.getSimpleName() + \".endTime\";\n\n    public static void setBeginTime(Context context) {\n        context.put(BEGIN_TIME, SystemClock.now());\n    }\n\n    public static Long getBeginTime(Context context) {\n        return context.get(BEGIN_TIME);\n    }\n\n    public static Long getEndTime(Context context) {\n        Long endTime = context.remove(END_TIME);\n        if (endTime == null) {\n            return SystemClock.now();\n        }\n        return endTime;\n    }\n\n    public static Long getDuration(Context context) {\n        return getEndTime(context) - getBeginTime(context);\n    }\n\n    public static Long getDuration(Context context, Object startKey) {\n        Long now = SystemClock.now();\n        return now - (Long)context.remove(startKey);\n    }\n\n    /**\n     * Get data from context\n     *\n     * @param context Store data\n     * @param key     key is the type of data. Like {@code value.getClass()}\n     * @param <T>     The type of data\n     * @return data\n     */\n    public static <T> T getFromContext(Context context, Object key) {\n        return context.get(key);\n    }\n\n    /**\n     * Remove data from context\n     * @param context data store\n     * @param key key is the type of data.\n     * @return\n     * @param <T> the type of data\n     */\n    public static <T> T removeFromContext(Context context, Object key) {\n        return context.remove(key);\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/context/IContextManager.java",
    "content": "/*\n * Copyright (c) 2022, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.plugin.api.context;\n\nimport com.megaease.easeagent.plugin.api.InitializeContext;\n\npublic interface IContextManager {\n    /**\n     * Get current context or create a context\n     * @return context\n     */\n    InitializeContext getContext();\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/context/RequestContext.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.context;\n\nimport com.megaease.easeagent.plugin.api.trace.Response;\nimport com.megaease.easeagent.plugin.api.trace.Scope;\nimport com.megaease.easeagent.plugin.api.trace.Setter;\nimport com.megaease.easeagent.plugin.api.trace.Span;\n\nimport java.util.Map;\n\n/**\n * A cross-process data context, including tracing and Forwarded Headers\n * <p>\n * The Scope must be close after plugin:\n *\n * <pre>{@code\n *    void after(...){\n *       RequestContext rc = context.get(...)\n *       try{\n *\n *       }finally{\n *           rc.scope().close();\n *       }\n *    }\n * }</pre>\n */\npublic interface RequestContext extends Setter {\n    /**\n     * When true, do nothing and nothing is reported . However, this RequestContext should\n     * still be injected into outgoing requests. Use this flag to avoid performing expensive\n     * computation.\n     */\n    boolean isNoop();\n\n    /**\n     * @return {@link Span} for next progress client span\n     */\n    Span span();\n\n    /**\n     * The Scope must be close after plugin:\n     *\n     * <pre>{@code\n     *    void after(...){\n     *       RequestContext rc = context.get(...)\n     *       try{\n     *\n     *       }finally{\n     *           rc.scope().close();\n     *       }\n     *    }\n     * }</pre>\n     *\n     * @return {@link Scope} for current Span\n     */\n    Scope scope();\n\n    /**\n     * set header for next progress\n     *\n     * @param name  of header\n     * @param value of header\n     */\n    void setHeader(String name, String value);\n\n    /**\n     * @return headers from the progress data context\n     */\n    Map<String, String> getHeaders();\n\n    /**\n     * finish the progress span and save tag from {@link Response#header(String)}\n     *\n     * @param response {@link Response}\n     */\n    void finish(Response response);\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/dispatcher/IDispatcher.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.dispatcher;\n\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.api.Context;\n\npublic interface IDispatcher {\n    void enter(int chainIndex, MethodInfo info);\n\n    Object exit(int chainIndex, MethodInfo methodInfo,\n                Context context, Object result, Throwable e);\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/health/AgentHealth.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.health;\n\npublic class AgentHealth {\n    private volatile boolean readinessEnabled;\n    private volatile boolean alive = true;\n    private volatile boolean ready;\n\n    public static final AgentHealth INSTANCE = new AgentHealth();\n\n    public static void setReady(boolean ready) {\n        INSTANCE.ready = ready;\n    }\n\n    public static void setReadinessEnabled(boolean readinessEnabled) {\n        INSTANCE.readinessEnabled = readinessEnabled;\n    }\n\n    public static void setAlive(boolean alive) {\n        INSTANCE.alive = alive;\n    }\n\n    public boolean isReadinessEnabled() {\n        return readinessEnabled;\n    }\n\n    public boolean isAlive() {\n        return alive;\n    }\n\n    public boolean isReady() {\n        return ready;\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/logging/AccessLogInfo.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.logging;\n\nimport com.fasterxml.jackson.annotation.JsonIgnore;\nimport com.fasterxml.jackson.annotation.JsonProperty;\nimport com.fasterxml.jackson.core.type.TypeReference;\nimport com.megaease.easeagent.plugin.report.EncodedData;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\n\nimport java.util.Map;\n\n@Data\n@NoArgsConstructor\npublic class AccessLogInfo {\n    public static final TypeReference<AccessLogInfo> TYPE_REFERENCE = new TypeReference<AccessLogInfo>() {\n    };\n\n    @JsonProperty(\"span_id\")\n    protected String spanId;\n\n    @JsonProperty(\"trace_id\")\n    protected String traceId;\n\n    @JsonProperty(\"pspan_id\")\n    protected String parentSpanId;\n\n    private String type = \"access-log\";\n\n    private String service;\n\n    private String system;\n\n    @JsonProperty(\"client_ip\")\n    private String clientIP = \"-\";\n\n    private String user = \"-\";\n\n    @JsonProperty(\"response_size\")\n    private int responseSize;\n\n    private long beginTime;\n\n    private long beginCpuTime;\n\n    @JsonProperty(\"request_time\")\n    private long requestTime;\n\n    private long cpuElapsedTime;\n\n    private String url;\n\n    private String method;\n\n    @JsonProperty(\"status_code\")\n    private String statusCode;\n\n    @JsonProperty(\"host_name\")\n    private String hostName;\n\n    @JsonProperty(\"host_ipv4\")\n    private String hostIpv4;\n\n    private String category = \"application\";\n\n    @JsonProperty(\"match_url\")\n    private String matchUrl;\n\n    private Map<String, String> headers;\n\n    private Map<String, String> queries;\n\n    private long timestamp;\n\n    @JsonIgnore\n    private EncodedData encodedData;\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/logging/ILoggerFactory.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.logging;\n\npublic interface ILoggerFactory {\n    /**\n     * Returns a logger that logs to the Java agent log output.\n     * @return A log where messages can be written to the Java agent log file or console.\n     */\n    public Logger getLogger(String name);\n\n    default public Logger getLogger(Class<?> clazz) {\n        return getLogger(clazz.getCanonicalName());\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/logging/Logger.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.logging;\n\n/**\n * This interface extract from slf4j-api to avoid conflict with user's application's usage of slf4j\n * By warping log4j2, Logger is compatible with slf4j interface.\n */\npublic interface Logger {\n    /**\n     * Case insensitive String constant used to retrieve the name of the root logger.\n     */\n    final public String ROOT_LOGGER_NAME = \"ROOT\";\n\n    /**\n     * Return the name of this <code>Logger</code> instance.\n     * @return name of this logger instance\n     */\n    public String getName();\n\n    /**\n     * Is the logger instance enabled for the TRACE level?\n     *\n     * @return True if this Logger is enabled for the TRACE level,\n     *         false otherwise.\n     */\n    public boolean isTraceEnabled();\n\n    /**\n     * Log a message at the TRACE level.\n     *\n     * @param msg the message string to be logged\n     */\n    public void trace(String msg);\n\n    /**\n     * Log a message at the TRACE level according to the specified format\n     * and argument.\n     * <p/>\n     * <p>This form avoids superfluous object creation when the logger\n     * is disabled for the TRACE level. </p>\n     *\n     * @param format the format string\n     * @param arg    the argument\n     */\n    public void trace(String format, Object arg);\n\n    /**\n     * Log a message at the TRACE level according to the specified format\n     * and arguments.\n     * <p/>\n     * <p>This form avoids superfluous object creation when the logger\n     * is disabled for the TRACE level. </p>\n     *\n     * @param format the format string\n     * @param arg1   the first argument\n     * @param arg2   the second argument\n     */\n    public void trace(String format, Object arg1, Object arg2);\n\n    /**\n     * Log a message at the TRACE level according to the specified format\n     * and arguments.\n     * <p/>\n     * <p>This form avoids superfluous string concatenation when the logger\n     * is disabled for the TRACE level. However, this variant incurs the hidden\n     * (and relatively small) cost of creating an <code>Object[]</code> before invoking the method,\n     * even if this logger is disabled for TRACE. The variants taking {@link #trace(String, Object) one} and\n     * {@link #trace(String, Object, Object) two} arguments exist solely in order to avoid this hidden cost.</p>\n     *\n     * @param format    the format string\n     * @param arguments a list of 3 or more arguments\n     */\n    public void trace(String format, Object... arguments);\n\n    /**\n     * Log an exception (throwable) at the TRACE level with an\n     * accompanying message.\n     *\n     * @param msg the message accompanying the exception\n     * @param t   the exception (throwable) to log\n     */\n    public void trace(String msg, Throwable t);\n\n    /**\n     * Is the logger instance enabled for the DEBUG level?\n     *\n     * @return True if this Logger is enabled for the DEBUG level,\n     *         false otherwise.\n     */\n    public boolean isDebugEnabled();\n\n    /**\n     * Log a message at the DEBUG level.\n     *\n     * @param msg the message string to be logged\n     */\n    public void debug(String msg);\n\n    /**\n     * Log a message at the DEBUG level according to the specified format\n     * and argument.\n     * <p/>\n     * <p>This form avoids superfluous object creation when the logger\n     * is disabled for the DEBUG level. </p>\n     *\n     * @param format the format string\n     * @param arg    the argument\n     */\n    public void debug(String format, Object arg);\n\n    /**\n     * Log a message at the DEBUG level according to the specified format\n     * and arguments.\n     * <p/>\n     * <p>This form avoids superfluous object creation when the logger\n     * is disabled for the DEBUG level. </p>\n     *\n     * @param format the format string\n     * @param arg1   the first argument\n     * @param arg2   the second argument\n     */\n    public void debug(String format, Object arg1, Object arg2);\n\n    /**\n     * Log a message at the DEBUG level according to the specified format\n     * and arguments.\n     * <p/>\n     * <p>This form avoids superfluous string concatenation when the logger\n     * is disabled for the DEBUG level. However, this variant incurs the hidden\n     * (and relatively small) cost of creating an <code>Object[]</code> before invoking the method,\n     * even if this logger is disabled for DEBUG. The variants taking\n     * {@link #debug(String, Object) one} and {@link #debug(String, Object, Object) two}\n     * arguments exist solely in order to avoid this hidden cost.</p>\n     *\n     * @param format    the format string\n     * @param arguments a list of 3 or more arguments\n     */\n    public void debug(String format, Object... arguments);\n\n    /**\n     * Log an exception (throwable) at the DEBUG level with an\n     * accompanying message.\n     *\n     * @param msg the message accompanying the exception\n     * @param t   the exception (throwable) to log\n     */\n    public void debug(String msg, Throwable t);\n\n    /**\n     * Is the logger instance enabled for the INFO level?\n     *\n     * @return True if this Logger is enabled for the INFO level,\n     *         false otherwise.\n     */\n    public boolean isInfoEnabled();\n\n    /**\n     * Log a message at the INFO level.\n     *\n     * @param msg the message string to be logged\n     */\n    public void info(String msg);\n\n    /**\n     * Log a message at the INFO level according to the specified format\n     * and argument.\n     * <p/>\n     * <p>This form avoids superfluous object creation when the logger\n     * is disabled for the INFO level. </p>\n     *\n     * @param format the format string\n     * @param arg    the argument\n     */\n    public void info(String format, Object arg);\n\n    /**\n     * Log a message at the INFO level according to the specified format\n     * and arguments.\n     * <p/>\n     * <p>This form avoids superfluous object creation when the logger\n     * is disabled for the INFO level. </p>\n     *\n     * @param format the format string\n     * @param arg1   the first argument\n     * @param arg2   the second argument\n     */\n    public void info(String format, Object arg1, Object arg2);\n\n    /**\n     * Log a message at the INFO level according to the specified format\n     * and arguments.\n     * <p/>\n     * <p>This form avoids superfluous string concatenation when the logger\n     * is disabled for the INFO level. However, this variant incurs the hidden\n     * (and relatively small) cost of creating an <code>Object[]</code> before invoking the method,\n     * even if this logger is disabled for INFO. The variants taking\n     * {@link #info(String, Object) one} and {@link #info(String, Object, Object) two}\n     * arguments exist solely in order to avoid this hidden cost.</p>\n     *\n     * @param format    the format string\n     * @param arguments a list of 3 or more arguments\n     */\n    public void info(String format, Object... arguments);\n\n    /**\n     * Log an exception (throwable) at the INFO level with an\n     * accompanying message.\n     *\n     * @param msg the message accompanying the exception\n     * @param t   the exception (throwable) to log\n     */\n    public void info(String msg, Throwable t);\n\n    /**\n     * Is the logger instance enabled for the WARN level?\n     *\n     * @return True if this Logger is enabled for the WARN level,\n     *         false otherwise.\n     */\n    public boolean isWarnEnabled();\n\n    /**\n     * Log a message at the WARN level.\n     *\n     * @param msg the message string to be logged\n     */\n    public void warn(String msg);\n\n    /**\n     * Log a message at the WARN level according to the specified format\n     * and argument.\n     * <p/>\n     * <p>This form avoids superfluous object creation when the logger\n     * is disabled for the WARN level. </p>\n     *\n     * @param format the format string\n     * @param arg    the argument\n     */\n    public void warn(String format, Object arg);\n\n    /**\n     * Log a message at the WARN level according to the specified format\n     * and arguments.\n     * <p/>\n     * <p>This form avoids superfluous string concatenation when the logger\n     * is disabled for the WARN level. However, this variant incurs the hidden\n     * (and relatively small) cost of creating an <code>Object[]</code> before invoking the method,\n     * even if this logger is disabled for WARN. The variants taking\n     * {@link #warn(String, Object) one} and {@link #warn(String, Object, Object) two}\n     * arguments exist solely in order to avoid this hidden cost.</p>\n     *\n     * @param format    the format string\n     * @param arguments a list of 3 or more arguments\n     */\n    public void warn(String format, Object... arguments);\n\n    /**\n     * Log a message at the WARN level according to the specified format\n     * and arguments.\n     * <p/>\n     * <p>This form avoids superfluous object creation when the logger\n     * is disabled for the WARN level. </p>\n     *\n     * @param format the format string\n     * @param arg1   the first argument\n     * @param arg2   the second argument\n     */\n    public void warn(String format, Object arg1, Object arg2);\n\n    /**\n     * Log an exception (throwable) at the WARN level with an\n     * accompanying message.\n     *\n     * @param msg the message accompanying the exception\n     * @param t   the exception (throwable) to log\n     */\n    public void warn(String msg, Throwable t);\n\n    /**\n     * Is the logger instance enabled for the ERROR level?\n     *\n     * @return True if this Logger is enabled for the ERROR level,\n     *         false otherwise.\n     */\n    public boolean isErrorEnabled();\n\n    /**\n     * Log a message at the ERROR level.\n     *\n     * @param msg the message string to be logged\n     */\n    public void error(String msg);\n\n    /**\n     * Log a message at the ERROR level according to the specified format\n     * and argument.\n     * <p/>\n     * <p>This form avoids superfluous object creation when the logger\n     * is disabled for the ERROR level. </p>\n     *\n     * @param format the format string\n     * @param arg    the argument\n     */\n    public void error(String format, Object arg);\n\n    /**\n     * Log a message at the ERROR level according to the specified format\n     * and arguments.\n     * <p/>\n     * <p>This form avoids superfluous object creation when the logger\n     * is disabled for the ERROR level. </p>\n     *\n     * @param format the format string\n     * @param arg1   the first argument\n     * @param arg2   the second argument\n     */\n    public void error(String format, Object arg1, Object arg2);\n\n    /**\n     * Log a message at the ERROR level according to the specified format\n     * and arguments.\n     * <p/>\n     * <p>This form avoids superfluous string concatenation when the logger\n     * is disabled for the ERROR level. However, this variant incurs the hidden\n     * (and relatively small) cost of creating an <code>Object[]</code> before invoking the method,\n     * even if this logger is disabled for ERROR. The variants taking\n     * {@link #error(String, Object) one} and {@link #error(String, Object, Object) two}\n     * arguments exist solely in order to avoid this hidden cost.</p>\n     *\n     * @param format    the format string\n     * @param arguments a list of 3 or more arguments\n     */\n    public void error(String format, Object... arguments);\n\n    /**\n     * Log an exception (throwable) at the ERROR level with an\n     * accompanying message.\n     *\n     * @param msg the message accompanying the exception\n     * @param t   the exception (throwable) to log\n     */\n    public void error(String msg, Throwable t);\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/logging/Mdc.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.logging;\n\npublic interface Mdc {\n    void put(String key, String value);\n\n    void remove(String key);\n\n    String get(String key);\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/metric/Counter.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.metric;\n\n/**\n * An incrementing and decrementing counter metric.\n */\npublic interface Counter extends Metric {\n    /**\n     * Increment the counter by one.\n     */\n    void inc();\n\n    /**\n     * Increment the counter by {@code n}.\n     *\n     * @param n the amount by which the counter will be increased\n     */\n    void inc(long n);\n\n    /**\n     * Decrement the counter by one.\n     */\n    void dec();\n\n    /**\n     * Decrement the counter by {@code n}.\n     *\n     * @param n the amount by which the counter will be decreased\n     */\n    void dec(long n);\n\n    /**\n     * Returns the counter's current value.\n     *\n     * @return the counter's current value\n     */\n    long getCount();\n\n    /**\n     * Returns the underlying Counter object or {@code null} if there is none. Here is a Counter\n     * objects: {@code com.codahale.metrics.Counter}\n     *\n     * @return\n     */\n    Object unwrap();\n}\n\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/metric/Gauge.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.metric;\n\n/**\n * A gauge metric is an instantaneous reading of a particular value. To instrument a queue's depth,\n * for example:<br>\n * <pre><code>\n * final Queue&lt;String&gt; queue = new ConcurrentLinkedQueue&lt;String&gt;();\n * final Gauge&lt;Integer&gt; queueDepth = new Gauge&lt;Integer&gt;() {\n *     public Integer getValue() {\n *         return queue.size();\n *     }\n * };\n * </code></pre>\n *\n * @param <T> the type of the metric's value\n */\npublic interface Gauge<T> extends Metric {\n    /**\n     * Returns the metric's current value.\n     *\n     * @return the metric's current value\n     */\n    T getValue();\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/metric/Histogram.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.metric;\n\n/**\n * A metric which calculates the distribution of a value.\n *\n * @see <a href=\"http://www.johndcook.com/standard_deviation.html\">Accurately computing running\n * variance</a>\n */\npublic interface Histogram extends Metric {\n    /**\n     * Adds a recorded value.\n     *\n     * @param value the length of the value\n     */\n    void update(int value);\n\n    /**\n     * Adds a recorded value.\n     *\n     * @param value the length of the value\n     */\n    void update(long value);\n\n    /**\n     * Returns the number of values recorded.\n     *\n     * @return the number of values recorded\n     */\n    long getCount();\n\n    /**\n     * Returns a snapshot of the values.\n     *\n     * @return a snapshot of the values\n     */\n    Snapshot getSnapshot();\n\n    /**\n     * Returns the underlying Histogram object or {@code null} if there is none. Here is a Histogram\n     * objects: {@code com.codahale.metrics.Histogram}\n     *\n     * @return\n     */\n    Object unwrap();\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/metric/Meter.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.metric;\n\n/**\n * A meter metric which measures mean throughput and one-, five-, and fifteen-minute\n * moving average throughput.\n */\npublic interface Meter extends Metric {\n\n    /**\n     * Mark the occurrence of an event.\n     */\n    void mark();\n\n    /**\n     * Mark the occurrence of a given number of events.\n     *\n     * @param n the number of events\n     */\n    void mark(long n);\n\n    /**\n     * Returns the number of events which have been marked.\n     *\n     * @return the number of events which have been marked\n     */\n    long getCount();\n\n    /**\n     * Returns the fifteen-minute moving average rate at which events have\n     * occurred since the meter was created.\n     *\n     * @return the fifteen-minute moving average rate at which events have\n     * occurred since the meter was created\n     */\n    double getFifteenMinuteRate();\n\n    /**\n     * Returns the five-minute moving average rate at which events have\n     * occurred since the meter was created.\n     *\n     * @return the five-minute moving average rate at which events have\n     * occurred since the meter was created\n     */\n    double getFiveMinuteRate();\n\n    /**\n     * Returns the mean rate at which events have occurred since the meter was created.\n     *\n     * @return the mean rate at which events have occurred since the meter was created\n     */\n\n    double getMeanRate();\n\n    /**\n     * Returns the one-minute moving average rate at which events have\n     * occurred since the meter was created.\n     *\n     * @return the one-minute moving average rate at which events have\n     * occurred since the meter was created\n     */\n    double getOneMinuteRate();\n\n    /**\n     * Returns the underlying Meter object or {@code null} if there is none. Here is a Meter\n     * objects: {@code com.codahale.metrics.Meter}\n     *\n     * @return\n     */\n    Object unwrap();\n}\n\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/metric/Metric.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.metric;\n\n/**\n * A tag interface to indicate that a class is a metric.\n */\npublic interface Metric {\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/metric/MetricProvider.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.metric;\n\npublic interface MetricProvider {\n    MetricRegistrySupplier metricSupplier();\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/metric/MetricRegistry.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.metric;\n\nimport java.util.Map;\nimport java.util.function.Supplier;\n\n/**\n * A registry of metric interface.\n */\npublic interface MetricRegistry {\n\n    /**\n     * Removes the metric with the given name.\n     *\n     * @param name the name of the metric\n     * @return whether or not the metric was removed\n     */\n    boolean remove(String name);\n\n    /**\n     * get all metrics\n     *\n     * @return Map<String, Metric>\n     */\n    Map<String, Metric> getMetrics();\n\n    /**\n     * Return the {@link Meter} registered under this name; or create and register\n     * a new {@link Meter} if none is registered.\n     *\n     * @param name the name of the metric\n     * @return a new or pre-existing {@link Meter}\n     */\n    Meter meter(String name);\n\n    /**\n     * Return the {@link Counter} registered under this name; or create and register\n     * a new {@link Counter} if none is registered.\n     *\n     * @param name the name of the metric\n     * @return a new or pre-existing {@link Counter}\n     */\n    Counter counter(String name);\n\n    /**\n     * Return the {@link Gauge} registered under this name; or create and register\n     * a new {@link Gauge} using the provided MetricSupplier if none is registered.\n     *\n     * @param name     the name of the metric\n     * @param supplier a Supplier that can be used to manufacture a Gauge\n     * @return a new or pre-existing {@link Gauge}\n     */\n    @SuppressWarnings(\"rawtypes\")\n    Gauge gauge(String name, MetricSupplier<Gauge> supplier);\n\n    /**\n     * Return the {@link Histogram} registered under this name; or create and register\n     * a new {@link Histogram} if none is registered.\n     *\n     * @param name the name of the metric\n     * @return a new or pre-existing {@link Histogram}\n     */\n    Histogram histogram(String name);\n\n    /**\n     * Return the {@link Timer} registered under this name; or create and register\n     * a new {@link Timer} if none is registered.\n     *\n     * @param name the name of the metric\n     * @return a new or pre-existing {@link Timer}\n     */\n    Timer timer(String name);\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/metric/MetricRegistrySupplier.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.metric;\n\nimport com.megaease.easeagent.plugin.api.Reporter;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.metric.name.MetricField;\nimport com.megaease.easeagent.plugin.api.metric.name.NameFactory;\nimport com.megaease.easeagent.plugin.api.metric.name.Tags;\n\n/**\n * A supplier of MetricRegistry interface.\n */\npublic interface MetricRegistrySupplier {\n    /**\n     * new and return a MetricRegistry for.\n     * Use configure report output:\n     * <pre>{@code\n     *  config.getBoolean(\"enabled\")\n     *  config.getInt(\"interval\")\n     *  config.getString(\"topic\")\n     *  config.getString(\"appendType\")\n     * }</pre>\n     * <p>\n     * <p>\n     * In the metric example here, all metrics are output in the form of json.\n     * All {@code tags} will also be placed in the json text in the form of key:value.\n     * <p>\n     * Different metric types have different values. The same metric will also have different values.\n     * for example:\n     * Timer can calculate count, avg, max and so on.\n     * What type and what value is calculated? What field is this value stored in json?\n     * In this method, the {@code nameFactory} is used for control.\n     * We have implemented some commonly used content by default, or we can customize our own content.\n     *\n     * <pre>{@code\n     * NameFactory nameFactory = NameFactory.createBuilder().counterType(MetricSubType.DEFAULT,\n     *              ImmutableMap.<MetricField, MetricValueFetcher>builder()\n     *             .put(MetricField.EXECUTION_COUNT, MetricValueFetcher.CountingCount)\n     *             .build()).build();\n     * MetricRegistry metricRegistry = supplier.newMetricRegistry(config, nameFactory, new Tags(\"application\", \"http-request\", \"url\"));\n     * metricRegistry.counter(nameFactory.counterName(\"http://127.0.0.1:8080\", MetricSubType.DEFAULT)).inc();\n     * }</pre>\n     * The above code tells the calculation program:\n     * Need a Counter, this Counter calculates the value of {@link MetricField#EXECUTION_COUNT}(key=\"cnt\"},\n     * this value is obtained using the {@link Counter#getCount}  method\n     * <p>\n     * The output is as follows:\n     * <pre>{@code\n     *     {\n     *         \"category\": \"application\",\n     *         \"type\": \"http-request\",\n     *         \"url\": \"http://127.0.0.1:8080\",\n     *         \"cnt\": 1\n     *     }\n     * }</pre>\n     *\n     * @param config      {@link IPluginConfig} metric config\n     * @param nameFactory {@link NameFactory} Calculation description and name description of the value of the metric.\n     * @param tags        {@link Tags} tags of metric\n     * @return {@link MetricRegistry}\n     */\n    MetricRegistry newMetricRegistry(IPluginConfig config, NameFactory nameFactory, Tags tags);\n\n    /**\n     * get plugin metric reporter\n     *\n     * <pre>{@code\n     *     Reporter reporter = supplier.reporter(config);\n     *     reporter.report(\"{'url': 'http://127.0.0.1:8080', 'cnt': 1}\");\n     * }\n     * @param config {@link IPluginConfig} metric config\n     * @return {@link Reporter}\n     */\n    Reporter reporter(IPluginConfig config);\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/metric/MetricSupplier.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.metric;\n\npublic interface MetricSupplier<M extends Metric> {\n    M newMetric();\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/metric/ServiceMetric.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.metric;\n\nimport com.megaease.easeagent.plugin.api.metric.name.MetricSubType;\nimport com.megaease.easeagent.plugin.api.metric.name.NameFactory;\n\nimport javax.annotation.Nonnull;\nimport java.util.function.Supplier;\n\n/**\n * a base Service Metric\n */\npublic abstract class ServiceMetric {\n    protected final MetricRegistry metricRegistry;\n    protected final NameFactory nameFactory;\n\n    public ServiceMetric(@Nonnull MetricRegistry metricRegistry, @Nonnull NameFactory nameFactory) {\n        this.metricRegistry = metricRegistry;\n        this.nameFactory = nameFactory;\n    }\n\n    public Meter meter(String key, MetricSubType subType) {\n        return metricRegistry.meter(nameFactory.meterName(key, subType));\n    }\n\n    public Counter counter(String key, MetricSubType subType) {\n        return metricRegistry.counter(nameFactory.counterName(key, subType));\n    }\n\n    public Gauge gauge(String key, MetricSubType subType, MetricSupplier<Gauge> supplier) {\n        return metricRegistry.gauge(nameFactory.gaugeName(key, subType), supplier);\n    }\n\n    public Histogram histogram(String key, MetricSubType subType) {\n        return metricRegistry.histogram(nameFactory.histogramName(key, subType));\n    }\n\n    public Timer timer(String key, MetricSubType subType) {\n        return metricRegistry.timer(nameFactory.timerName(key, subType));\n    }\n\n    public MetricRegistry getMetricRegistry() {\n        return metricRegistry;\n    }\n\n    public NameFactory getNameFactory() {\n        return nameFactory;\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/metric/ServiceMetricRegistry.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.metric;\n\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.metric.name.NameFactory;\nimport com.megaease.easeagent.plugin.api.metric.name.Tags;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\n\nimport java.lang.reflect.Type;\nimport java.util.Objects;\nimport java.util.concurrent.ConcurrentHashMap;\n\npublic class ServiceMetricRegistry {\n    public static final ConcurrentHashMap<Key, ServiceMetric> INSTANCES = new ConcurrentHashMap<>();\n\n    /**\n     * Obtain an ServiceMetric when it is already registered. If you have not registered, create one and return\n     * The registered {@link Key} is domain, namespace, id, tags and the type by the supplier.\n     *\n     * @param config              {@link IPluginConfig} domain, namespace from id from\n     * @param tags                {@link Tags} metric tags\n     * @param supplier            {@link ServiceMetric} Instance Supplier\n     * @param <T>                 the type of ServiceMetric by the Supplier\n     * @return the type of ServiceMetric by the Supplier\n     */\n    public static <T extends ServiceMetric> T getOrCreate(IPluginConfig config, Tags tags, ServiceMetricSupplier<T> supplier) {\n        return getOrCreate(config.domain(), config.namespace(), config.id(), tags, supplier);\n    }\n\n    /**\n     * Obtain an ServiceMetric when it is already registered. If you have not registered, create one and return\n     * The registered {@link Key} is domain, namespace, id, tags and the type by the supplier.\n     *\n     * @param domain              String\n     * @param namespace           String\n     * @param id                  String\n     * @param tags                {@link Tags} metric tags\n     * @param supplier            {@link ServiceMetric} Instance Supplier\n     * @param <T>                 the type of ServiceMetric by the Supplier\n     * @return the type of ServiceMetric by the Supplier\n     */\n    @SuppressWarnings(\"unchecked\")\n    public static <T extends ServiceMetric> T getOrCreate(String domain, String namespace, String id, Tags tags, ServiceMetricSupplier<T> supplier) {\n        Key key = new Key(domain, namespace, id, tags, supplier.getType());\n        ServiceMetric metric = INSTANCES.get(key);\n        if (metric != null) {\n            return (T) metric;\n        }\n        synchronized (INSTANCES) {\n            metric = INSTANCES.get(key);\n            if (metric != null) {\n                return (T) metric;\n            }\n            IPluginConfig config = EaseAgent.getConfig(domain, namespace, id);\n            NameFactory nameFactory = supplier.newNameFactory();\n            MetricRegistry metricRegistry = EaseAgent.newMetricRegistry(config, nameFactory, tags);\n            T newMetric = supplier.newInstance(metricRegistry, nameFactory);\n            INSTANCES.put(key, newMetric);\n            return newMetric;\n        }\n    }\n\n    static class Key {\n        private final int hash;\n        private final String domain;\n        private final String namespace;\n        private final String id;\n        private final Tags tags;\n        private final Type type;\n\n        public Key(String domain, String namespace, String id, Tags tags, Type type) {\n            this.domain = domain;\n            this.namespace = namespace;\n            this.id = id;\n            this.tags = tags;\n            this.type = type;\n            this.hash = Objects.hash(domain, namespace, id, tags, type);\n        }\n\n        @Override\n        public boolean equals(Object o) {\n            if (this == o) return true;\n            if (o == null || getClass() != o.getClass()) return false;\n            Key key = (Key) o;\n            return hash == key.hash &&\n                Objects.equals(domain, key.domain) &&\n                Objects.equals(namespace, key.namespace) &&\n                Objects.equals(id, key.id) &&\n                Objects.equals(tags, key.tags) &&\n                Objects.equals(type, key.type);\n        }\n\n        @Override\n        public int hashCode() {\n            return hash;\n        }\n\n        @Override\n        public String toString() {\n            return \"Key{\" +\n                \"hash=\" + hash +\n                \", domain='\" + domain + '\\'' +\n                \", namespace='\" + namespace + '\\'' +\n                \", id='\" + id + '\\'' +\n                \", tags=\" + tags +\n                \", type=\" + type +\n                '}';\n        }\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/metric/ServiceMetricSupplier.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.metric;\n\nimport com.megaease.easeagent.plugin.api.metric.name.NameFactory;\n\nimport java.lang.reflect.ParameterizedType;\nimport java.lang.reflect.Type;\n\n/**\n * A {@link NameFactory} Supplier\n *\n * @param <T> the type of ServiceMetric by this Supplier\n */\npublic abstract class ServiceMetricSupplier<T extends ServiceMetric> {\n\n    private final Type type;\n\n    public ServiceMetricSupplier() {\n        Type superClass = getClass().getGenericSuperclass();\n        if (superClass instanceof Class<?>) { // sanity check, should never happen\n            throw new IllegalArgumentException(\"Internal error: TypeReference constructed without actual type information\");\n        }\n        type = ((ParameterizedType) superClass).getActualTypeArguments()[0];\n    }\n\n    /**\n     * the type of ServiceMetric\n     *\n     * @return {@link Type}\n     */\n    public Type getType() {\n        return type;\n    }\n\n    public abstract NameFactory newNameFactory();\n\n    /**\n     * new a ServiceMetric\n     *\n     * @param metricRegistry {@link MetricRegistry}\n     * @param nameFactory    {@link NameFactory}\n     * @return a type of ServiceMetric\n     */\n    public abstract T newInstance(MetricRegistry metricRegistry, NameFactory nameFactory);\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/metric/Snapshot.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.metric;\n\nimport java.io.OutputStream;\n\n/**\n * A statistical snapshot of a {@link Snapshot}.\n */\npublic interface Snapshot extends Metric {\n    /**\n     * Returns the value at the given quantile.\n     *\n     * @param quantile a given quantile, in {@code [0..1]}\n     * @return the value in the distribution at {@code quantile}\n     */\n    double getValue(double quantile);\n\n    /**\n     * Returns the entire set of values in the snapshot.\n     *\n     * @return the entire set of values\n     */\n    long[] getValues();\n\n    /**\n     * Returns the number of values in the snapshot.\n     *\n     * @return the number of values\n     */\n    int size();\n\n    /**\n     * Returns the highest value in the snapshot.\n     *\n     * @return the highest value\n     */\n    long getMax();\n\n    /**\n     * Returns the arithmetic mean of the values in the snapshot.\n     *\n     * @return the arithmetic mean\n     */\n    double getMean();\n\n    /**\n     * Returns the lowest value in the snapshot.\n     *\n     * @return the lowest value\n     */\n    long getMin();\n\n    /**\n     * Returns the standard deviation of the values in the snapshot.\n     *\n     * @return the standard value\n     */\n    double getStdDev();\n\n    /**\n     * Writes the values of the snapshot to the given stream.\n     *\n     * @param output an output stream\n     */\n    void dump(OutputStream output);\n\n    /**\n     * Returns the median value in the distribution.\n     *\n     * @return the median value\n     */\n    default double getMedian() {\n        return getValue(0.5);\n    }\n\n    /**\n     * Returns the value at the 75th percentile in the distribution.\n     *\n     * @return the value at the 75th percentile\n     */\n    default double get75thPercentile() {\n        return getValue(0.75);\n    }\n\n    /**\n     * Returns the value at the 95th percentile in the distribution.\n     *\n     * @return the value at the 95th percentile\n     */\n    default double get95thPercentile() {\n        return getValue(0.95);\n    }\n\n    /**\n     * Returns the value at the 98th percentile in the distribution.\n     *\n     * @return the value at the 98th percentile\n     */\n    default double get98thPercentile() {\n        return getValue(0.98);\n    }\n\n    /**\n     * Returns the value at the 99th percentile in the distribution.\n     *\n     * @return the value at the 99th percentile\n     */\n    default double get99thPercentile() {\n        return getValue(0.99);\n    }\n\n    /**\n     * Returns the value at the 99.9th percentile in the distribution.\n     *\n     * @return the value at the 99.9th percentile\n     */\n    default double get999thPercentile() {\n        return getValue(0.999);\n    }\n\n    /**\n     * Returns the underlying Snapshot object or {@code null} if there is none. Here is a Snapshot\n     * objects: {@code com.codahale.metrics.Snapshot}\n     *\n     * @return\n     */\n    Object unwrap();\n}\n\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/metric/Timer.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.metric;\n\nimport java.time.Duration;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.Supplier;\n\n/**\n * A timer metric which aggregates timing durations and provides duration statistics, plus\n * throughput statistics via {@link Meter}.\n */\npublic interface Timer extends Metric {\n\n    /**\n     * A timing context.\n     *\n     * @see Timer#time()\n     */\n    interface Context {\n\n        /**\n         * Updates the timer with the difference between current and start time. Call to this method will\n         * not reset the start time. Multiple calls result in multiple updates.\n         *\n         * @return the elapsed time in nanoseconds\n         */\n        long stop();\n\n        /**\n         * Equivalent to calling {@link #stop()}.\n         */\n        void close();\n    }\n\n    /**\n     * Adds a recorded duration.\n     *\n     * @param duration the length of the duration\n     * @param unit     the scale unit of {@code duration}\n     */\n    void update(long duration, TimeUnit unit);\n\n    /**\n     * Adds a recorded duration.\n     *\n     * @param duration the {@link Duration} to add to the timer. Negative or zero value are ignored.\n     */\n    void update(Duration duration);\n\n    /**\n     * Times and records the duration of event.\n     *\n     * @param event a {@link Callable} whose {@link Callable#call()} method implements a process\n     *              whose duration should be timed\n     * @param <T>   the type of the value returned by {@code event}\n     * @return the value returned by {@code event}\n     * @throws Exception if {@code event} throws an {@link Exception}\n     */\n    <T> T time(Callable<T> event) throws Exception;\n\n    /**\n     * Times and records the duration of event. Should not throw exceptions, for that use the\n     * {@link #time(Callable)} method.\n     *\n     * @param event a {@link Supplier} whose {@link Supplier#get()} method implements a process\n     *              whose duration should be timed\n     * @param <T>   the type of the value returned by {@code event}\n     * @return the value returned by {@code event}\n     */\n    <T> T timeSupplier(Supplier<T> event);\n\n    /**\n     * Times and records the duration of event.\n     *\n     * @param event a {@link Runnable} whose {@link Runnable#run()} method implements a process\n     *              whose duration should be timed\n     */\n    void time(Runnable event);\n\n    /**\n     * Returns a new {@link Context}.\n     *\n     * @return a new {@link Context}\n     * @see Context\n     */\n    Timer.Context time();\n\n    /**\n     * Returns the number of events which have been marked.\n     *\n     * @return the number of events which have been marked\n     */\n    long getCount();\n\n    /**\n     * Returns the fifteen-minute moving average rate at which events have\n     * occurred since the meter was created.\n     *\n     * @return the fifteen-minute moving average rate at which events have\n     * occurred since the meter was created\n     */\n    double getFifteenMinuteRate();\n\n    /**\n     * Returns the five-minute moving average rate at which events have\n     * occurred since the meter was created.\n     *\n     * @return the five-minute moving average rate at which events have\n     * occurred since the meter was created\n     */\n    double getFiveMinuteRate();\n\n    /**\n     * Returns the mean rate at which events have occurred since the meter was created.\n     *\n     * @return the mean rate at which events have occurred since the meter was created\n     */\n\n    double getMeanRate();\n\n    /**\n     * Returns the one-minute moving average rate at which events have\n     * occurred since the meter was created.\n     *\n     * @return the one-minute moving average rate at which events have\n     * occurred since the meter was created\n     */\n    double getOneMinuteRate();\n\n    /**\n     * Returns a snapshot of the values.\n     *\n     * @return a snapshot of the values\n     */\n    Snapshot getSnapshot();\n\n    /**\n     * Returns the underlying Timer object or {@code null} if there is none. Here is a Timer\n     * objects: {@code com.codahale.metrics.Timer}\n     *\n     * @return\n     */\n    Object unwrap();\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/metric/name/ConverterType.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.metric.name;\n\npublic enum ConverterType {\n\n    NONE(0),\n    RATE(1),\n    DURATION(2),\n    ;\n\n    private int value;\n\n    public int getValue() {\n        return value;\n    }\n\n    ConverterType(int value) {\n        this.value = value;\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/metric/name/MetricField.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.metric.name;\n\n\n/**\n * MetricField describes a metric value attributes including what's metric name , what's metric type, and what's metrics precision.\n */\npublic enum MetricField {\n\n    MIN_EXECUTION_TIME(\"min\", ConverterType.DURATION, 2),\n    MAX_EXECUTION_TIME(\"max\", ConverterType.DURATION, 2),\n    MEAN_EXECUTION_TIME(\"mean\", ConverterType.DURATION, 2),\n    P25_EXECUTION_TIME(\"p25\", ConverterType.DURATION, 2),\n    P50_EXECUTION_TIME(\"p50\", ConverterType.DURATION, 2),\n    P75_EXECUTION_TIME(\"p75\", ConverterType.DURATION, 2),\n    P95_EXECUTION_TIME(\"p95\", ConverterType.DURATION, 2),\n    P98_EXECUTION_TIME(\"p98\", ConverterType.DURATION, 2),\n    P99_EXECUTION_TIME(\"p99\", ConverterType.DURATION, 2),\n    P999_EXECUTION_TIME(\"p999\", ConverterType.DURATION, 2),\n    STD(\"std\"),\n    EXECUTION_COUNT(\"cnt\"),\n    EXECUTION_ERROR_COUNT(\"errcnt\"),\n    M1_RATE(\"m1\", ConverterType.RATE, 5),\n    M5_RATE(\"m5\", ConverterType.RATE, 5),\n    M15_RATE(\"m15\", ConverterType.RATE, 5),\n    RETRY_M1_RATE(\"retrym1\", ConverterType.RATE, 5),\n    RETRY_M5_RATE(\"retrym5\", ConverterType.RATE, 5),\n    RETRY_M15_RATE(\"retrym15\", ConverterType.RATE, 5),\n    RATELIMITER_M1_RATE(\"rlm1\", ConverterType.RATE, 5),\n    RATELIMITER_M5_RATE(\"rlm5\", ConverterType.RATE, 5),\n    RATELIMITER_M15_RATE(\"rlm15\", ConverterType.RATE, 5),\n    CIRCUITBREAKER_M1_RATE(\"cbm1\", ConverterType.RATE, 5),\n    CIRCUITBREAKER_M5_RATE(\"cbm5\", ConverterType.RATE, 5),\n    CIRCUITBREAKER_M15_RATE(\"cbm15\", ConverterType.RATE, 5),\n    MEAN_RATE(\"mean_rate\", ConverterType.RATE, 5),\n    M1_ERROR_RATE(\"m1err\", ConverterType.RATE, 5),\n    M5_ERROR_RATE(\"m5err\", ConverterType.RATE, 5),\n    M15_ERROR_RATE(\"m15err\", ConverterType.RATE, 5),\n    M1_COUNT(\"m1cnt\", ConverterType.RATE, 0),\n    M5_COUNT(\"m5cnt\", ConverterType.RATE, 0),\n    M15_COUNT(\"m15cnt\", ConverterType.RATE, 0),\n    TIMES_RATE(\"time_rate\", ConverterType.RATE, 5),\n    TOTAL_COLLECTION_TIME(\"total_collection_time\", ConverterType.RATE, 0),\n    TIMES(\"times\", ConverterType.RATE, 0),\n    /* channel is for rabbitmq */\n    CHANNEL_M1_RATE(\"channel_m1_rate\", ConverterType.RATE, 5),\n    CHANNEL_M5_RATE(\"channel_m5_rate\", ConverterType.RATE, 5),\n    CHANNEL_M15_RATE(\"channel_m15_rate\", ConverterType.RATE, 5),\n    QUEUE_M1_RATE(\"queue_m1_rate\", ConverterType.RATE, 5),\n    QUEUE_M5_RATE(\"queue_m5_rate\", ConverterType.RATE, 5),\n    QUEUE_M15_RATE(\"queue_m15_rate\", ConverterType.RATE, 5),\n    QUEUE_M1_ERROR_RATE(\"queue_m1_error_rate\", ConverterType.RATE, 5),\n    QUEUE_M5_ERROR_RATE(\"queue_m5_error_rate\", ConverterType.RATE, 5),\n    QUEUE_M15_ERROR_RATE(\"queue_m15_error_rate\", ConverterType.RATE, 5),\n    /*producer and consumer is for message kafka rabbitmq service*/\n    PRODUCER_M1_RATE(\"prodrm1\", ConverterType.RATE, 5),\n    PRODUCER_M5_RATE(\"prodrm5\", ConverterType.RATE, 5),\n    PRODUCER_M15_RATE(\"prodrm15\", ConverterType.RATE, 5),\n    PRODUCER_M1_ERROR_RATE(\"prodrm1err\", ConverterType.RATE, 5),\n    PRODUCER_M5_ERROR_RATE(\"prodrm5err\", ConverterType.RATE, 5),\n    PRODUCER_M15_ERROR_RATE(\"prodrm15err\", ConverterType.RATE, 5),\n    CONSUMER_M1_RATE(\"consrm1\", ConverterType.RATE, 5),\n    CONSUMER_M5_RATE(\"consrm5\", ConverterType.RATE, 5),\n    CONSUMER_M15_RATE(\"consrm15\", ConverterType.RATE, 5),\n    CONSUMER_M1_ERROR_RATE(\"consrm1err\", ConverterType.RATE, 5),\n    CONSUMER_M5_ERROR_RATE(\"consrm5err\", ConverterType.RATE, 5),\n    CONSUMER_M15_ERROR_RATE(\"consrm15err\", ConverterType.RATE, 5),\n    EXECUTION_PRODUCER_ERROR_COUNT(\"prodrerrcnt\"),\n    EXECUTION_CONSUMER_ERROR_COUNT(\"consrerrcnt\"),\n    EXECUTION_PRODUCER_COUNT(\"prodrcnt\"),\n    EXECUTION_CONSUMER_COUNT(\"consrcnt\"),\n    PRODUCER_MIN_EXECUTION_TIME(\"prodrmin\", ConverterType.DURATION, 2),\n    PRODUCER_MAX_EXECUTION_TIME(\"prodrmax\", ConverterType.DURATION, 2),\n    PRODUCER_MEAN_EXECUTION_TIME(\"prodrmean\", ConverterType.DURATION, 2),\n    PRODUCER_P25_EXECUTION_TIME(\"prodrp25\", ConverterType.DURATION, 2),\n    PRODUCER_P50_EXECUTION_TIME(\"prodrp50\", ConverterType.DURATION, 2),\n    PRODUCER_P75_EXECUTION_TIME(\"prodrp75\", ConverterType.DURATION, 2),\n    PRODUCER_P95_EXECUTION_TIME(\"prodrp95\", ConverterType.DURATION, 2),\n    PRODUCER_P98_EXECUTION_TIME(\"prodrp98\", ConverterType.DURATION, 2),\n    PRODUCER_P99_EXECUTION_TIME(\"prodrp99\", ConverterType.DURATION, 2),\n    PRODUCER_P999_EXECUTION_TIME(\"prodrp999\", ConverterType.DURATION, 2),\n    CONSUMER_MIN_EXECUTION_TIME(\"consrmin\", ConverterType.DURATION, 2),\n    CONSUMER_MAX_EXECUTION_TIME(\"consrmax\", ConverterType.DURATION, 2),\n    CONSUMER_MEAN_EXECUTION_TIME(\"consrmean\", ConverterType.DURATION, 2),\n    CONSUMER_P25_EXECUTION_TIME(\"consrp25\", ConverterType.DURATION, 2),\n    CONSUMER_P50_EXECUTION_TIME(\"consrp50\", ConverterType.DURATION, 2),\n    CONSUMER_P75_EXECUTION_TIME(\"consrp75\", ConverterType.DURATION, 2),\n    CONSUMER_P95_EXECUTION_TIME(\"consrp95\", ConverterType.DURATION, 2),\n    CONSUMER_P98_EXECUTION_TIME(\"consrp98\", ConverterType.DURATION, 2),\n    CONSUMER_P99_EXECUTION_TIME(\"consrp99\", ConverterType.DURATION, 2),\n    CONSUMER_P999_EXECUTION_TIME(\"consrp999\", ConverterType.DURATION, 2),\n    NONE(\"\", ConverterType.RATE, 0);\n\n\n    private final String field;\n    private final ConverterType type;\n    private final int scale;\n\n    MetricField(String field, ConverterType type, int scale) {\n        this.field = field;\n        this.type = type;\n        this.scale = scale;\n    }\n\n    MetricField(String field) {\n        this(field, ConverterType.NONE, 0);\n    }\n\n    public String getField() {\n        return field;\n    }\n\n    public ConverterType getType() {\n        return type;\n    }\n\n    public int getScale() {\n        return scale;\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/metric/name/MetricName.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.metric.name;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n\npublic class MetricName {\n\n    final MetricSubType metricSubType;\n    final String key;\n    final MetricType metricType;\n    final Map<MetricField, MetricValueFetcher> valueFetcher;\n\n    public MetricName(MetricSubType metricSubType, String key, MetricType metricType, Map<MetricField, MetricValueFetcher> valueFetcher) {\n        this.metricSubType = metricSubType;\n        this.key = key;\n        this.metricType = metricType;\n        this.valueFetcher = valueFetcher;\n    }\n\n    public MetricSubType getMetricSubType() {\n        return metricSubType;\n    }\n\n    public String getKey() {\n        return key;\n    }\n\n    public MetricType getMetricType() {\n        return metricType;\n    }\n\n    public Map<MetricField, MetricValueFetcher> getValueFetcher() {\n        return valueFetcher;\n    }\n\n    public static MetricName metricNameFor(String name) {\n        return new MetricName(\n                MetricSubType.valueFor(name.substring(0, 2)),\n                name.substring(3),\n                MetricType.values()[Integer.parseInt(name.substring(2, 3))],\n                new HashMap<>());\n    }\n\n    public String name() {\n        return metricSubType.getCode() + metricType.ordinal() + key;\n    }\n\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/metric/name/MetricSubType.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.metric.name;\n\npublic enum MetricSubType {\n    DEFAULT(\"00\"),\n    ERROR(\"01\"),\n    CHANNEL(\"02\"), //for rabbitmq\n    CONSUMER(\"03\"), //for messaging kafka/rabbitmq consumer\n    PRODUCER(\"04\"), //for messaging kafka/rabbitmq producer\n    CONSUMER_ERROR(\"05\"), //for messaging kafka/rabbitmq consumer error\n    PRODUCER_ERROR(\"06\"), //for messaging kafka/rabbitmq producer error\n    NONE(\"99\");\n\n    private final String code;\n\n    public String getCode() {\n        return code;\n    }\n\n    MetricSubType(String s) {\n        this.code = s;\n    }\n\n    public static MetricSubType valueFor(String code) {\n        MetricSubType[] values = MetricSubType.values();\n        for (MetricSubType value : values) {\n            if (value.code.equals(code)) {\n                return value;\n            }\n        }\n        throw new IllegalArgumentException(\"code \" + code + \" is invalid\");\n    }\n\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/metric/name/MetricType.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.metric.name;\n\nimport com.megaease.easeagent.plugin.api.metric.*;\n\npublic enum MetricType {\n\n    TimerType(Timer.class),\n    HistogramType(Histogram.class),\n    MeterType(Meter.class),\n    CounterType(Counter.class),\n    GaugeType(Gauge.class);\n\n    <T> MetricType(Class<T> clazz) {\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/metric/name/MetricValueFetcher.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.metric.name;\n\nimport com.megaease.easeagent.plugin.api.metric.Counter;\nimport com.megaease.easeagent.plugin.api.metric.Meter;\nimport com.megaease.easeagent.plugin.api.metric.Metric;\nimport com.megaease.easeagent.plugin.api.metric.Snapshot;\n\nimport java.util.function.Function;\n\npublic enum MetricValueFetcher {\n    CountingCount(Counter::getCount, Counter.class),\n    SnapshotMaxValue(Snapshot::getMax, Snapshot.class),\n    SnapshotMeanValue(Snapshot::getMean, Snapshot.class),\n    SnapshotMinValue(Snapshot::getMin, Snapshot.class),\n    Snapshot25Percentile(s -> s.getValue(0.25), Snapshot.class),\n    SnapshotMedianValue(Snapshot::getMedian, Snapshot.class),\n    Snapshot50PercentileValue(Snapshot::getMedian, Snapshot.class),\n    Snapshot75PercentileValue(Snapshot::get75thPercentile, Snapshot.class),\n    Snapshot95PercentileValue(Snapshot::get95thPercentile, Snapshot.class),\n    Snapshot98PercentileValue(Snapshot::get98thPercentile, Snapshot.class),\n    Snapshot99PercentileValue(Snapshot::get99thPercentile, Snapshot.class),\n    Snapshot999PercentileValue(Snapshot::get999thPercentile, Snapshot.class),\n    MeteredM1Rate(Meter::getOneMinuteRate, Meter.class),\n    MeteredM1RateIgnoreZero(Meter::getOneMinuteRate, Meter.class, aDouble -> aDouble),\n    MeteredM5Rate(Meter::getFiveMinuteRate, Meter.class),\n    MeteredM15Rate(Meter::getFifteenMinuteRate, Meter.class),\n    MeteredMeanRate(Meter::getMeanRate, Meter.class),\n    MeteredCount(Meter::getCount, Meter.class);\n\n    public static <T, V> Function<T, V> wrapIgnoreZeroFunc(Function<T, V> origin) {\n        return null;\n    }\n\n    private final Function func;\n    private final Class clazz;\n    private final Function checker;\n\n    <T, V> MetricValueFetcher(Function<T, V> function, Class<T> clazz) {\n        this(function, clazz, v -> v);\n    }\n\n    <T, V> MetricValueFetcher(Function<T, V> function, Class<T> clazz, Function<V, V> checker) {\n        this.func = function;\n        this.clazz = clazz;\n        this.checker = checker;\n    }\n\n    public Function getFunc() {\n        return func;\n    }\n\n    public Class getClazz() {\n        return clazz;\n    }\n\n    public Function getChecker() {\n        return checker;\n    }\n\n    public Object apply(Metric obj) {\n        return checker.apply(func.apply(clazz.cast(obj)));\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/metric/name/NameFactory.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.metric.name;\n\nimport java.util.*;\n\n/**\n * The name and value describing of the Metric.\n */\npublic interface NameFactory {\n\n    /**\n     * Create a DefaultNameFactory Builder\n     */\n    static Builder createBuilder() {\n        return new Builder();\n    }\n\n    /**\n     * Get the Set of MetricType that NameFactory exists.\n     */\n    Set<MetricType> metricTypes();\n\n    /**\n     * Return the MetricName of all meters, they must exist in the MetricSubType of\n     * the meter that exists in the NameFactory\n     *\n     * @param key the key of MetricName\n     */\n    Map<MetricSubType, MetricName> meterNames(String key);\n\n    /**\n     * Return the MetricName of all histograms, they must exist in the MetricSubType of\n     * the histogram that exists in the NameFactory\n     *\n     * @param key the key of MetricName\n     */\n    Map<MetricSubType, MetricName> histogramNames(String key);\n\n    /**\n     * Return the MetricName of all counters, they must exist in the MetricSubType of\n     * the counter that exists in the NameFactory\n     *\n     * @param key the key of MetricName\n     */\n    Map<MetricSubType, MetricName> counterNames(String key);\n\n    /**\n     * Return the MetricName of all timers, they must exist in the MetricSubType of\n     * the timer that exists in the NameFactory\n     *\n     * @param key the key of MetricName\n     */\n    Map<MetricSubType, MetricName> timerNames(String key);\n\n    /**\n     * Return the MetricName of all gauges, they must exist in the MetricSubType of\n     * the gauge that exists in the NameFactory\n     *\n     * @param key the key of MetricName\n     */\n    Map<MetricSubType, MetricName> gaugeNames(String key);\n\n    /**\n     * Return a meter name. the {@code subType} must exist in the meter MetricSubType of that exists in the NameFactory\n     *\n     * @param key     the key for metric\n     * @param subType the metric type\n     * @return a metric name\n     */\n    String meterName(String key, MetricSubType subType);\n\n    /**\n     * Return a meter name. the {@code subType} must exist in the meter MetricSubType of that exists in the NameFactory\n     *\n     * @param key     the key for metric\n     * @param subType the metric type\n     * @return a metric name\n     */\n    String histogramName(String key, MetricSubType subType);\n\n    /**\n     * Return a counter name. the {@code subType} must exist in the counter MetricSubType of that exists in the NameFactory\n     *\n     * @param key     the key for metric\n     * @param subType the metric type\n     * @return a metric name\n     */\n    String counterName(String key, MetricSubType subType);\n\n    /**\n     * Return a timer name. the {@code subType} must exist in the timer MetricSubType of that exists in the NameFactory\n     *\n     * @param key     the key for metric\n     * @param subType the metric type\n     * @return a metric name\n     */\n    String timerName(String key, MetricSubType subType);\n\n    /**\n     * Return a gauge name. the {@code subType} must exist in the gauge MetricSubType of that exists in the NameFactory\n     *\n     * @param key     the key for metric\n     * @param subType the metric type\n     * @return a metric name\n     */\n    String gaugeName(String key, MetricSubType subType);\n\n    class DefaultNameFactory implements NameFactory {\n\n        private final List<Tuple<MetricSubType, Map<MetricField, MetricValueFetcher>>> histogramTypes;\n        private final List<Tuple<MetricSubType, Map<MetricField, MetricValueFetcher>>> counterTypes;\n        private final List<Tuple<MetricSubType, Map<MetricField, MetricValueFetcher>>> timerTypes;\n        private final List<Tuple<MetricSubType, Map<MetricField, MetricValueFetcher>>> gaugeTypes;\n        private final List<Tuple<MetricSubType, Map<MetricField, MetricValueFetcher>>> meterTypes;\n\n\n        private DefaultNameFactory(List<Tuple<MetricSubType, Map<MetricField, MetricValueFetcher>>> meterTypes,\n                                   List<Tuple<MetricSubType, Map<MetricField, MetricValueFetcher>>> histogramTypes,\n                                   List<Tuple<MetricSubType, Map<MetricField, MetricValueFetcher>>> counterTypes,\n                                   List<Tuple<MetricSubType, Map<MetricField, MetricValueFetcher>>> timerTypes,\n                                   List<Tuple<MetricSubType, Map<MetricField, MetricValueFetcher>>> gaugeTypes) {\n            this.meterTypes = meterTypes;\n            this.histogramTypes = histogramTypes;\n            this.counterTypes = counterTypes;\n            this.timerTypes = timerTypes;\n            this.gaugeTypes = gaugeTypes;\n        }\n\n        @Override\n        public Set<MetricType> metricTypes() {\n            Set<MetricType> metricTypes = new HashSet<>();\n            if (!histogramTypes.isEmpty()) {\n                metricTypes.add(MetricType.HistogramType);\n            }\n            if (!counterTypes.isEmpty()) {\n                metricTypes.add(MetricType.CounterType);\n            }\n            if (!timerTypes.isEmpty()) {\n                metricTypes.add(MetricType.TimerType);\n            }\n            if (!gaugeTypes.isEmpty()) {\n                metricTypes.add(MetricType.GaugeType);\n            }\n            if (!meterTypes.isEmpty()) {\n                metricTypes.add(MetricType.MeterType);\n            }\n            return metricTypes;\n        }\n\n        @Override\n        public Map<MetricSubType, MetricName> meterNames(String key) {\n            final Map<MetricSubType, MetricName> results = new HashMap<>();\n            meterTypes.forEach(t -> results.put(t.getX(),\n                new MetricName(t.getX(), key, MetricType.MeterType, t.getY())));\n            return results;\n        }\n\n        @Override\n        public String meterName(String key, MetricSubType subType) {\n            return getName(key, MetricType.MeterType, subType, meterTypes);\n        }\n\n        @Override\n        public Map<MetricSubType, MetricName> histogramNames(String key) {\n            final Map<MetricSubType, MetricName> results = new HashMap<>();\n            histogramTypes.forEach(t -> results.put(t.getX(),\n                new MetricName(t.getX(), key, MetricType.HistogramType, t.getY())));\n            return results;\n        }\n\n        @Override\n        public String histogramName(String key, MetricSubType subType) {\n            return getName(key, MetricType.HistogramType, subType, histogramTypes);\n        }\n\n        @Override\n        public Map<MetricSubType, MetricName> counterNames(String key) {\n            final Map<MetricSubType, MetricName> results = new HashMap<>();\n            counterTypes.forEach(t -> results.put(t.getX(),\n                new MetricName(t.getX(), key, MetricType.CounterType, t.getY())));\n            return results;\n        }\n\n        @Override\n        public String counterName(String key, MetricSubType subType) {\n            return getName(key, MetricType.CounterType, subType, counterTypes);\n        }\n\n\n        @Override\n        public Map<MetricSubType, MetricName> timerNames(String key) {\n            final Map<MetricSubType, MetricName> results = new HashMap<>();\n            timerTypes.forEach(t -> results.put(t.getX(),\n                new MetricName(t.getX(), key, MetricType.TimerType, t.getY())));\n            return results;\n        }\n\n        @Override\n        public String timerName(String key, MetricSubType subType) {\n            return getName(key, MetricType.TimerType, subType, timerTypes);\n        }\n\n        @Override\n        public Map<MetricSubType, MetricName> gaugeNames(String key) {\n            final Map<MetricSubType, MetricName> results = new HashMap<>();\n            gaugeTypes.forEach(t -> results.put(t.getX(),\n                new MetricName(t.getX(), key, MetricType.GaugeType, t.getY())));\n            return results;\n        }\n\n        @Override\n        public String gaugeName(String key, MetricSubType metricSubType) {\n            return getName(key, MetricType.GaugeType, metricSubType, gaugeTypes);\n        }\n\n        private String getName(String key, MetricType metricType, MetricSubType metricSubType, List<Tuple<MetricSubType,\n            Map<MetricField, MetricValueFetcher>>> metricsTypes) {\n            MetricName metricName = null;\n            for (Tuple<MetricSubType, Map<MetricField, MetricValueFetcher>> t : metricsTypes) {\n                if (t.getX().equals(metricSubType)) {\n                    metricName = new MetricName(t.getX(), key, metricType, t.getY());\n                }\n            }\n            if (metricName == null) {\n                throw new IllegalArgumentException(\"Invalid metricSubType [\" + metricSubType.name() + \"] of \" + metricType.name() +\n                    \" not be registered in NameFactory\");\n            }\n            return metricName.name();\n        }\n    }\n\n\n    class Tuple<X, Y> {\n        private X x;\n        private Y y;\n\n        public Tuple(X x, Y y) {\n            this.x = x;\n            this.y = y;\n        }\n\n        public X getX() {\n            return x;\n        }\n\n        public void setX(X x) {\n            this.x = x;\n        }\n\n        public Y getY() {\n            return y;\n        }\n\n        public void setY(Y y) {\n            this.y = y;\n        }\n    }\n\n\n    class Builder {\n        private final List<Tuple<MetricSubType, Map<MetricField, MetricValueFetcher>>> histogramTypes = new ArrayList<>();\n        private final List<Tuple<MetricSubType, Map<MetricField, MetricValueFetcher>>> counterTypes = new ArrayList<>();\n        private final List<Tuple<MetricSubType, Map<MetricField, MetricValueFetcher>>> timerTypes = new ArrayList<>();\n        private final List<Tuple<MetricSubType, Map<MetricField, MetricValueFetcher>>> gaugeTypes = new ArrayList<>();\n        private final List<Tuple<MetricSubType, Map<MetricField, MetricValueFetcher>>> meterTypes = new ArrayList<>();\n\n        Builder() {\n        }\n\n        public NameFactory build() {\n            return new DefaultNameFactory(meterTypes, histogramTypes, counterTypes, timerTypes, gaugeTypes);\n        }\n\n        public Builder meterType(MetricSubType metricSubType, Map<MetricField, MetricValueFetcher> valueFetchers) {\n            meterTypes.add(new Tuple<>(metricSubType, valueFetchers));\n            return this;\n        }\n\n        public Builder histogramType(MetricSubType metricSubType, Map<MetricField, MetricValueFetcher> valueFetchers) {\n            histogramTypes.add(new Tuple<>(metricSubType, valueFetchers));\n            return this;\n        }\n\n        public Builder counterType(MetricSubType metricSubType, Map<MetricField, MetricValueFetcher> valueFetchers) {\n            counterTypes.add(new Tuple<>(metricSubType, valueFetchers));\n            return this;\n        }\n\n        public Builder timerType(MetricSubType metricSubType, Map<MetricField, MetricValueFetcher> valueFetchers) {\n            this.timerTypes.add(new Tuple<>(metricSubType, valueFetchers));\n            return this;\n        }\n\n        public Builder gaugeType(MetricSubType metricSubType, Map<MetricField, MetricValueFetcher> valueFetchers) {\n            this.gaugeTypes.add(new Tuple<>(metricSubType, valueFetchers));\n            return this;\n        }\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/metric/name/Tags.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.metric.name;\n\nimport com.megaease.easeagent.plugin.api.ProgressFields;\n\nimport javax.annotation.Nonnull;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Objects;\n\n/**\n * A tags describing the metric.\n * It has three predefined tags: category, type, {@code keyFieldName}\n * Its tag is copied as follows:\n * <pre>{@code\n *  output.put(\"category\", tags.category)\n *  output.put(\"type\", tags.type)\n *  output.put(tags.keyFieldName, {@link NameFactory}.key[?])\n *  tags.tags.forEach((k,v)->{\n *      output.put(k,v)\n *  })\n * }</pre>\n */\npublic class Tags {\n    public static final String CATEGORY = \"category\";\n    public static final String TYPE = \"type\";\n    private final String category;\n    private final String type;\n    private final String keyFieldName;\n    private final Map<String, String> tags;\n\n    /**\n     * @param category     {@link #getCategory()}\n     * @param type         {@link #getType()}\n     * @param keyFieldName {@link #getKeyFieldName()}\n     */\n    public Tags(@Nonnull String category, @Nonnull String type, @Nonnull String keyFieldName) {\n        this.category = category;\n        this.type = type;\n        this.keyFieldName = keyFieldName;\n        this.tags = new HashMap<>(ProgressFields.getServiceTags());\n    }\n\n    /**\n     * put tag for.\n     *\n     * @param key   tag key\n     * @param value tag value\n     * @return this methods return {@linkplain Tags} for chaining, but the instance is always the same.\n     */\n    public Tags put(String key, String value) {\n        this.tags.put(key, value);\n        return this;\n    }\n\n    /**\n     * tag category describing of the metrics.\n     * for example: \"application\"\n     *\n     * @return\n     */\n    public String getCategory() {\n        return category;\n    }\n\n    /**\n     * tag type describing of the metrics.\n     * for example: \"http-request\"\n     *\n     * @return\n     */\n    public String getType() {\n        return type;\n    }\n\n    /**\n     * tag {@link NameFactory} keys describing of the metrics.\n     * for example: \"url\"\n     *\n     * <pre>{@code\n     *  keyFieldName=\"url\"\n     *  nameFactory.timerName(\"http://127.0.0.1:8080/\", ...);\n     *  // it will be tag.put(\"url\", \"http://127.0.0.1:8080/\")\n     * }</pre>\n     *\n     * @return\n     * @see NameFactory\n     */\n    public String getKeyFieldName() {\n        return keyFieldName;\n    }\n\n    /**\n     * Custom tags describing of the metrics.\n     *\n     * @return custom tags\n     */\n    public Map<String, String> getTags() {\n        return tags;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) return true;\n        if (o == null || getClass() != o.getClass()) return false;\n        Tags tags1 = (Tags) o;\n        return Objects.equals(category, tags1.category) &&\n            Objects.equals(type, tags1.type) &&\n            Objects.equals(keyFieldName, tags1.keyFieldName) &&\n            Objects.equals(tags, tags1.tags);\n    }\n\n    @Override\n    public int hashCode() {\n\n        return Objects.hash(category, type, keyFieldName, tags);\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/middleware/MiddlewareConstants.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.middleware;\n\npublic final class MiddlewareConstants {\n    public static final String ENV_REDIS = \"EASE_RESOURCE_REDIS\";\n    public static final String ENV_ES = \"EASE_RESOURCE_ELASTICSEARCH\";\n    public static final String ENV_MONGODB = \"EASE_RESOURCE_MONGODB\";\n    public static final String ENV_KAFKA = \"EASE_RESOURCE_KAFKA\";\n    public static final String ENV_RABBITMQ = \"EASE_RESOURCE_RABBITMQ\";\n    public static final String ENV_DATABASE = \"EASE_RESOURCE_DATABASE\";\n\n\n    public static final String TYPE_TAG_NAME = \"component.type\";\n    public static final String TYPE_REDIS = \"redis\";\n    public static final String TYPE_ES = \"elasticsearch\";\n    public static final String TYPE_KAFKA = \"kafka\";\n    public static final String TYPE_RABBITMQ = \"rabbitmq\";\n    public static final String TYPE_DATABASE = \"database\";\n    public static final String TYPE_MONGODB = \"mongodb\";\n\n    public static final String TYPE_MOTAN = \"motan\";\n\n    public static final String REDIRECTED_LABEL_REMOTE_TAG_NAME = \"label.remote\";\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/middleware/Redirect.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.middleware;\n\npublic enum Redirect {\n    REDIS(MiddlewareConstants.ENV_REDIS, true),\n    ELASTICSEARCH(MiddlewareConstants.ENV_ES, true),\n    KAFKA(MiddlewareConstants.ENV_KAFKA, true),\n    RABBITMQ(MiddlewareConstants.ENV_RABBITMQ, true),\n    DATABASE(MiddlewareConstants.ENV_DATABASE, false),\n    MONGODB(MiddlewareConstants.ENV_MONGODB, false);\n\n    private final String env;\n    private final boolean needParse;\n    private final ResourceConfig config;\n\n    Redirect(String env, boolean needParse) {\n        this.env = env;\n        this.needParse = needParse;\n        this.config = ResourceConfig.getResourceConfig(env, needParse);\n    }\n\n    public boolean isNeedParse() {\n        return needParse;\n    }\n\n    public String getEnv() {\n        return env;\n    }\n\n    public boolean hasConfig() {\n        return config != null;\n    }\n\n    public ResourceConfig getConfig() {\n        return config;\n    }\n\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/middleware/RedirectProcessor.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.middleware;\n\nimport com.fasterxml.jackson.core.type.TypeReference;\nimport com.megaease.easeagent.plugin.api.logging.Logger;\nimport com.megaease.easeagent.plugin.api.metric.name.Tags;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.utils.SystemEnv;\nimport com.megaease.easeagent.plugin.utils.common.JsonUtil;\nimport com.megaease.easeagent.plugin.utils.common.StringUtils;\n\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\n\npublic class RedirectProcessor {\n    private static final Logger LOGGER = EaseAgent.getLogger(RedirectProcessor.class);\n    protected static final String ENV_EASEMESH_TAGS = \"EASEMESH_TAGS\";\n\n    public static final RedirectProcessor INSTANCE = new RedirectProcessor();\n\n    private volatile Map<Redirect, String> redirectedUris = new HashMap<>();\n    private final Map<String, String> tags = getServiceTagsFromEnv();\n\n    public static void redirected(Redirect key, String uris) {\n        INSTANCE.setRedirected(key, uris);\n    }\n\n    public static void setTagsIfRedirected(Redirect key, Span span) {\n        setTagsIfRedirected(key, span, null);\n    }\n\n    public static void setTagsIfRedirected(Redirect key, Span span, String uris) {\n        String remote = getRemote(key, uris);\n        if (!StringUtils.isEmpty(remote)) {\n            span.tag(MiddlewareConstants.REDIRECTED_LABEL_REMOTE_TAG_NAME, remote);\n        }\n    }\n\n    public static void setTagsIfRedirected(Redirect key, Tags tags) {\n        String remote = INSTANCE.getRedirected(key);\n        if (remote == null) {\n            return;\n        }\n        Map<String, String> serviceTags = INSTANCE.getTags();\n        if (serviceTags != null && !serviceTags.isEmpty()) {\n            for (Map.Entry<String, String> entry : serviceTags.entrySet()) {\n                tags.put(entry.getKey(), entry.getValue());\n            }\n        }\n    }\n\n    public static Map<String, String> tags() {\n        return INSTANCE.getTags();\n    }\n\n    public Map<String, String> getTags() {\n        return tags;\n    }\n\n    private static String getRemote(Redirect key, String uris) {\n        if (!key.hasConfig()) {\n            return null;\n        }\n        String remote = INSTANCE.getRedirected(key);\n        if (remote == null) {\n            return null;\n        }\n        if (uris != null) {\n            return uris;\n        } else {\n            return remote;\n        }\n    }\n\n\n    @SuppressWarnings(\"all\")\n    public void init() {\n        for (Redirect redirect : Redirect.values()) {//init\n            //ignore\n        }\n    }\n\n    private synchronized void setRedirected(Redirect key, String uris) {\n        Map<Redirect, String> uriMap = new HashMap<>(this.redirectedUris);\n        uriMap.put(key, uris);\n        this.redirectedUris = uriMap;\n    }\n\n    private synchronized String getRedirected(Redirect key) {\n        return this.redirectedUris.get(key);\n    }\n\n\n    protected static Map<String, String> getServiceTagsFromEnv() {\n        return getServiceTags(ENV_EASEMESH_TAGS);\n    }\n\n    protected static Map<String, String> getServiceTags(String env) {\n        String str = SystemEnv.get(env);\n        if (StringUtils.isEmpty(env)) {\n            return Collections.emptyMap();\n        }\n        try {\n            Map<String, String> map = JsonUtil.toObject(str, new TypeReference<Map<String, String>>() {\n            });\n            Map<String, String> result = new HashMap<>();\n            for (Map.Entry<String, String> entry : map.entrySet()) {\n                if (StringUtils.isEmpty(entry.getKey()) || StringUtils.isEmpty(entry.getValue())) {\n                    continue;\n                }\n                result.put(entry.getKey(), entry.getValue());\n            }\n            return result;\n        } catch (Exception e) {\n            LOGGER.warn(\"get env {} result: `{}` to map fail. {}\", env, str, e.getMessage());\n        }\n        return Collections.emptyMap();\n    }\n\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/middleware/ResourceConfig.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.middleware;\n\nimport com.fasterxml.jackson.core.type.TypeReference;\nimport com.megaease.easeagent.plugin.utils.SystemEnv;\nimport com.megaease.easeagent.plugin.utils.common.JsonUtil;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Objects;\n\npublic class ResourceConfig {\n\n    private String userName;\n    private String password;\n    private String uris;\n    private final List<String> uriList = new ArrayList<>();\n    private final List<HostAndPort> hostAndPorts = new ArrayList<>();\n\n    public static ResourceConfig getResourceConfig(String env, boolean needParse) {\n        String str = SystemEnv.get(env);\n        if (str == null) {\n            return null;\n        }\n        ResourceConfig resourceConfig = JsonUtil.toObject(str, new TypeReference<ResourceConfig>() {\n        });\n        resourceConfig.parseHostAndPorts(needParse);\n        if (resourceConfig.hasUrl()) {\n            return resourceConfig;\n        }\n        return null;\n    }\n\n    private void parseHostAndPorts(boolean needParse) {\n        if (!needParse) {\n            uriList.add(this.uris);\n            return;\n        }\n        if (uris == null || uris.isEmpty()) {\n            return;\n        }\n        String[] list = uris.split(\",\");\n        for (String uri : list) {\n            uriList.add(uri);\n            int begin = uri.indexOf(\":\");\n            int end = uri.lastIndexOf(\":\");\n            if (begin == end) {\n                String[] arr = uri.split(\":\");\n                HostAndPort obj = new HostAndPort();\n                obj.setHost(arr[0]);\n                obj.setPort(Integer.parseInt(arr[1]));\n                this.hostAndPorts.add(obj);\n            }\n        }\n    }\n\n    public HostAndPort getFirstHostAndPort() {\n        if (this.hostAndPorts.isEmpty()) {\n            return null;\n        }\n        return this.hostAndPorts.get(0);\n    }\n\n    public String getFirstUri() {\n        if (uriList == null || uriList.isEmpty()) {\n            return null;\n        }\n        return this.uriList.get(0);\n    }\n\n    public String getUserName() {\n        return userName;\n    }\n\n    public void setUserName(String userName) {\n        this.userName = userName;\n    }\n\n    public String getPassword() {\n        return password;\n    }\n\n    public void setPassword(String password) {\n        this.password = password;\n    }\n\n    public boolean hasUrl() {\n        return this.uris != null && !this.uris.isEmpty();\n    }\n\n    public String getUris() {\n        return uris;\n    }\n\n    public void setUris(String uris) {\n        this.uris = uris;\n    }\n\n    public List<String> getUriList() {\n        return uriList;\n    }\n\n    public List<HostAndPort> getHostAndPorts() {\n        return hostAndPorts;\n    }\n\n    public static class HostAndPort {\n        private String host;\n        private Integer port;\n\n        public String getHost() {\n            return host;\n        }\n\n        public void setHost(String host) {\n            this.host = host;\n        }\n\n        public Integer getPort() {\n            return port;\n        }\n\n        public void setPort(Integer port) {\n            this.port = port;\n        }\n\n        @Override\n        public boolean equals(Object o) {\n            if (this == o) return true;\n            if (o == null || getClass() != o.getClass()) return false;\n            HostAndPort that = (HostAndPort) o;\n            return Objects.equals(host, that.host) &&\n                Objects.equals(port, that.port);\n        }\n\n        @Override\n        public int hashCode() {\n\n            return Objects.hash(host, port);\n        }\n\n        public String uri() {\n            return host + \":\" + port;\n        }\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/middleware/Type.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.middleware;\n\npublic enum Type {\n    REDIS(MiddlewareConstants.TYPE_REDIS),\n    DATABASE(MiddlewareConstants.TYPE_DATABASE),\n    KAFKA(MiddlewareConstants.TYPE_KAFKA),\n    RABBITMQ(MiddlewareConstants.TYPE_RABBITMQ),\n    ELASTICSEARCH(MiddlewareConstants.TYPE_ES),\n    MONGODB(MiddlewareConstants.TYPE_MONGODB),\n    MOTAN(MiddlewareConstants.TYPE_MOTAN)\n    ;\n\n    final String remoteType;\n\n    Type(String remoteType) {\n        this.remoteType = remoteType;\n    }\n\n    public String getRemoteType() {\n        return remoteType;\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/otlp/common/AgentAttributes.java",
    "content": "/*\n * Copyright (c) 2022, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.plugin.api.otlp.common;\n\nimport io.opentelemetry.api.common.AttributeKey;\nimport io.opentelemetry.api.common.Attributes;\nimport io.opentelemetry.api.common.AttributesBuilder;\n\nimport javax.annotation.Nullable;\nimport javax.annotation.ParametersAreNonnullByDefault;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.function.BiConsumer;\n\n@ParametersAreNonnullByDefault\n@SuppressWarnings(\"unchecked, rawtype, unused\")\npublic final class AgentAttributes extends HashMap<AttributeKey<?>, Object> implements Attributes {\n    @Nullable\n    @Override\n    public <T> T get(AttributeKey<T> key) {\n        Object v = super.get(key);\n        if (v == null) {\n            return null;\n        }\n        return (T)v;\n    }\n\n    @Override\n    public void forEach(BiConsumer<? super AttributeKey<?>, ? super Object> consumer) {\n        super.forEach(consumer);\n    }\n\n    @Override\n    public Map<AttributeKey<?>, Object> asMap() {\n        return this;\n    }\n\n    @Override\n    public AttributesBuilder toBuilder() {\n        return new Builder(this);\n    }\n\n    public static AttributesBuilder builder() {\n        return new Builder();\n    }\n\n    static class Builder implements AttributesBuilder {\n        AgentAttributes attrs;\n        private final long capacity;\n        private final int lengthLimit;\n\n        public Builder(AgentAttributes from) {\n            this.attrs = from;\n            this.capacity = Integer.MAX_VALUE;\n            this.lengthLimit = Integer.MAX_VALUE;\n        }\n\n        public Builder() {\n            this(Integer.MAX_VALUE, Integer.MAX_VALUE);\n        }\n\n        public Builder(int capacity, int limit) {\n            this.capacity = capacity;\n            this.lengthLimit = limit;\n            this.attrs = new AgentAttributes();\n        }\n\n        @Override\n        public Attributes build() {\n            return this.attrs;\n        }\n\n        @Override\n        public <T> AttributesBuilder put(AttributeKey<Long> key, int value) {\n            if (this.attrs.size() > this.capacity) {\n                return this;\n            }\n            this.attrs.put(key, value);\n            return this;\n        }\n\n        @Override\n        public <T> AttributesBuilder put(AttributeKey<T> key, T value) {\n            if (this.attrs.size() > this.capacity) {\n                return this;\n            }\n            this.attrs.put(key, value);\n            return this;\n        }\n\n        @Override\n        public AttributesBuilder putAll(Attributes attributes) {\n            if (attributes.size() + this.attrs.size() > this.capacity)  {\n                return this;\n            }\n            this.attrs.putAll(attributes.asMap());\n            return this;\n        }\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/otlp/common/AgentInstrumentLibInfo.java",
    "content": "/*\n * Copyright (c) 2022, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.plugin.api.otlp.common;\n\nimport io.opentelemetry.sdk.common.InstrumentationLibraryInfo;\n\nimport java.util.concurrent.ConcurrentHashMap;\n\npublic class AgentInstrumentLibInfo {\n    static ConcurrentHashMap<String, InstrumentationLibraryInfo> infoMap = new ConcurrentHashMap<>();\n\n    public static InstrumentationLibraryInfo getInfo(String loggerName) {\n        InstrumentationLibraryInfo info = infoMap.get(loggerName);\n        if (info != null) {\n            return info;\n        }\n        info = InstrumentationLibraryInfo.create(loggerName, null);\n        infoMap.putIfAbsent(loggerName, info);\n        return info;\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/otlp/common/AgentLogData.java",
    "content": "/*\n * Copyright (c) 2022, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.plugin.api.otlp.common;\n\nimport com.megaease.easeagent.plugin.report.EncodedData;\nimport io.opentelemetry.sdk.logs.data.LogData;\nimport io.opentelemetry.sdk.resources.EaseAgentResource;\n\nimport java.util.Map;\n\npublic interface AgentLogData extends LogData {\n    /**\n     * get logger thread name\n     * @return thread name\n     */\n    String getThreadName();\n\n    /**\n     * get logger name\n     * @return logger name\n     */\n    String getLocation();\n\n    /**\n     * get unix timestamp in milliseconds\n     * @return timestamp\n     */\n    long getEpochMillis();\n\n    /**\n     * get agent resource - system/service\n     * @return agent resource\n     */\n    EaseAgentResource getAgentResource();\n\n    /**\n     * complete attributes\n     */\n    void completeAttributes();\n\n    /**\n     * return pattern map\n     * @return pattern map\n     */\n    Map<String, String> getPatternMap();\n\n    /**\n     * return throwable/Exception\n     * @return throwbale\n     */\n    Throwable getThrowable();\n\n    /**\n     * return encoded data\n     * @return encoded data\n     */\n    EncodedData getEncodedData();\n\n    void setEncodedData(EncodedData data);\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/otlp/common/AgentLogDataImpl.java",
    "content": "/*\n * Copyright (c) 2022, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.plugin.api.otlp.common;\n\nimport com.megaease.easeagent.plugin.report.EncodedData;\nimport io.opentelemetry.api.common.Attributes;\nimport io.opentelemetry.api.common.AttributesBuilder;\nimport io.opentelemetry.api.trace.SpanContext;\nimport io.opentelemetry.sdk.common.InstrumentationLibraryInfo;\nimport io.opentelemetry.sdk.logs.data.Body;\nimport io.opentelemetry.sdk.logs.data.Severity;\nimport io.opentelemetry.sdk.resources.EaseAgentResource;\nimport lombok.Data;\n\nimport java.io.PrintWriter;\nimport java.io.StringWriter;\nimport java.util.Collection;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.concurrent.TimeUnit;\n\n@Data\n@SuppressWarnings(\"unused\")\npublic class AgentLogDataImpl implements AgentLogData {\n    private EaseAgentResource resource = EaseAgentResource.getResource();\n    private InstrumentationLibraryInfo instrumentationLibraryInfo;\n    private long epochMillis;\n    private SpanContext spanContext;\n    private Severity severity;\n    private String severityText;\n    private String name = null;\n\n    private Body body;\n    private Attributes attributes;\n\n    private String threadName;\n    private long threadId;\n    private Throwable throwable;\n\n    private Map<String, String> patternMap = null;\n    private EncodedData encodedData;\n\n    public AgentLogDataImpl(Builder builder) {\n        this.epochMillis = builder.epochMills;\n        this.spanContext = builder.spanContext == null ? SpanContext.getInvalid() : builder.spanContext;\n        this.severity = builder.severity;\n        this.severityText = builder.severityText != null ? builder.severityText : this.severity.name();\n        this.body = builder.body;\n\n        this.attributes = builder.attributesBuilder != null\n            ? builder.attributesBuilder.build()\n            : AgentAttributes.builder().build();\n\n        this.instrumentationLibraryInfo = AgentInstrumentLibInfo.getInfo(builder.logger);\n        this.threadName = builder.threadName;\n        this.threadId = builder.threadId;\n        this.throwable = builder.throwable;\n    }\n\n    @Override\n    public String getThreadName() {\n        return this.threadName;\n    }\n\n    @Override\n    public String getLocation() {\n        return this.instrumentationLibraryInfo.getName();\n    }\n\n    @Override\n    public EaseAgentResource getAgentResource() {\n        return this.resource;\n    }\n\n    @Override\n    public void completeAttributes() {\n        AttributesBuilder attrsBuilder = this.attributes.toBuilder();\n        if (this.throwable != null) {\n            attrsBuilder.put(SemanticKey.EXCEPTION_TYPE, throwable.getClass().getName());\n            attrsBuilder.put(SemanticKey.EXCEPTION_MESSAGE, throwable.getMessage());\n\n            StringWriter writer = new StringWriter();\n            throwable.printStackTrace(new PrintWriter(writer));\n            attrsBuilder.put(SemanticKey.EXCEPTION_STACKTRACE, writer.toString());\n        }\n\n        attrsBuilder.put(SemanticKey.THREAD_NAME, threadName);\n        attrsBuilder.put(SemanticKey.THREAD_ID, threadId);\n    }\n\n    @Override\n    public Map<String, String> getPatternMap() {\n        if (this.patternMap == null) {\n            this.patternMap = new HashMap<>();\n        }\n        return this.patternMap;\n    }\n\n    public static Builder builder() {\n        return new Builder();\n    }\n\n    @Override\n    public long getEpochNanos() {\n        return TimeUnit.MILLISECONDS.toNanos(this.epochMillis);\n    }\n\n    @SuppressWarnings(\"UnusedReturnValue\")\n    public static class Builder {\n        private String logger;\n        private long epochMills;\n        private SpanContext spanContext;\n        private Severity severity;\n        private String severityText;\n        private Body body;\n        private Throwable throwable;\n\n        private AttributesBuilder attributesBuilder = null;\n\n        private String threadName;\n        private long threadId;\n\n        public Builder logger(String logger) {\n            this.logger = logger;\n            return this;\n        }\n\n        public Builder epochMills(long timestamp) {\n            this.epochMills = timestamp;\n            return this;\n        }\n\n        public Builder spanContext() {\n            this.spanContext = OtlpSpanContext.getLogSpanContext();\n            return this;\n        }\n\n        public Builder severity(Severity level) {\n            this.severity = level;\n            return this;\n        }\n\n        public Builder severityText(String level) {\n            this.severityText = level;\n            return this;\n        }\n\n        public Builder body(String msg) {\n            this.body = Body.string(msg);\n            return this;\n        }\n\n        public Builder thread(Thread thread) {\n            this.threadName = thread.getName();\n            this.threadId = thread.getId();\n            return this;\n        }\n\n        public Builder throwable(Throwable throwable) {\n            this.throwable = throwable;\n            return this;\n        }\n\n        public Builder contextData(Collection<String> keys, Map<String, String> data) {\n            if (keys == null || keys.isEmpty()) {\n                if (data.isEmpty()) {\n                    return this;\n                }\n                keys = data.keySet();\n            }\n\n            AttributesBuilder ab = getAttributesBuilder();\n            for (String key : keys) {\n                ab.put(SemanticKey.stringKey(key), data.get(key));\n            }\n            return this;\n        }\n\n        public AttributesBuilder getAttributesBuilder() {\n            if (attributesBuilder == null) {\n                attributesBuilder = AgentAttributes.builder();\n            }\n            return attributesBuilder;\n        }\n\n        public AgentLogData build() {\n            return new AgentLogDataImpl(this);\n        }\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/otlp/common/LogMapper.java",
    "content": "/*\n * Copyright (c) 2022, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.otlp.common;\n\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\n\npublic interface LogMapper {\n    public String MDC_KEYS = \"encoder.collectMDCKeys\";\n\n    AgentLogData mapLoggingEvent(MethodInfo methodInfo, int levelInt, IPluginConfig config);\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/otlp/common/OtlpSpanContext.java",
    "content": "/*\n * Copyright (c) 2022, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.plugin.api.otlp.common;\n\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport io.opentelemetry.api.trace.SpanContext;\nimport io.opentelemetry.api.trace.TraceFlags;\nimport io.opentelemetry.api.trace.TraceState;\n\npublic class OtlpSpanContext {\n    OtlpSpanContext() {}\n\n    public static SpanContext getLogSpanContext() {\n        Context context = EaseAgent.getContext();\n        SpanContext spanContext;\n\n        if (!context.currentTracing().hasCurrentSpan()) {\n            spanContext = SpanContext.getInvalid();\n        } else {\n            Span span = context.currentTracing().currentSpan();\n            spanContext = new AgentSpanContext(span, TraceFlags.getSampled(), TraceState.getDefault());\n        }\n        return spanContext;\n    }\n\n    static class AgentSpanContext implements SpanContext {\n        String traceId;\n        String spanId;\n        TraceFlags flags;\n        TraceState state;\n        boolean remote;\n\n        public AgentSpanContext(Span span, TraceFlags flags, TraceState state) {\n            this(span, flags, state, false);\n        }\n\n        public AgentSpanContext(Span span, TraceFlags flags, TraceState state, boolean isRemote) {\n            this.traceId = Long.toHexString(span.traceId());\n            this.spanId = Long.toHexString(span.spanId());\n            this.flags = flags;\n            this.state = state;\n            this.remote = isRemote;\n        }\n\n        @Override\n        public String getTraceId() {\n            return this.traceId;\n        }\n\n        @Override\n        public String getSpanId() {\n            return this.spanId;\n        }\n\n        @Override\n        public TraceFlags getTraceFlags() {\n            return this.flags;\n        }\n\n        @Override\n        public TraceState getTraceState() {\n            return this.state;\n        }\n\n        @Override\n        public boolean isRemote() {\n            return this.remote;\n        }\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/otlp/common/SemanticKey.java",
    "content": "/*\n * Copyright (c) 2022, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.plugin.api.otlp.common;\n\nimport io.opentelemetry.api.common.AttributeKey;\nimport io.opentelemetry.semconv.trace.attributes.SemanticAttributes;\nimport org.checkerframework.checker.units.qual.C;\n\nimport java.util.concurrent.ConcurrentHashMap;\n\npublic class SemanticKey {\n    public static final String SCHEMA_URL = SemanticAttributes.SCHEMA_URL;\n    public static final AttributeKey<String> THREAD_NAME = SemanticAttributes.THREAD_NAME;\n    public static final AttributeKey<Long> THREAD_ID = SemanticAttributes.THREAD_ID;\n\n    public static final AttributeKey<String> EXCEPTION_TYPE = SemanticAttributes.EXCEPTION_TYPE;\n    public static final AttributeKey<String> EXCEPTION_MESSAGE = SemanticAttributes.EXCEPTION_MESSAGE;\n    public static final AttributeKey<String> EXCEPTION_STACKTRACE = SemanticAttributes.EXCEPTION_STACKTRACE;\n\n    private static final ConcurrentHashMap<String, AttributeKey<String>> keysMap = new ConcurrentHashMap<>();\n\n    public static AttributeKey<String> stringKey(String key) {\n        AttributeKey<String> vk = keysMap.get(key);\n        return vk != null ? vk : keysMap.computeIfAbsent(key, AttributeKey::stringKey);\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/trace/Extractor.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.trace;\n\n/**\n * Used to continue an incoming trace. For example, by reading http headers.\n *\n * <p><em>Note</em>: This type is safe to implement as a lambda, or use as a method reference as\n * it is effectively a {@code FunctionalInterface}. It isn't annotated as such because the project\n * has a minimum Java language level 6.\n *\n * @see Tracing#nextSpan(Message)\n */\npublic interface Extractor<R extends MessagingRequest> {\n    /**\n     * Returns either a trace context or sampling flags parsed from the request. If nothing was\n     * parsable, sampling flags will be set to {@link com.megaease.easeagent.plugin.bridge.NoOpTracer.EmptyExtractor#INSTANCE}.\n     *\n     * @param request holds propagation fields. For example, an incoming message or http request.\n     */\n\n    Message extract(R request);\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/trace/Getter.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.trace;\n\npublic interface Getter {\n    /**\n     * @param name\n     * @return\n     * @see Request#header(String)\n     */\n    String header(String name);\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/trace/ITracing.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.trace;\n\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.AsyncContext;\nimport com.megaease.easeagent.plugin.api.context.RequestContext;\n\nimport java.util.List;\n\n/**\n * Subtype of {@link Tracing} which can exportAsync,importAsync,clientRequest and serverReceive.\n *\n * <p>This type can be extended so that the object graph can be built differently or overridden,\n * for example via zipkin or when mocking.\n */\npublic interface ITracing extends Tracing {\n    /**\n     * Export a {@link SpanContext} for async\n     * It will only export the information about the current Span.\n     * If you need SpanContext, generate result use {@link Context#exportAsync()}.\n     *\n     * @return {@link SpanContext}\n     * @see Context#exportAsync()\n     */\n    SpanContext exportAsync();\n\n    /**\n     * Import a {@link SpanContext} for async\n     * It will only import the information about the async TraceContext.\n     * If you need import SpanContext and get Scope, generate result use {@link Context#importAsync(AsyncContext)}.\n     *\n     * @param snapshot {@link SpanContext}\n     * @return {@link Scope}\n     * @see Context#importAsync(AsyncContext)\n     */\n    Scope importAsync(SpanContext snapshot);\n\n\n    /**\n     * Create a RequestContext for Cross-server Trace link\n     * <p>\n     * It just only pass multiple key:value values required by Trace through\n     * {@link Request#setHeader(String, String)}, And set the Span's kind, name and\n     * cached scope through {@link Request#kind()}, {@link Request#name()} and {@link Request#cacheScope()}.\n     * If you need Cross-process and get RequestContext, generate result use {@link Context#clientRequest(Request)}.\n     *\n     * @param request {@link Request}\n     * @return {@link RequestContext}\n     * @see Context#clientRequest(Request)\n     */\n    RequestContext clientRequest(Request request);\n\n    /**\n     * Obtain key:value from the request passed by a parent server and create a RequestContext\n     * <p>\n     * It will set the Span's kind, name and cached scope through {@link Request#kind()}, {@link Request#name()}\n     * and {@link Request#cacheScope()}.\n     * <p>\n     * It just only obtain the key:value required by Trace from the {@link Request#header(String)},\n     * If you need and get RequestContext, generate result use {@link Context#serverReceive(Request)} }.\n     * <p>\n     *\n     * @param request {@link Request}\n     * @return {@link RequestContext}\n     * @see Context#serverReceive(Request)\n     */\n    RequestContext serverReceive(Request request);\n\n    /**\n     * @return the keys necessary for Span\n     */\n    List<String> propagationKeys();\n\n    /**\n     * Obtain key:value from the message request and create a Span, Examples: kafka consumer, rabbitmq consumer\n     * <p>\n     * It will set the Span's kind, name and cached scope through {@link Request#kind()}, {@link Request#name()}\n     * and {@link Request#cacheScope()}.\n     *\n     * <p>\n     * It will set the Span's tags \"messaging.operation\", \"messaging.channel_kind\", \"messaging.channel_name\" from request\n     * {@link MessagingRequest#operation()} {@link MessagingRequest#channelKind()} {@link MessagingRequest#channelName()}\n     *\n     * <p>\n     * It just only obtain the key:value required by Trace from the {@link Request#header(String)},\n     * If you need and get Span, generate result use {@link Context#consumerSpan(MessagingRequest)}.\n     *\n     * @param request {@link MessagingRequest}\n     * @return {@link Span}\n     * @see Context#consumerSpan(MessagingRequest)\n     */\n    Span consumerSpan(MessagingRequest request);\n\n\n    /**\n     * Create a Span for message producer. Examples: kafka producer, rabbitmq producer\n     * <p>\n     * It will set the Span's tags \"messaging.operation\", \"messaging.channel_kind\", \"messaging.channel_name\" from request\n     * {@link MessagingRequest#operation()} {@link MessagingRequest#channelKind()} {@link MessagingRequest#channelName()}\n     *\n     * <p>\n     * It just only pass multiple key:value values required by Trace through\n     * {@link Request#setHeader(String, String)}, And set the Span's kind, name and\n     * cached scope through {@link Request#kind()}, {@link Request#name()} and {@link Request#cacheScope()}.\n     * If you need and get Span, generate result use {@link Context#producerSpan(MessagingRequest)}.\n     *\n     * @param request {@link MessagingRequest}\n     * @return {@link Span}\n     * @see Context#producerSpan(MessagingRequest)\n     */\n    Span producerSpan(MessagingRequest request);\n\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/trace/Injector.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.trace;\n\n/**\n * Used to send the trace context downstream. For example, as http headers.\n *\n * <p>For example, to put the context on an {@link java.net.HttpURLConnection}, you can do this:\n * <pre>{@code\n * // in your constructor\n * producerInjector = tracing.messagingTracing().producerInjector();\n *\n * // later in your code, reuse the function you created above to add trace headers\n * HttpURLConnection connection = (HttpURLConnection) new URL(\"http://myserver\").openConnection();\n * producerInjector.consumerInject(span, new MessagingRequest(){ public void setHeader(k,v){connection.setRequestProperty(k,v);}} );\n * }</pre>\n */\npublic interface Injector<R extends MessagingRequest> {\n    /**\n     * Usually calls a {@link Request#setHeader(String, String)} for each propagation field to send downstream.\n     *\n     * @param span    possibly unsampled.\n     * @param request holds propagation fields. For example, an outgoing message or http request.\n     */\n    void inject(Span span, R request);\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/trace/Message.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.trace;\n\n\npublic interface Message<M> {\n    M get();\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/trace/MessagingRequest.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.trace;\n\n\n/**\n * Interface request type used for parsing and sampling of messaging producers and consumers.\n */\npublic interface MessagingRequest extends Request {\n\n    /**\n     * The unqualified, case-sensitive semantic message operation name. The currently defined names\n     * are \"send\" and \"receive\".\n     *\n     * <p>Examples:\n     * <pre><ul>\n     *   <li>Amazon SQS - {@code AmazonSQS.sendMessageBatch()} is a \"send\" operation</li>\n     *   <li>JMS - {@code MessageProducer.send()} is a \"send\" operation</li>\n     *   <li>Kafka - {@code Consumer.poll()} is a \"receive\" operation</li>\n     *   <li>RabbitMQ - {@code Consumer.handleDelivery()} is a \"receive\" operation</li>\n     * </ul></pre>\n     *\n     * <p>Note: There is no constant set of operations, yet. Even when there is a constant set, there\n     * may be operations such as \"browse\" or \"purge\" which aren't defined. Once implementation\n     * matures, a constant file will be defined, with potentially more names.\n     *\n     * <p>Conventionally associated with the tag \"messaging.operation\"\n     *\n     * @return the messaging operation or null if unreadable.\n     */\n    String operation();\n\n    /**\n     * Type of channel, e.g. \"queue\" or \"topic\". {@code null} if unreadable.\n     *\n     * <p>Conventionally associated with the tag \"messaging.channel_kind\"\n     *\n     * @see #channelName()\n     */\n    // Naming matches conventions for Span\n    String channelKind();\n\n    /**\n     * Messaging channel name, e.g. \"hooks\" or \"complaints\". {@code null} if unreadable.\n     *\n     * <p>Conventionally associated with the tag \"messaging.channel_name\"\n     *\n     * @see #channelKind()\n     */\n    String channelName();\n\n    /**\n     * Returns the underlying request object or {@code null} if there is none. Here are some request\n     * objects: {@code org.apache.http.HttpRequest}, {@code org.apache.dubbo.rpc.Invocation}, {@code\n     * org.apache.kafka.clients.consumer.ConsumerRecord}.\n     *\n     * <p>Note: Some implementations are composed of multiple types, such as a request and a socket\n     * address of the client. Moreover, an implementation may change the type returned due to\n     * refactoring. Unless you control the implementation, cast carefully (ex using {@code\n     * instanceof}) instead of presuming a specific type will always be returned.\n     */\n    Object unwrap();\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/trace/MessagingTracing.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.trace;\n\nimport com.megaease.easeagent.plugin.api.Context;\n\nimport java.util.function.Function;\n\n/**\n * a MessagingTracing\n *\n * @param <R>\n */\npublic interface MessagingTracing<R extends MessagingRequest> {\n\n    /**\n     * @return {@link Extractor}\n     */\n    Extractor<R> producerExtractor();\n\n    /**\n     * @return {@link Extractor}\n     */\n    Extractor<R> consumerExtractor();\n\n    /**\n     * @return {@link Injector}\n     */\n    Injector<R> producerInjector();\n\n    /**\n     * @return {@link Injector}\n     */\n    Injector<R> consumerInjector();\n\n    /**\n     * Returns an overriding sampling decision for a new trace. Defaults to ignore the request and use\n     * the {@link #consumerSampler()}  trace ID instead}.\n     *\n     * <p>This decision happens when trace IDs were not in headers, or a sampling decision has not\n     * yet been made. For example, if a trace is already in progress, this function is not called. You\n     * can implement this to skip channels that you never want to trace.\n     */\n    Function<R, Boolean> consumerSampler();\n\n    /**\n     * Returns an overriding sampling decision for a new trace. Defaults to ignore the request and use\n     * the {@link #producerSampler()}  trace ID instead}.\n     *\n     * <p>This decision happens when a trace was not yet started in process. For example, you may be\n     * making an messaging request as a part of booting your application. You may want to opt-out of\n     * tracing producer requests that did not originate from a consumer request.\n     */\n    Function<R, Boolean> producerSampler();\n\n    /**\n     * Obtain key:value from the message request and create a Span, Examples: kafka consumer, rabbitmq consumer\n     * <p>\n     * It will set the Span's kind, name and cached scope through {@link Request#kind()}, {@link Request#name()}\n     * and {@link Request#cacheScope()}.\n     *\n     * <p>\n     * It will set the Span's tags \"messaging.operation\", \"messaging.channel_kind\", \"messaging.channel_name\" from request\n     * {@link MessagingRequest#operation()} {@link MessagingRequest#channelKind()} {@link MessagingRequest#channelName()}\n     *\n     * <p>\n     * It just only obtain the key:value required by Trace from the {@link Request#header(String)},\n     * If you need and get Span, generate result use {@link Context#consumerSpan(MessagingRequest)}.\n     *\n     * @param request {@link MessagingRequest}\n     * @return {@link Span}\n     * @see Context#consumerSpan(MessagingRequest)\n     */\n    Span consumerSpan(R request);\n\n\n    /**\n     * Create a Span for message producer. Examples: kafka producer, rabbitmq producer\n     * <p>\n     * It will set the Span's tags \"messaging.operation\", \"messaging.channel_kind\", \"messaging.channel_name\" from request\n     * {@link MessagingRequest#operation()} {@link MessagingRequest#channelKind()} {@link MessagingRequest#channelName()}\n     *\n     * <p>\n     * It just only pass multiple key:value values required by Trace through\n     * {@link Request#setHeader(String, String)}, And set the Span's kind, name and\n     * cached scope through {@link Request#kind()}, {@link Request#name()} and {@link Request#cacheScope()}.\n     * If you need and get Span, generate result use {@link Context#producerSpan(MessagingRequest)}.\n     *\n     * @param request {@link MessagingRequest}\n     * @return {@link Span}\n     * @see Context#producerSpan(MessagingRequest)\n     */\n    Span producerSpan(R request);\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/trace/Request.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.trace;\n\nimport com.megaease.easeagent.plugin.api.Context;\n\n/**\n * Interface request type used for parsing and sampling.\n */\npublic interface Request extends Setter, Getter {\n    /**\n     * The remote {@link Span.Kind} describing the direction and type of the request.\n     * {@code span.kind(request.kind())}\n     */\n    Span.Kind kind();\n\n    /**\n     * The header get of the request for Span and EaseAgent Context.\n     * <pre>{@code\n     *  String traceId = request.header(\"X-B3-TraceId\");\n     *  String spanId = request.header(\"X-B3-SpanId\");\n     *  String parentSpanId = request.header(\"X-B3-ParentSpanId\");\n     *  String rootSource = request.header(\"root-source\");\n     *  ......\n     * }</pre>\n     * <p>\n     * It is usually called on the server side when collaboration between multiple processes is required.\n     * {@code client --> <spanId,root-source...>server }\n     * <p>\n     * The class that implements this method needs to provide the name: value passed by the previous process,\n     * It can be passed by using http or tcp.\n     *\n     * <pre>{@code\n     *  class IRequest implements Request{\n     *      HttpRequest httpRequest;\n     *      String header(String name){\n     *          return httpRequest.getHeaders(name);\n     *      }\n     *  }\n     * }</pre>\n     *\n     * @see Context#serverReceive(Request)\n     */\n    String header(String name);\n\n    /**\n     * The remote name describing the direction of the request.\n     * {@code span.name(request.name())}\n     */\n    String name();\n\n    /**\n     * When true, cache the scope in span.\n     * <pre>{@code\n     *  span.cacheScope();\n     * }</pre>\n     *\n     * @return boolean\n     * @see {@link Span#cacheScope()}\n     */\n    boolean cacheScope();\n\n    /**\n     * The header set of the span and EaseAgent for request.\n     * It is usually called on the client when collaboration between multiple processes is required.\n     * {@code client<spanId,root-source...> --> server }\n     * <p>\n     * The class that implements this method needs to pass the name:value of the method to the next process,\n     * It can be passed by using http or tcp.\n     *\n     * <p>\n     * <pre>{@code\n     *  request.setHeader(\"X-B3-TraceId\", span.traceIdString());\n     *  request.setHeader(\"X-B3-SpanId\", span.spanIdString());\n     *  request.setHeader(\"root-source\", context.get(\"root-source\"));\n     *  ......\n     * }</pre>\n     * <p>\n     * <pre>{@code\n     *  class IRequest implements Request{\n     *      HttpRequest httpRequest;\n     *      void setHeader(String name, String value){\n     *          httpRequest.setHeader(name, value);\n     *      }\n     *  }\n     * }</pre>\n     *\n     * @see Context#clientRequest(Request)\n     */\n    void setHeader(String name, String value);\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/trace/Response.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.trace;\n\nimport com.megaease.easeagent.plugin.api.context.RequestContext;\n\n/**\n * Interface Response type used for parsing and sampling.\n * Used when multi-process collaboration is needed, information is extracted from the response and recorded in the {@link Span#tag(String, String)}\n * Usually used to support \"ease mesh\".\n *\n * @see RequestContext#finish(Response)\n */\npublic interface Response extends Getter {\n\n\n    /**\n     * The method of extracting information from the response\n     */\n    String header(String name);\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/trace/Scope.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.trace;\n\nimport java.io.Closeable;\n\n/**\n * A span remains in the scope it was bound to until close is called.\n *\n * <p>This type can be extended so that the object graph can be built differently or overridden,\n * for example via zipkin or when mocking.\n * <p>\n * The Scope must be close after plugin:\n * <p>\n * example 1:\n * <pre>{@code\n *    void after(...){\n *       RequestContext pCtx = context.get(...)\n *       try{\n *          //do business\n *       }finally{\n *           pCtx.scope().close();\n *       }\n *    }\n * }</pre>\n * <p>\n * example 2:\n * <pre>{@code\n *    void after(...){\n *       RequestContext pCtx = context.get(...)\n *       try (Scope scope = pCtx.scope()) {\n *          //do business\n *       }\n *    }\n * }</pre>\n * <p>\n */\npublic interface Scope extends Closeable {\n    /**\n     * No exceptions are thrown when unbinding a span scope.\n     * It must be call after your business.\n     * <pre>{@code\n     *  try{\n     *      ......\n     *  }finally{\n     *      scope.close();\n     *  }\n     * }</pre>\n     */\n    @Override\n    void close();\n\n    /**\n     * Returns the underlying Scope object or {@code null} if there is none. Here is a Scope\n     * objects: {@code brave.Scope}.\n     *\n     * @return\n     */\n    Object unwrap();\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/trace/Setter.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.trace;\n\npublic interface Setter {\n    /**\n     * @param name  header name\n     * @param value header value\n     * @see Request#setHeader(String name, String value)\n     */\n    void setHeader(String name, String value);\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/trace/Span.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.trace;\n\nimport javax.annotation.Nullable;\n\n/**\n * Here's a typical example of synchronous tracing from perspective of the span:\n * <pre>{@code\n * // Note span methods chain. Explicitly start the span when ready.\n * Span span = tracer.nextSpan().name(\"encode\").start();\n * // A span is not responsible for making itself current (scoped); the tracer is\n * try (Encoder encoder = getEncoder()) {\n *   return encoder.encode();\n * } catch (RuntimeException | Error e) {\n *   span.error(e); // Unless you handle exceptions, you might not know the operation failed!\n *   throw e;\n * } finally {\n *   span.finish(); // finish - start = the duration of the operation in microseconds\n * }\n * }</pre>\n *\n * <p>This captures duration of {@link #start()} until {@link #finish()} is called.\n *\n * <h3>Usage notes</h3>\n * All methods return {@linkplain Span} for chaining, but the instance is always the same. Also,\n * when only tracing in-process operations.\n *\n * <p>This type can be extended so that the object graph can be built differently or overridden,\n * for example via zipkin or when mocking.\n */\n// Design note: this does not require a builder as the span is mutable anyway. Having a single\n// mutation interface is less code to maintain. Those looking to prepare a span before starting it\n// can simply call start when they are ready.\n@SuppressWarnings(\"unused\")\npublic interface Span {\n    enum Kind {\n        CLIENT,\n        SERVER,\n        /**\n         * When present, {@link #start()} is the moment a producer sent a message to a destination. A\n         * duration between {@link #start()} and {@link #finish()} may imply batching delay. {@link\n         * #remoteServiceName(String)} and {@link #remoteIpAndPort(String, int)} indicates the\n         * destination, such as a broker.\n         *\n         * <p>Unlike {@link #CLIENT}, messaging spans never share a span ID. For example, the {@link\n         * #CONSUMER} of the same message has {@link Span#parentId()} set to this span's {@link\n         * Span#spanId()}.\n         */\n        PRODUCER,\n        /**\n         * When present, {@link #start()} is the moment a consumer received a message from an origin. A\n         * duration between {@link #start()} and {@link #finish()} may imply a processing backlog. while\n         * {@link #remoteServiceName(String)} and {@link #remoteIpAndPort(String, int)} indicates the\n         * origin, such as a broker.\n         *\n         * <p>Unlike {@link #SERVER}, messaging spans never share a span ID. For example, the {@link\n         * #PRODUCER} of this message is the {@link Span#parentId()} of this span.\n         */\n        CONSUMER\n    }\n\n    /**\n     * When true, no recording is done and nothing is reported . However, this span should\n     * still be injected into outgoing requests. Use this flag to avoid performing expensive\n     * computation.\n     */\n    boolean isNoop();\n\n    /**\n     * Sets the string name for the logical operation this span represents.\n     */\n    Span name(String name);\n\n    /**\n     * Tags give your span context for search, viewing and analysis. For example, a key\n     * \"your_app.version\" would let you lookup spans by version. A tag \"sql.query\" isn't searchable,\n     * but it can help in debugging when viewing a trace.\n     *\n     * @param key   Name used to lookup spans, such as \"your_app.version\", cannot be <code>null</code>.\n     * @param value String value, cannot be <code>null</code>.\n     */\n    Span tag(String key, String value);\n\n    /**\n     * Associates an event that explains latency with the current system time.\n     *\n     * @param value A short tag indicating the event, like \"finagle.retry\"\n     */\n    Span annotate(String value);\n\n    /**\n     * Starts the span with an implicit timestamp.\n     *\n     * <p>Spans can be modified before calling start. For example, you can add tags to the span and\n     * set its name without lock contention.\n     */\n    Span start();\n\n    /**\n     * Like {@link #start()}, except with a given timestamp in microseconds.\n     *\n     * <p>Take extreme care with this feature as it is easy to have incorrect timestamps. If you must\n     * use this.\n     */\n    Span start(long timestamp);\n\n    /**\n     * When present, the span is remote. This value clarifies how to interpret {@link\n     * #remoteServiceName(String)} and {@link #remoteIpAndPort(String, int)}.\n     */\n    Span kind(@Nullable Kind kind);\n\n    /**\n     * Like {@link #annotate(String)}, except with a given timestamp in microseconds.\n     *\n     * <p>Take extreme care with this feature as it is easy to have incorrect timestamps. If you must\n     * use this.\n     */\n    Span annotate(long timestamp, String value);\n\n    /**\n     * Records an error that impacted this operation.\n     *\n     * <p><em>Note:</em> Calling this does not {@linkplain #finish() finish} the span.\n     */\n    // Design note: <T extends Throwable> T error(T throwable) is tempting but this doesn't work in\n    // multi-catch. In practice, you should always at least catch RuntimeException and Error.\n\n    Span error(Throwable throwable);\n\n    /**\n     * Lower-case label of the remote node in the service graph, such as \"fav-star\". Do not set if\n     * unknown. Avoid names with variables or unique identifiers embedded.\n     *\n     * <p>This is a primary label for trace lookup and aggregation, so it should be intuitive and\n     * consistent. Many use a name from service discovery.\n     *\n     * @see #remoteIpAndPort(String, int)\n     */\n    Span remoteServiceName(String remoteServiceName);\n\n    /**\n     * Sets the IP and port associated with the remote endpoint. For example, the server's listen\n     * socket or the connected client socket. This can also be set to forwarded values, such as an\n     * advertised IP.\n     *\n     * <p>Invalid inputs, such as hostnames, will return false. Port is only set with a valid IP, and\n     * zero or negative port values are ignored. For example, to set only the IP address, leave port\n     * as zero.\n     *\n     * <p>This returns boolean, not Span as it is often the case strings are malformed. Using this,\n     * you can do conditional parsing like so:\n     * <pre>{@code\n     * if (span.remoteIpAndPort(address.getHostAddress(), target.getPort())) return;\n     * span.remoteIpAndPort(address.getHostName(), target.getPort());\n     * }</pre>\n     *\n     * <p>Note: Comma separated lists are not supported. If you have multiple entries choose the one\n     * most indicative of the remote side. For example, the left-most entry in X-Forwarded-For.\n     *\n     * @param remoteIp   the IPv4 or IPv6 literal representing the remote service connection\n     * @param remotePort the port associated with the IP, or zero if unknown.\n     * @see #remoteServiceName(String)\n     * @since 5.2\n     */\n    // NOTE: this is remote (IP, port) vs remote IP:port String as zipkin2.Endpoint separates the two,\n    // and IP:port strings are uncommon at runtime (even if they are common at config).\n    // Parsing IP:port pairs on each request, including concerns like IPv6 bracketing, would add\n    // weight for little benefit. If this changes, we can overload it.\n    boolean remoteIpAndPort(@Nullable String remoteIp, int remotePort);\n\n    /**\n     * Throws away the current span without reporting it.\n     */\n    void abandon();\n\n    /**\n     * Reports the span complete, assigning the most precise duration possible.\n     */\n    void finish();\n\n    /**\n     * Like {@link #finish()}, except with a given timestamp in microseconds.\n     *\n     * <p> span duration is derived by subtracting the start\n     * timestamp from this, and set when appropriate.\n     *\n     * <p>Take extreme care with this feature as it is easy to have incorrect timestamps. If you must\n     * use this.\n     */\n    void finish(long timestamp);\n\n    /**\n     * Reports the span, even if unfinished. Most users will not call this method.\n     *\n     * <p>This primarily supports two use cases: one-way spans and orphaned spans. For example, a\n     * one-way span can be modeled as a span where one tracer calls start and another calls finish. In\n     * order to report that span from its origin, flush must be called.\n     *\n     * <p>Another example is where a user didn't call finish within a deadline or before a shutdown\n     * occurs. By flushing, you can report what was in progress.\n     */\n    // * a span should not be routinely flushed, only when it has finished, or we don't believe this\n    //   tracer will finish it.\n    void flush();\n\n    /**\n     * Usually calls a {@link Request#setHeader(String, String)} for each propagation field to send downstream.\n     *\n     * @param request holds propagation fields. For example, an outgoing message or http request.\n     */\n    void inject(Request request);\n\n    /**\n     * Sets the current span in scope until the returned object is closed. It is a programming error\n     * to drop or never close the result. Using try-with-resources is preferred for this reason.\n     */\n    Scope maybeScope();\n\n    /**\n     * Sets the current span in scope and cache the scope in span until the span is calling {@link #finish()}.\n     * It is a programming error to drop or never close the result. Using try-with-resources is preferred for this reason.\n     */\n    Span cacheScope();\n\n    /**\n     * Returns the hex representation of the span's trace ID\n     */\n    String traceIdString();\n\n    /**\n     * Returns the hex representation of the span's ID\n     */\n    String spanIdString();\n\n    /**\n     * Returns the hex representation of the span's parent ID\n     */\n    String parentIdString();\n\n    /**\n     * Unique 8-byte identifier for a trace, set on all spans within it.\n     */\n    Long traceId();\n\n    /**\n     * Unique 8-byte identifier of this span within a trace.\n     *\n     * <p>A span is uniquely identified in storage by ({@linkplain #traceId}).\n     */\n    Long spanId();\n\n    /**\n     * The parent's {@link #spanId} or null if this the root span in a trace.\n     */\n    Long parentId();\n\n    /**\n     * Returns the underlying Span object or {@code null} if there is none. Here is some Span\n     * objects: {@code brave.LazySpan}, {@code brave.RealSpan} .\n     *\n     * @return\n     */\n    Object unwrap();\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/trace/SpanContext.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.trace;\n\npublic interface SpanContext {\n\n    /**\n     * When true, do nothing anything and nothing is reported . However, this Tracing should\n     * still be injected into outgoing requests. Use this flag to avoid performing expensive\n     * computation.\n     *\n     * @return boolean\n     */\n    boolean isNoop();\n\n    /**\n     * Returns the underlying Span Context object or {@code null} if there is none. Here is a span Context\n     * objects: {@code brave.propagation.TraceContext}.\n     * @return\n     */\n    Object unwrap();\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/trace/Tracing.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.trace;\n\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.bridge.NoOpTracer;\n\n/**\n * This provides utilities needed for trace instrumentation.\n *\n * <p>This type can be extended so that the object graph can be built differently or overridden,\n * for example via zipkin or when mocking.\n */\npublic interface Tracing {\n\n    /**\n     * When true, do nothing anything and nothing is reported . However, this Tracing should\n     * still be injected into outgoing requests. Use this flag to avoid performing expensive\n     * computation.\n     *\n     * @return boolean\n     */\n    boolean isNoop();\n\n    /**\n     * true if Thread\n     * @return boolean\n     */\n    boolean hasCurrentSpan();\n\n    /**\n     * Returns the current span in scope or {@link NoOpTracer#NO_OP_SPAN} if there isn't one.\n     *\n     * <p> as it is a stable type and will never return null.\n     *\n     * @return {@link Span}\n     */\n    Span currentSpan();\n\n    /**\n     * Returns a new child span if there's a {@link #currentSpan()} or a new trace if there isn't.\n     *\n     * @return {@link Span}\n     */\n    Span nextSpan();\n\n    /**\n     * get MessagingTracing for message tracing\n     * <p>\n     * If you have a Message Server and need Span, generate result use {@link Context#consumerSpan(MessagingRequest)} and\n     * {@link Context#producerSpan(MessagingRequest)}.\n     *\n     * @return {@link MessagingRequest}\n     */\n    MessagingTracing<MessagingRequest> messagingTracing();\n\n    /**\n     * Returns the underlying Tracing object or {@code null} if there is none. Here is a Tracing\n     * objects: {@code brave.propagation.TraceContext}.\n     * @return\n     */\n    Object unwrap();\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/trace/TracingContext.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.trace;\n\nimport com.megaease.easeagent.plugin.api.Context;\n\n/**\n * Subtype of {@link Context} which can set up Tracing.\n */\npublic interface TracingContext extends Context {\n\n    /**\n     * set up tracing to the session tracing context\n     *\n     * @param tracing {@link ITracing}\n     */\n    void setCurrentTracing(ITracing tracing);\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/trace/TracingProvider.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.trace;\n\npublic interface TracingProvider {\n    TracingSupplier tracingSupplier();\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/api/trace/TracingSupplier.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.trace;\n\nimport com.megaease.easeagent.plugin.api.InitializeContext;\n\nimport java.util.function.Supplier;\n\npublic interface TracingSupplier {\n    ITracing get(Supplier<InitializeContext> contextSupplier);\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/asm/Modifier.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.asm;\n\n/**\n * Modifier class extract ACC_* form Opcodes.java in ASM.\n * ASM: a very small and fast Java bytecode manipulation framework\n * Copyright (c) 2000-2011 INRIA, France Telecom\n */\npublic interface Modifier {\n    int ACC_NONE = 0x0000; // ignored\n    int ACC_PUBLIC = 0x0001; // class, field, method\n    int ACC_PRIVATE = 0x0002; // class, field, method\n    int ACC_PROTECTED = 0x0004; // class, field, method\n    int ACC_STATIC = 0x0008; // field, method\n    int ACC_FINAL = 0x0010; // class, field, method, parameter\n    int ACC_SUPER = 0x0020; // class\n    int ACC_SYNCHRONIZED = 0x0020; // method\n    int ACC_OPEN = 0x0020; // module\n    int ACC_TRANSITIVE = 0x0020; // module requires\n    int ACC_VOLATILE = 0x0040; // field\n    int ACC_BRIDGE = 0x0040; // method\n    int ACC_STATIC_PHASE = 0x0040; // module requires\n    int ACC_VARARGS = 0x0080; // method\n    int ACC_TRANSIENT = 0x0080; // field\n    int ACC_NATIVE = 0x0100; // method\n    int ACC_INTERFACE = 0x0200; // class\n    int ACC_ABSTRACT = 0x0400; // class, method\n    int ACC_STRICT = 0x0800; // method\n    int ACC_SYNTHETIC = 0x1000; // class, field, method, parameter, module *\n    int ACC_ANNOTATION = 0x2000; // class\n    int ACC_ENUM = 0x4000; // class(?) field inner\n    int ACC_MANDATED = 0x8000; // field, method, parameter, module, module *\n    int ACC_MODULE = 0x8000; // class\n\n    // ASM specific access flags.\n    // WARNING: the 16 least significant bits must NOT be used, to avoid conflicts with standard\n    // access flags, and also to make sure that these flags are automatically filtered out when\n    // written in class files (because access flags are stored using 16 bits only).\n\n    int ACC_RECORD = 0x10000; // class\n    int ACC_DEPRECATED = 0x20000; // class, field, method\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/async/AgentThreadFactory.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.async;\n\nimport javax.annotation.Nullable;\nimport java.util.concurrent.ThreadFactory;\nimport java.util.concurrent.atomic.AtomicInteger;\n\npublic class AgentThreadFactory implements ThreadFactory {\n    protected static AtomicInteger createCount = new AtomicInteger(1);\t\t\t\t\t// Used internally to compute Thread names that comply with the Java specification\n\n    @Override\n    public Thread newThread(@Nullable Runnable r) {\n        Thread thread = new Thread(r, \"EaseAgent-\" + createCount.getAndIncrement());\n        thread.setDaemon(true);\n        return thread;\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/async/ScheduleHelper.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.async;\n\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.ScheduledExecutorService;\nimport java.util.concurrent.ThreadFactory;\nimport java.util.concurrent.TimeUnit;\n\npublic class ScheduleHelper {\n    public static final ScheduleHelper DEFAULT = new ScheduleHelper();\n\n    private final ThreadFactory threadFactory = new AgentThreadFactory();\n    private ScheduledExecutorService scheduleService = Executors.newSingleThreadScheduledExecutor(threadFactory);\n\n    public void nonStopExecute(int initialDelay, int delay, Runnable command) {\n        Executors.newSingleThreadScheduledExecutor(threadFactory)\n            .scheduleWithFixedDelay(command, initialDelay, delay, TimeUnit.SECONDS);\n    }\n\n    public void execute(int initialDelay, int delay, Runnable command) {\n        this.scheduleService.scheduleWithFixedDelay(command, initialDelay, delay, TimeUnit.SECONDS);\n    }\n\n    public void shutdown() {\n        this.scheduleService.shutdown();\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/async/ScheduleRunner.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.async;\n\npublic interface ScheduleRunner {\n\n    void doJob();\n\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/async/ThreadLocalCurrentContext.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.async;\n\nimport javax.annotation.Nullable;\nimport java.io.Closeable;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.function.BiConsumer;\n\npublic class ThreadLocalCurrentContext {\n    public static final ThreadLocalCurrentContext DEFAULT = new ThreadLocalCurrentContext(new InheritableThreadLocal<>());\n    final ThreadLocal<Context> local;\n    final RevertToNullScope revertToNull;\n\n    public ThreadLocalCurrentContext(ThreadLocal<Context> local) {\n        if (local == null) throw new NullPointerException(\"local == null\");\n        this.local = local;\n        this.revertToNull = new RevertToNullScope(local);\n    }\n\n    public Context get() {\n        return local.get();\n    }\n\n    public Scope newScope(@Nullable Context current) {\n        final Context previous = local.get();\n        local.set(current);\n        return previous != null ? new RevertToPreviousScope(local, previous) : revertToNull;\n    }\n\n    public Scope maybeScope(@Nullable Context context) {\n        Context current = get();\n        if (Objects.equals(current, context)) {\n            return Scope.NOOP;\n        }\n        return newScope(context);\n    }\n\n    public void fill(BiConsumer<String, String> consumer, String[] names) {\n        final Context ctx = get();\n        if (ctx != null) {\n            for (String one : names) {\n                consumer.accept(one, ctx.get(one));\n            }\n        }\n    }\n\n    /**\n     * Wraps the input so that it executes with the same context as now.\n     */\n    public Runnable wrap(Runnable task) {\n        final Context invocationContext = get();\n        return new CurrentContextRunnable(this, invocationContext, task);\n    }\n\n    public static boolean isWrapped(Runnable task) {\n        return task instanceof CurrentContextRunnable;\n    }\n\n    public static Context createContext(String... kvs) {\n        if (kvs.length % 2 != 0) {\n            throw new IllegalArgumentException(\"size of kvs should be even number\");\n        }\n        final Context ctx = new Context();\n        for (int i = 0; i < kvs.length; i += 2) {\n            ctx.put(kvs[i], kvs[i + 1]);\n        }\n        return ctx;\n    }\n\n    public static class CurrentContextRunnable implements Runnable {\n        private final ThreadLocalCurrentContext threadLocalCurrentContext;\n        private final Context ctx;\n        private final Runnable original;\n\n        public CurrentContextRunnable(ThreadLocalCurrentContext threadLocalCurrentContext, Context ctx, Runnable original) {\n            this.threadLocalCurrentContext = threadLocalCurrentContext;\n            this.ctx = ctx;\n            this.original = original;\n        }\n\n        @Override\n        public void run() {\n            try (Scope scope = this.threadLocalCurrentContext.maybeScope(ctx)) {\n                original.run();\n            }\n        }\n    }\n\n    public interface Scope extends Closeable {\n        Scope NOOP = new NOOPScope();\n\n        @Override\n        void close();\n    }\n\n    public static class NOOPScope implements Scope {\n        @Override\n        public void close() {\n        }\n\n        @Override\n        public String toString() {\n            return \"NoopScope\";\n        }\n    }\n\n    public static class Context {\n        private final Map<String, String> data = new HashMap<>();\n\n        public String put(String key, String value) {\n            return data.put(key, value);\n        }\n\n        public String get(String key) {\n            return data.get(key);\n        }\n\n        public boolean containsKey(String key) {\n            return data.containsKey(key);\n        }\n\n        @Override\n        public String toString() {\n            return data.toString();\n        }\n    }\n\n    static final class RevertToNullScope implements Scope {\n        final ThreadLocal<Context> local;\n\n        RevertToNullScope(ThreadLocal<Context> local) {\n            this.local = local;\n        }\n\n        @Override\n        public void close() {\n            local.set(null);\n        }\n    }\n\n    static final class RevertToPreviousScope implements Scope {\n        final ThreadLocal<Context> local;\n        final Context previous;\n\n        RevertToPreviousScope(ThreadLocal<Context> local, Context previous) {\n            this.local = local;\n            this.previous = previous;\n        }\n\n        @Override\n        public void close() {\n            local.set(previous);\n        }\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/async/ThreadUtils.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.async;\n\nimport java.util.function.Supplier;\n\npublic class ThreadUtils {\n\n    private ThreadUtils() {\n    }\n\n    public static <V> V callWithClassLoader(ClassLoader use, Supplier<V> runnable) {\n        ClassLoader old = Thread.currentThread().getContextClassLoader();\n        Thread.currentThread().setContextClassLoader(use);\n        try {\n            return runnable.get();\n        } finally {\n            Thread.currentThread().setContextClassLoader(old);\n        }\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/bean/AgentInitializingBean.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.bean;\n\npublic interface AgentInitializingBean {\n    void afterPropertiesSet();\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/bean/BeanProvider.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.bean;\n\nimport com.megaease.easeagent.plugin.Ordered;\n\npublic interface BeanProvider extends Ordered {\n    enum BeanOrder {\n        INIT(0, \"init\"),\n        HIGH(20, \"high\"),\n        METRIC_REGISTRY(200, \"metric\"),\n        LOW(210, \"low\");\n\n        private final int order;\n        private final String name;\n\n        BeanOrder(int s, String name) {\n            this.order = s;\n            this.name = name;\n        }\n\n        public int getOrder() {\n            return this.order;\n        }\n\n        public String getName() {\n            return this.name;\n        }\n    }\n\n    @Override\n    default int order() {\n        return BeanOrder.HIGH.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/bridge/AgentInfo.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.bridge;\n\npublic class AgentInfo {\n    private final String type;\n    private final String version;\n\n    public AgentInfo(String type, String version) {\n        this.type = type;\n        this.version = version;\n    }\n\n    public String getType() {\n        return type;\n    }\n\n    public String getVersion() {\n        return version;\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/bridge/EaseAgent.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.bridge;\n\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.Reporter;\nimport com.megaease.easeagent.plugin.api.config.*;\nimport com.megaease.easeagent.plugin.api.context.IContextManager;\nimport com.megaease.easeagent.plugin.api.dispatcher.IDispatcher;\nimport com.megaease.easeagent.plugin.api.logging.ILoggerFactory;\nimport com.megaease.easeagent.plugin.api.logging.Logger;\nimport com.megaease.easeagent.plugin.api.logging.Mdc;\nimport com.megaease.easeagent.plugin.api.metric.*;\nimport com.megaease.easeagent.plugin.api.metric.name.NameFactory;\nimport com.megaease.easeagent.plugin.api.metric.name.Tags;\nimport com.megaease.easeagent.plugin.report.AgentReport;\n\nimport java.net.URLClassLoader;\nimport java.util.function.Supplier;\n\n/**\n * the bridge api will be initiated when agent startup\n */\npublic final class EaseAgent {\n    public static AgentInfo agentInfo;\n    public static MetricRegistrySupplier metricRegistrySupplier = NoOpMetrics.NO_OP_METRIC_SUPPLIER;\n    public static IContextManager initializeContextSupplier = () -> NoOpContext.NO_OP_CONTEXT;\n    public static ILoggerFactory loggerFactory = NoOpLoggerFactory.INSTANCE;\n    public static Mdc loggerMdc = NoOpLoggerFactory.NO_OP_MDC_INSTANCE;\n    public static IConfigFactory configFactory = new NoOpConfigFactory();\n    public static AgentReport agentReport = new NoOpAgentReporter();\n\n    public static IDispatcher dispatcher = new NoOpDispatcher();\n\n    public static Supplier<URLClassLoader> agentClassloader = () -> null;\n\n    public static URLClassLoader getAgentClassLoader() {\n        return agentClassloader.get();\n    }\n\n    public static AgentReport getAgentReport() {\n        return agentReport;\n    }\n\n    /**\n     * @see ILoggerFactory#getLogger(Class)\n     */\n    public static Logger getLogger(Class clazz) {\n        return loggerFactory.getLogger(clazz);\n    }\n\n    /**\n     * @see MetricRegistrySupplier#newMetricRegistry(IPluginConfig, NameFactory, Tags)\n     */\n    public static MetricRegistry newMetricRegistry(IPluginConfig config, NameFactory nameFactory, Tags tags) {\n        return metricRegistrySupplier.newMetricRegistry(config, nameFactory, tags);\n    }\n\n    public static <T extends ServiceMetric> T getOrCreateServiceMetric(IPluginConfig config, Tags tags, ServiceMetricSupplier<T> supplier) {\n        return ServiceMetricRegistry.getOrCreate(config, tags, supplier);\n    }\n\n    /**\n     * @see MetricRegistrySupplier#reporter(IPluginConfig)\n     */\n    public static Reporter metricReporter(IPluginConfig config) {\n        return metricRegistrySupplier.reporter(config);\n    }\n\n    public static Config getConfig() {\n        return configFactory.getConfig();\n    }\n\n    /**\n     * Returns a configuration property from the agent's global configuration.\n     *\n     * @return The configuration of this Java agent.\n     */\n    public static String getConfig(String property) {\n        return configFactory.getConfig(property);\n    }\n\n    /**\n     * find the configuration property from the agent's global configuration.\n     * if not exist, then return @{defaultValue}\n     *\n     * @param defaultValue default value returned when the property is not exist\n     * @return The configuration of this Java agent.\n     */\n    public static String getConfig(String property, String defaultValue) {\n        return configFactory.getConfig(property, defaultValue);\n    }\n\n    /**\n     * get a Config by domain, namespace and name\n     *\n     * @param domain\n     * @param namespace\n     * @param name\n     * @return {@link IPluginConfig}\n     */\n    public static IPluginConfig getConfig(String domain, String namespace, String name) {\n        return configFactory.getConfig(domain, namespace, name);\n    }\n\n    /**\n     * get or create an AutoRefreshPluginConfigImpl\n     *\n     * @param domain    String\n     * @param namespace String\n     * @param name      String\n     * @return AutoRefreshPluginConfigImpl\n     * @see AutoRefreshPluginConfigRegistry#getOrCreate(String, String, String)\n     */\n    public static AutoRefreshPluginConfigImpl getOrCreateAutoRefreshConfig(String domain, String namespace, String name) {\n        return AutoRefreshPluginConfigRegistry.getOrCreate(domain, namespace, name);\n    }\n\n    /**\n     * @return current tracing {@link Context} for session\n     */\n    public static Context getContext() {\n        return initializeContextSupplier.getContext();\n    }\n\n    public static AgentInfo getAgentInfo() {\n        return agentInfo;\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/bridge/NoOpAgentReporter.java",
    "content": "/*\n * Copyright (c) 2022, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.plugin.bridge;\n\nimport com.megaease.easeagent.plugin.api.Reporter;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.logging.AccessLogInfo;\nimport com.megaease.easeagent.plugin.api.otlp.common.AgentLogData;\nimport com.megaease.easeagent.plugin.report.AgentReport;\nimport com.megaease.easeagent.plugin.report.metric.MetricReporterFactory;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\n\npublic class NoOpAgentReporter implements AgentReport {\n    @Override\n    public void report(ReportSpan span) {\n        // ignored\n    }\n\n    @Override\n    public void report(AccessLogInfo log) {\n        // ignored\n    }\n\n    @Override\n    public void report(AgentLogData log) {\n        // ignored\n    }\n\n    @Override\n    public MetricReporterFactory metricReporter() {\n        return new MetricReporterFactory() {\n            @Override\n            public Reporter reporter(IPluginConfig config) {\n                return NoOpReporter.NO_OP_REPORTER;\n            }\n        };\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/bridge/NoOpCleaner.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.bridge;\n\nimport com.megaease.easeagent.plugin.api.Cleaner;\n\npublic class NoOpCleaner implements Cleaner {\n    public static final NoOpCleaner INSTANCE = new NoOpCleaner();\n\n    @Override\n    public void close() {\n\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/bridge/NoOpConfigFactory.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.bridge;\n\nimport com.megaease.easeagent.plugin.api.config.Config;\nimport com.megaease.easeagent.plugin.api.config.ConfigChangeListener;\nimport com.megaease.easeagent.plugin.api.config.IConfigFactory;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\n\nimport java.util.*;\n\npublic class NoOpConfigFactory implements IConfigFactory {\n    @Override\n    public Config getConfig() {\n        return NoOpConfig.INSTANCE;\n    }\n\n    @Override\n    public String getConfig(String property) {\n        return null;\n    }\n\n    @Override\n    public String getConfig(String property, String defaultValue) {\n        return defaultValue;\n    }\n\n    @Override\n    public IPluginConfig getConfig(String domain, String namespace, String id) {\n        return new NoOpIPluginConfig(domain, namespace, id);\n    }\n\n    static class NoOpConfig implements Config {\n        static final NoOpConfig INSTANCE = new NoOpConfig();\n        private final HashMap<String, String> source = new HashMap<>();\n\n        @Override\n        public boolean hasPath(String path) {\n            return false;\n        }\n\n        @Override\n        public String getString(String name) {\n            return null;\n        }\n\n        @Override\n        public String getString(String name, String defVal) {\n            return defVal;\n        }\n\n        @Override\n        public Integer getInt(String name) {\n            return null;\n        }\n\n        @Override\n        public Integer getInt(String name, int defValue) {\n            Integer anInt = getInt(name);\n            if (anInt == null) {\n                return defValue;\n            }\n            return anInt;\n        }\n\n        @Override\n        public Boolean getBoolean(String name) {\n            return false;\n        }\n\n        @Override\n        public Boolean getBoolean(String name, boolean defValue) {\n            Boolean aBoolean = getBoolean(name);\n            if (aBoolean == null) {\n                return defValue;\n            }\n            return aBoolean;\n        }\n\n        @Override\n        public Boolean getBooleanNullForUnset(String name) {\n            return null;\n        }\n\n        @Override\n        public Double getDouble(String name) {\n            return null;\n        }\n\n        @Override\n        public Double getDouble(String name, double defValue) {\n            return defValue;\n        }\n\n        @Override\n        public Long getLong(String name) {\n            return null;\n        }\n\n        @Override\n        public Long getLong(String name, long defValue) {\n            return defValue;\n        }\n\n        @Override\n        public List<String> getStringList(String name) {\n            return Collections.emptyList();\n        }\n\n        @Override\n        public Runnable addChangeListener(ConfigChangeListener listener) {\n            return null;\n        }\n\n        @Override\n        public Set<String> keySet() {\n            return this.source.keySet();\n        }\n\n        @Override\n        public Map<String, String> getConfigs() {\n            return source;\n        }\n\n        @Override\n        public void updateConfigs(Map<String, String> changes) {\n            // ignored\n        }\n\n        @Override\n        public void updateConfigsNotNotify(Map<String, String> changes) {\n            // ignored\n        }\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/bridge/NoOpContext.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.bridge;\n\nimport com.megaease.easeagent.plugin.api.Cleaner;\nimport com.megaease.easeagent.plugin.api.InitializeContext;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.context.AsyncContext;\nimport com.megaease.easeagent.plugin.api.context.RequestContext;\nimport com.megaease.easeagent.plugin.api.trace.*;\n\nimport java.util.Collections;\nimport java.util.Iterator;\nimport java.util.Map;\n\npublic class NoOpContext {\n    public static final NoopContext NO_OP_CONTEXT = NoopContext.INSTANCE;\n    public static final EmptyAsyncContext NO_OP_ASYNC_CONTEXT = EmptyAsyncContext.INSTANCE;\n    public static final NoopRequestContext NO_OP_PROGRESS_CONTEXT = NoopRequestContext.INSTANCE;\n\n    public static class NoopContext implements InitializeContext {\n        private static final NoopContext INSTANCE = new NoopContext();\n        private static final Iterator<String> EMPTY_KEYS = new Iterator<String>() {\n            @Override\n            public boolean hasNext() {\n                return false;\n            }\n\n            @Override\n            public String next() {\n                return null;\n            }\n        };\n\n        @Override\n        public boolean isNoop() {\n            return true;\n        }\n\n        @Override\n        public Tracing currentTracing() {\n            return NoOpTracer.NO_OP_TRACING;\n        }\n\n        @Override\n        public <V> V putLocal(String key, V value) {\n            return null;\n        }\n\n        @Override\n        public <V> V getLocal(String key) {\n            return null;\n        }\n\n        @Override\n        public <V> V get(Object key) {\n            return null;\n        }\n\n        @Override\n        public <V> V remove(Object key) {\n            return null;\n        }\n\n        @Override\n        public <V> V put(Object key, V value) {\n            return value;\n        }\n\n        @Override\n        public IPluginConfig getConfig() {\n            return NoOpIPluginConfig.INSTANCE;\n        }\n\n        @Override\n        public int enter(Object key) {\n            return 0;\n        }\n\n        @Override\n        public int exit(Object key) {\n            return 0;\n        }\n\n        @Override\n        public AsyncContext exportAsync() {\n            return EmptyAsyncContext.INSTANCE;\n        }\n\n        @Override\n        public Cleaner importAsync(AsyncContext snapshot) {\n            return NoOpCleaner.INSTANCE;\n        }\n\n        @Override\n        public RequestContext clientRequest(Request request) {\n            return NoopRequestContext.INSTANCE;\n        }\n\n        @Override\n        public RequestContext serverReceive(Request request) {\n            return NoopRequestContext.INSTANCE;\n        }\n\n        @Override\n        public Span consumerSpan(MessagingRequest request) {\n            return NoOpTracer.NO_OP_SPAN;\n        }\n\n        @Override\n        public Span producerSpan(MessagingRequest request) {\n            return NoOpTracer.NO_OP_SPAN;\n        }\n\n        @Override\n        public Span nextSpan() {\n            return NoOpTracer.NO_OP_SPAN;\n        }\n\n        @Override\n        public <T> T pop() {\n            return null;\n        }\n\n        @Override\n        public <T> T peek() {\n            return null;\n        }\n\n        @Override\n        public <T> void push(T obj) {\n        }\n\n        @Override\n        public Runnable wrap(Runnable task) {\n            return task;\n        }\n\n        @Override\n        public boolean isWrapped(Runnable task) {\n            return true;\n        }\n\n        @Override\n        public boolean isNecessaryKeys(String key) {\n            return false;\n        }\n\n        @Override\n        public void consumerInject(Span span, MessagingRequest request) {\n\n        }\n\n        @Override\n        public void producerInject(Span span, MessagingRequest request) {\n\n        }\n\n        @Override\n        public void injectForwardedHeaders(Setter setter) {\n\n        }\n\n        @Override\n        public Cleaner importForwardedHeaders(Getter getter) {\n            return NoOpCleaner.INSTANCE;\n        }\n\n        public void setCurrentTracing(ITracing tracing) {\n        }\n\n        @Override\n        public void pushConfig(IPluginConfig config) {\n        }\n\n        @Override\n        public IPluginConfig popConfig() {\n            return NoOpIPluginConfig.INSTANCE;\n        }\n\n        @Override\n        public void pushRetBound() {\n        }\n\n        @Override\n        public void popRetBound() {\n        }\n\n        @Override\n        public void popToBound() {\n        }\n\n        @Override\n        public void clear() {\n\n        }\n    }\n\n\n    public static class EmptyAsyncContext implements AsyncContext {\n        private static final EmptyAsyncContext INSTANCE = new EmptyAsyncContext();\n\n        @Override\n        public boolean isNoop() {\n            return true;\n        }\n\n        @Override\n        public SpanContext getSpanContext() {\n            return NoOpTracer.NO_OP_SPAN_CONTEXT;\n        }\n\n        @Override\n        public Cleaner importToCurrent() {\n            return NoOpCleaner.INSTANCE;\n        }\n\n        @Override\n        public Map<Object, Object> getAll() {\n            return Collections.emptyMap();\n        }\n\n        @Override\n        public <T> T get(Object o) {\n            return null;\n        }\n\n        @Override\n        public <V> V put(Object key, V value) {\n            return null;\n        }\n    }\n\n    public static class NoopRequestContext implements RequestContext {\n        private static final NoopRequestContext INSTANCE = new NoopRequestContext();\n\n        @Override\n        public boolean isNoop() {\n            return true;\n        }\n\n        @Override\n        public Span span() {\n            return NoOpTracer.NO_OP_SPAN;\n        }\n\n        @Override\n        public Scope scope() {\n            return NoOpTracer.NO_OP_SCOPE;\n        }\n\n        @Override\n        public void setHeader(String name, String value) {\n\n        }\n\n        @Override\n        public Map<String, String> getHeaders() {\n            return Collections.emptyMap();\n        }\n\n        @Override\n        public void finish(Response response) {\n\n        }\n    }\n\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/bridge/NoOpDispatcher.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.bridge;\n\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.dispatcher.IDispatcher;\n\npublic class NoOpDispatcher implements IDispatcher {\n    @Override\n    public void enter(int chainIndex, MethodInfo info) {\n    }\n\n    @Override\n    public Object exit(int chainIndex, MethodInfo methodInfo, Context context, Object result, Throwable e) {\n        return result;\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/bridge/NoOpIPluginConfig.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.bridge;\n\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.config.PluginConfigChangeListener;\n\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Set;\n\npublic class NoOpIPluginConfig implements IPluginConfig {\n    public static NoOpIPluginConfig INSTANCE = new NoOpIPluginConfig(\"Noop\", \"Noop\", \"Noop\");\n    private final String domain;\n    private final String namespace;\n    private final String id;\n\n    public NoOpIPluginConfig(String domain, String namespace, String id) {\n        this.domain = domain;\n        this.namespace = namespace;\n        this.id = id;\n    }\n\n\n    @Override\n    public String domain() {\n        return domain;\n    }\n\n    @Override\n    public String namespace() {\n        return namespace;\n    }\n\n    @Override\n    public String id() {\n        return id;\n    }\n\n    @Override\n    public boolean hasProperty(String property) {\n        return false;\n    }\n\n    @Override\n    public String getString(String property) {\n        return null;\n    }\n\n    @Override\n    public Integer getInt(String property) {\n        return null;\n    }\n\n    @Override\n    public Boolean getBoolean(String property) {\n        return null;\n    }\n\n    @Override\n    public boolean enabled() {\n        return false;\n    }\n\n    @Override\n    public Double getDouble(String property) {\n        return null;\n    }\n\n    @Override\n    public Long getLong(String property) {\n        return null;\n    }\n\n    @Override\n    public List<String> getStringList(String property) {\n        return Collections.emptyList();\n    }\n\n    @Override\n    public IPluginConfig getGlobal() {\n        return this;\n    }\n\n    @Override\n    public void addChangeListener(PluginConfigChangeListener listener) {\n\n    }\n\n    @Override\n    public Set<String> keySet() {\n        return Collections.emptySet();\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/bridge/NoOpLoggerFactory.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.bridge;\n\nimport com.megaease.easeagent.plugin.api.logging.ILoggerFactory;\nimport com.megaease.easeagent.plugin.api.logging.Logger;\nimport com.megaease.easeagent.plugin.api.logging.Mdc;\n\npublic class NoOpLoggerFactory implements ILoggerFactory {\n    public static final NoOpLoggerFactory INSTANCE = new NoOpLoggerFactory();\n    public static final NoOpMdc NO_OP_MDC_INSTANCE = new NoOpMdc();\n\n    public Logger getLogger(String name) {\n        return new NoOpLogger(name);\n    }\n\n    public static class NoOpMdc implements Mdc {\n\n        @Override\n        public void put(String key, String value) {\n\n        }\n\n        @Override\n        public void remove(String key) {\n\n        }\n\n        @Override\n        public String get(String key) {\n            return null;\n        }\n    }\n\n    public static class NoOpLogger implements Logger {\n        private final String name;\n\n        public NoOpLogger(String name) {\n            this.name = name;\n        }\n\n        @Override\n        public String getName() {\n            return name;\n        }\n\n        @Override\n        public boolean isTraceEnabled() {\n            return false;\n        }\n\n        @Override\n        public void trace(String msg) {\n\n        }\n\n        @Override\n        public void trace(String format, Object arg) {\n\n        }\n\n        @Override\n        public void trace(String format, Object arg1, Object arg2) {\n\n        }\n\n        @Override\n        public void trace(String format, Object... arguments) {\n\n        }\n\n        @Override\n        public void trace(String msg, Throwable t) {\n\n        }\n\n        @Override\n        public boolean isDebugEnabled() {\n            return false;\n        }\n\n        @Override\n        public void debug(String msg) {\n\n        }\n\n        @Override\n        public void debug(String format, Object arg) {\n\n        }\n\n        @Override\n        public void debug(String format, Object arg1, Object arg2) {\n\n        }\n\n        @Override\n        public void debug(String format, Object... arguments) {\n\n        }\n\n        @Override\n        public void debug(String msg, Throwable t) {\n\n        }\n\n        @Override\n        public boolean isInfoEnabled() {\n            return false;\n        }\n\n        @Override\n        public void info(String msg) {\n\n        }\n\n        @Override\n        public void info(String format, Object arg) {\n\n        }\n\n        @Override\n        public void info(String format, Object arg1, Object arg2) {\n\n        }\n\n        @Override\n        public void info(String format, Object... arguments) {\n\n        }\n\n        @Override\n        public void info(String msg, Throwable t) {\n\n        }\n\n        @Override\n        public boolean isWarnEnabled() {\n            return false;\n        }\n\n        @Override\n        public void warn(String msg) {\n\n        }\n\n        @Override\n        public void warn(String format, Object arg) {\n\n        }\n\n        @Override\n        public void warn(String format, Object... arguments) {\n\n        }\n\n        @Override\n        public void warn(String format, Object arg1, Object arg2) {\n\n        }\n\n        @Override\n        public void warn(String msg, Throwable t) {\n\n        }\n\n        @Override\n        public boolean isErrorEnabled() {\n            return false;\n        }\n\n        @Override\n        public void error(String msg) {\n\n        }\n\n        @Override\n        public void error(String format, Object arg) {\n\n        }\n\n        @Override\n        public void error(String format, Object arg1, Object arg2) {\n\n        }\n\n        @Override\n        public void error(String format, Object... arguments) {\n\n        }\n\n        @Override\n        public void error(String msg, Throwable t) {\n\n        }\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/bridge/NoOpMetrics.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.bridge;\n\nimport com.megaease.easeagent.plugin.api.Reporter;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.metric.*;\nimport com.megaease.easeagent.plugin.api.metric.name.NameFactory;\nimport com.megaease.easeagent.plugin.api.metric.name.Tags;\n\nimport java.io.OutputStream;\nimport java.time.Duration;\nimport java.util.Collections;\nimport java.util.Map;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.Supplier;\n\nimport static com.megaease.easeagent.plugin.bridge.NoOpReporter.NO_OP_REPORTER;\n\npublic final class NoOpMetrics {\n    public static final MetricRegistrySupplier NO_OP_METRIC_SUPPLIER = NoopMetricsRegistrySupplier.INSTANCE;\n    public static final Gauge NO_OP_GAUGE = NoopGauge.INSTANCE;\n    public static final Snapshot NO_OP_SNAPSHOT = NoopSnapshot.INSTANCE;\n    public static final Timer NO_OP_TIMER = NoopTimer.INSTANCE;\n    public static final Histogram NO_OP_HISTOGRAM = NoopHistogram.INSTANCE;\n    public static final Counter NO_OP_COUNTER = NoopCounter.INSTANCE;\n    public static final Meter NO_OP_METER = NoopMeter.INSTANCE;\n    public static final MetricRegistry NO_OP_METRIC = NoopMetricRegistry.INSTANCE;\n\n    public static final class NoopMetricsRegistrySupplier implements MetricRegistrySupplier {\n        private static final NoopMetricsRegistrySupplier INSTANCE = new NoopMetricsRegistrySupplier();\n\n        @Override\n        public MetricRegistry newMetricRegistry(IPluginConfig config, NameFactory nameFactory, Tags tags) {\n            return NoopMetricRegistry.INSTANCE;\n        }\n\n        @Override\n        public Reporter reporter(IPluginConfig config) {\n            return NO_OP_REPORTER;\n        }\n    }\n\n    public static final class NoopGauge<T> implements Gauge<T> {\n        private static final NoopGauge INSTANCE = new NoopGauge<>();\n\n        /**\n         * {@inheritDoc}\n         */\n        @Override\n        public T getValue() {\n            return null;\n        }\n\n    }\n\n    public static final class NoopSnapshot implements Snapshot {\n        private static final NoopSnapshot INSTANCE = new NoopSnapshot();\n        private static final long[] EMPTY_LONG_ARRAY = new long[0];\n\n        /**\n         * {@inheritDoc}\n         */\n        @Override\n        public double getValue(double quantile) {\n            return 0D;\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        @Override\n        public long[] getValues() {\n            return EMPTY_LONG_ARRAY;\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        @Override\n        public int size() {\n            return 0;\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        @Override\n        public long getMax() {\n            return 0L;\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        @Override\n        public double getMean() {\n            return 0D;\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        @Override\n        public long getMin() {\n            return 0L;\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        @Override\n        public double getStdDev() {\n            return 0D;\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        @Override\n        public void dump(OutputStream output) {\n            // NOP\n        }\n\n        @Override\n        public Object unwrap() {\n            return null;\n        }\n    }\n\n    public static final class NoopTimer implements Timer {\n        private static final NoopTimer INSTANCE = new NoopTimer();\n        private static final Timer.Context CONTEXT = new NoopTimer.Context();\n\n        @Override\n        public Object unwrap() {\n            return null;\n        }\n\n        private static class Context implements Timer.Context {\n\n            /**\n             * {@inheritDoc}\n             */\n            @Override\n            public long stop() {\n                return 0L;\n            }\n\n            /**\n             * {@inheritDoc}\n             */\n            @Override\n            public void close() {\n                // NOP\n            }\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        @Override\n        public void update(long duration, TimeUnit unit) {\n            // NOP\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        @Override\n        public void update(Duration duration) {\n            // NOP\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        @Override\n        public <T> T time(Callable<T> event) throws Exception {\n            return event.call();\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        @Override\n        public <T> T timeSupplier(Supplier<T> event) {\n            return event.get();\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        @Override\n        public void time(Runnable event) {\n            // NOP\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        @Override\n        public Timer.Context time() {\n            return CONTEXT;\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        @Override\n        public long getCount() {\n            return 0L;\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        @Override\n        public double getFifteenMinuteRate() {\n            return 0D;\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        @Override\n        public double getFiveMinuteRate() {\n            return 0D;\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        @Override\n        public double getMeanRate() {\n            return 0D;\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        @Override\n        public double getOneMinuteRate() {\n            return 0D;\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        @Override\n        public Snapshot getSnapshot() {\n            return NoopSnapshot.INSTANCE;\n        }\n    }\n\n    public static final class NoopHistogram implements Histogram {\n        private static final NoopHistogram INSTANCE = new NoopHistogram();\n\n        /**\n         * {@inheritDoc}\n         */\n        @Override\n        public void update(int value) {\n            // NOP\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        @Override\n        public void update(long value) {\n            // NOP\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        @Override\n        public long getCount() {\n            return 0L;\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        @Override\n        public Snapshot getSnapshot() {\n            return NoopSnapshot.INSTANCE;\n        }\n\n        @Override\n        public Object unwrap() {\n            return null;\n        }\n    }\n\n    public static final class NoopCounter implements Counter {\n        private static final NoopCounter INSTANCE = new NoopCounter();\n\n        /**\n         * {@inheritDoc}\n         */\n        @Override\n        public void inc() {\n            // NOP\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        @Override\n        public void inc(long n) {\n            // NOP\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        @Override\n        public void dec() {\n            // NOP\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        @Override\n        public void dec(long n) {\n            // NOP\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        @Override\n        public long getCount() {\n            return 0L;\n        }\n\n        @Override\n        public Object unwrap() {\n            return null;\n        }\n    }\n\n    public static final class NoopMeter implements Meter {\n        private static final NoopMeter INSTANCE = new NoopMeter();\n\n        /**\n         * {@inheritDoc}\n         */\n        @Override\n        public void mark() {\n            // NOP\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        @Override\n        public void mark(long n) {\n            // NOP\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        @Override\n        public long getCount() {\n            return 0L;\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        @Override\n        public double getFifteenMinuteRate() {\n            return 0D;\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        @Override\n        public double getFiveMinuteRate() {\n            return 0D;\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        @Override\n        public double getMeanRate() {\n            return 0D;\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        @Override\n        public double getOneMinuteRate() {\n            return 0D;\n        }\n\n        @Override\n        public Object unwrap() {\n            return null;\n        }\n    }\n\n    public static final class NoopMetricRegistry implements MetricRegistry {\n        private static final NoopMetricRegistry INSTANCE = new NoopMetricRegistry();\n\n        @Override\n        public boolean remove(String name) {\n            return true;\n        }\n\n        @Override\n        public Map<String, Metric> getMetrics() {\n            return Collections.emptyMap();\n        }\n\n        @Override\n        public Meter meter(String name) {\n            return NoopMeter.INSTANCE;\n        }\n\n        @Override\n        public Counter counter(String name) {\n            return NoopCounter.INSTANCE;\n        }\n\n        @Override\n        public Gauge gauge(String name, MetricSupplier<Gauge> supplier) {\n            return NoopGauge.INSTANCE;\n        }\n\n        @Override\n        public Histogram histogram(String name) {\n            return NoopHistogram.INSTANCE;\n        }\n\n        @Override\n        public Timer timer(String name) {\n            return NoopTimer.INSTANCE;\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/bridge/NoOpReporter.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.bridge;\n\nimport com.megaease.easeagent.plugin.api.Reporter;\nimport com.megaease.easeagent.plugin.report.EncodedData;\n\npublic class NoOpReporter implements Reporter {\n    public static final NoOpReporter NO_OP_REPORTER = new NoOpReporter();\n\n    @Override\n    public void report(String msg) {\n        // ignored\n    }\n\n    @Override\n    public void report(EncodedData msg) {\n        // ignored\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/bridge/NoOpTracer.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.bridge;\n\nimport com.megaease.easeagent.plugin.api.context.RequestContext;\nimport com.megaease.easeagent.plugin.api.trace.*;\nimport com.megaease.easeagent.plugin.utils.NoNull;\n\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.function.Function;\n\npublic class NoOpTracer {\n    public static final ITracing NO_OP_TRACING = NoopTracing.INSTANCE;\n    public static final Span NO_OP_SPAN = NoopSpan.INSTANCE;\n    public static final SpanContext NO_OP_SPAN_CONTEXT = EmptySpanContext.INSTANCE;\n    public static final Scope NO_OP_SCOPE = NoopScope.INSTANCE;\n    public static final EmptyExtractor NO_OP_EXTRACTOR = EmptyExtractor.INSTANCE;\n    public static final EmptyMessagingTracing NO_OP_MESSAGING_TRACING = EmptyMessagingTracing.INSTANCE;\n\n    public static Span noNullSpan(Span span) {\n        return NoNull.of(span, NO_OP_SPAN);\n    }\n\n    public static Extractor noNullExtractor(Extractor extractor) {\n        return NoNull.of(extractor, NO_OP_EXTRACTOR);\n    }\n\n    public static class NoopSpan implements Span {\n        private static final NoopSpan INSTANCE = new NoopSpan();\n\n        @Override\n        public boolean isNoop() {\n            return true;\n        }\n\n        @Override\n        public Span start() {\n            return this;\n        }\n\n        @Override\n        public Span start(long timestamp) {\n            return this;\n        }\n\n        @Override\n        public Span name(String name) {\n            return this;\n        }\n\n        @Override\n        public Span kind(Kind kind) {\n            return this;\n        }\n\n        @Override\n        public Span annotate(String value) {\n            return this;\n        }\n\n        @Override\n        public Span annotate(long timestamp, String value) {\n            return this;\n        }\n\n        @Override\n        public Span remoteServiceName(String remoteServiceName) {\n            return this;\n        }\n\n        /**\n         * Returns true in order to prevent secondary conditions when in no-op mode\n         */\n        @Override\n        public boolean remoteIpAndPort(String remoteIp, int port) {\n            return true;\n        }\n\n        @Override\n        public Span tag(String key, String value) {\n            return this;\n        }\n\n        @Override\n        public Span error(Throwable throwable) {\n            return this;\n        }\n\n        @Override\n        public void finish(long timestamp) {\n        }\n\n        @Override\n        public void abandon() {\n        }\n\n        @Override\n        public void finish() {\n\n        }\n\n        @Override\n        public void flush() {\n        }\n\n        @Override\n        public void inject(Request request) {\n\n        }\n\n        @Override\n        public Scope maybeScope() {\n            return NoopScope.INSTANCE;\n        }\n\n        @Override\n        public Span cacheScope() {\n            return this;\n        }\n\n        @Override\n        public String traceIdString() {\n            return \"\";\n        }\n\n        @Override\n        public String spanIdString() {\n            return \"\";\n        }\n\n        @Override\n        public String parentIdString() {\n            return \"\";\n        }\n\n        @Override\n        public Long traceId() {\n            return null;\n        }\n\n        @Override\n        public Long spanId() {\n            return null;\n        }\n\n        @Override\n        public Long parentId() {\n            return null;\n        }\n\n        @Override\n        public Object unwrap() {\n            return null;\n        }\n\n        @Override\n        public String toString() {\n            return \"NoopSpan\";\n        }\n    }\n\n    public static class NoopScope implements Scope {\n        private static final NoopScope INSTANCE = new NoopScope();\n\n        @Override\n        public void close() {\n\n        }\n\n        @Override\n        public Object unwrap() {\n            return null;\n        }\n    }\n\n    public static class NoopTracing implements ITracing {\n        private static final NoopTracing INSTANCE = new NoopTracing();\n\n        @Override\n        public Span currentSpan() {\n            return NoopSpan.INSTANCE;\n        }\n\n        @Override\n        public Span nextSpan() {\n            return NoopSpan.INSTANCE;\n        }\n\n        @Override\n        public SpanContext exportAsync() {\n            return EmptySpanContext.INSTANCE;\n        }\n\n        @Override\n        public Scope importAsync(SpanContext snapshot) {\n            return NoopScope.INSTANCE;\n        }\n\n        @Override\n        public RequestContext clientRequest(Request request) {\n            return NoOpContext.NO_OP_PROGRESS_CONTEXT;\n        }\n\n        @Override\n        public RequestContext serverReceive(Request request) {\n            return NoOpContext.NO_OP_PROGRESS_CONTEXT;\n        }\n\n        @Override\n        public List<String> propagationKeys() {\n            return Collections.emptyList();\n        }\n\n        @Override\n        public Span consumerSpan(MessagingRequest request) {\n            return NoOpTracer.NO_OP_SPAN;\n        }\n\n        @Override\n        public Span producerSpan(MessagingRequest request) {\n            return NoOpTracer.NO_OP_SPAN;\n        }\n\n        @Override\n        public MessagingTracing<MessagingRequest> messagingTracing() {\n            return EmptyMessagingTracing.INSTANCE;\n        }\n\n        @Override\n        public Object unwrap() {\n            return null;\n        }\n\n        @Override\n        public String toString() {\n            return \"NoopTracing\";\n        }\n\n        @Override\n        public boolean isNoop() {\n            return true;\n        }\n\n        @Override\n        public boolean hasCurrentSpan() {\n            return false;\n        }\n    }\n\n    public static class EmptyMessagingTracing implements MessagingTracing<MessagingRequest> {\n        private static final EmptyMessagingTracing INSTANCE = new EmptyMessagingTracing();\n        private static final Function<MessagingRequest, Boolean> NOOP_SAMPLER = r -> null;\n\n        @Override\n        public Extractor<MessagingRequest> producerExtractor() {\n            return EmptyExtractor.INSTANCE;\n        }\n\n        @Override\n        public Extractor<MessagingRequest> consumerExtractor() {\n            return EmptyExtractor.INSTANCE;\n        }\n\n        @Override\n        public Injector<MessagingRequest> producerInjector() {\n            return EmptyInjector.INSTANCE;\n        }\n\n        @Override\n        public Injector<MessagingRequest> consumerInjector() {\n            return EmptyInjector.INSTANCE;\n        }\n\n        @Override\n        public Function<MessagingRequest, Boolean> consumerSampler() {\n            return NOOP_SAMPLER;\n        }\n\n        @Override\n        public Function<MessagingRequest, Boolean> producerSampler() {\n            return NOOP_SAMPLER;\n        }\n\n        @Override\n        public Span consumerSpan(MessagingRequest request) {\n            return NoOpTracer.NO_OP_SPAN;\n        }\n\n        @Override\n        public Span producerSpan(MessagingRequest request) {\n            return NoOpTracer.NO_OP_SPAN;\n        }\n    }\n\n    public static class EmptyMessage implements Message {\n        private static final EmptyMessage INSTANCE = new EmptyMessage();\n        private static final Object OBJ_INSTANCE = new Object();\n\n        @Override\n        public Object get() {\n            return OBJ_INSTANCE;\n        }\n    }\n\n    public static class EmptyExtractor implements Extractor<MessagingRequest> {\n        private static final EmptyExtractor INSTANCE = new EmptyExtractor();\n\n\n        @Override\n        public Message extract(MessagingRequest request) {\n            return EmptyMessage.INSTANCE;\n        }\n    }\n\n    public static class EmptyInjector implements Injector<MessagingRequest> {\n        private static final EmptyInjector INSTANCE = new EmptyInjector();\n\n        @Override\n        public void inject(Span span, MessagingRequest request) {\n\n        }\n    }\n\n    public static class EmptySpanContext implements SpanContext {\n        private static final EmptySpanContext INSTANCE = new EmptySpanContext();\n\n        @Override\n        public boolean isNoop() {\n            return true;\n        }\n\n        @Override\n        public Object unwrap() {\n            return this;\n        }\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/enums/ClassMatch.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.enums;\n\npublic enum ClassMatch {\n    SUPER_CLASS,\n    INTERFACE,\n    NAMED,\n    ANNOTATION,\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/enums/Operator.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.enums;\n\npublic enum Operator {\n    AND,\n    OR,\n    NEGATE,\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/enums/Order.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.enums;\n\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\n\n/**\n * Priority definition, lower value with higher priority.\n * Higher priority interceptor run enter before lower ones\n * but exit after lower priority interceptors.\n */\npublic enum Order {\n    FOUNDATION(0, \"foundation\"),\n    HIGHEST(10, \"highest\"),\n    INIT(18, ConfigConst.PluginID.INIT),\n    REDIRECT(19, ConfigConst.PluginID.REDIRECT),\n    HIGH(20, \"high\"),\n    FORWARDED(30, ConfigConst.PluginID.FORWARDED),\n\n    TRACING_INIT(90, ConfigConst.PluginID.TRACING),\n    TRACING(100, ConfigConst.PluginID.TRACING),\n\n    METRIC(200, ConfigConst.PluginID.METRIC),\n    LOG(201, ConfigConst.PluginID.LOG),\n    LOW(210, \"low\"),\n    LOWEST(255, \"lowest\");\n\n    private final int value;\n    private final String name;\n\n    Order(int s, String name) {\n        this.value = s;\n        this.name = name;\n    }\n\n    public int getOrder() {\n        return this.value;\n    }\n\n    public String getName() {\n        return this.name;\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/enums/StringMatch.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.enums;\n\npublic enum StringMatch {\n    START_WITH,\n    END_WITH,\n    CONTAINS,\n    EQUALS,\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/field/AgentDynamicFieldAccessor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.field;\n\nimport com.megaease.easeagent.plugin.api.logging.Logger;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\n\nimport java.lang.reflect.Field;\n\npublic class AgentDynamicFieldAccessor {\n    private static final Logger logger = EaseAgent.loggerFactory.getLogger(AgentDynamicFieldAccessor.class);\n\n    public static final String DYNAMIC_FIELD_NAME = \"ease_agent_dynamic_$$$_data\";\n\n    @SuppressWarnings(\"unchecked\")\n    public static <T> T getDynamicFieldValue(Object target) {\n        if (!(target instanceof DynamicFieldAccessor)) {\n            logger.warn(target.getClass().getName() + \" must implements DynamicFieldAccessor\");\n            return null;\n        }\n        return (T) ((DynamicFieldAccessor) target).getEaseAgent$$DynamicField$$Data();\n    }\n\n    public static void setDynamicFieldValue(Object target, Object value) {\n        if (!(target instanceof DynamicFieldAccessor)) {\n            logger.warn(target.getClass().getName() + \" must implements DynamicFieldAccessor\");\n            return;\n        }\n        ((DynamicFieldAccessor) target).setEaseAgent$$DynamicField$$Data(value);\n    }\n\n    public static Field getDynamicFieldFromClass(Class<?> clazz) {\n        return AgentFieldReflectAccessor.getFieldFromClass(clazz, DYNAMIC_FIELD_NAME);\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/field/AgentFieldReflectAccessor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.field;\n\nimport java.lang.reflect.Field;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\n\npublic class AgentFieldReflectAccessor {\n    private static final Map<String, Field> FIELD_MAP = new ConcurrentHashMap<>();\n\n    public static void setFieldValue(Object target, String fieldName, Object fieldValue) {\n        Field field = getFieldFromClass(target.getClass(), fieldName);\n        try {\n            field.set(target, fieldValue);\n        } catch (IllegalAccessException ignored) {\n        }\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    public static <T> T getFieldValue(Object target, String fieldName) {\n        Field field = getFieldFromClass(target.getClass(), fieldName);\n        if (field == null) {\n            return null;\n        }\n        try {\n            return (T) field.get(target);\n        } catch (IllegalAccessException ignored) {\n        }\n        return null;\n    }\n\n    public static void setStaticFieldValue(Class<?> clazz, String fieldName, Object fieldValue) {\n        Field field = getFieldFromClass(clazz, fieldName);\n        try {\n            field.set(null, fieldValue);\n        } catch (IllegalAccessException ignored) {\n        }\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    public static <T> T getStaticFieldValue(Class<?> clazz, String fieldName) {\n        Field field = getFieldFromClass(clazz, fieldName);\n        if (field == null) {\n            return null;\n        }\n        try {\n            return (T) field.get(null);\n        } catch (IllegalAccessException ignored) {\n        }\n        return null;\n    }\n\n    public static Field getFieldFromClass(Class<?> clazz, String fieldName) {\n        String key = clazz.getName() + \".\" + fieldName;\n        Field field = FIELD_MAP.get(key);\n        if (field != null) {\n            return field;\n        }\n        field = innerGetFieldFromClass(clazz, fieldName);\n        if (field != null) {\n            FIELD_MAP.put(key, field);\n        }\n        return field;\n    }\n\n    public static Field innerGetFieldFromClass(Class<?> clazz, String fieldName) {\n        try {\n            Field field = clazz.getDeclaredField(fieldName);\n            field.setAccessible(true);\n            return field;\n        } catch (NoSuchFieldException ignored) {\n            Class<?> superclass = clazz.getSuperclass();\n            if (superclass.equals(Object.class)) {\n                return null;\n            }\n            return innerGetFieldFromClass(superclass, fieldName);\n        }\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/field/DynamicFieldAccessor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.field;\n\npublic interface DynamicFieldAccessor {\n\n    void setEaseAgent$$DynamicField$$Data(Object data);\n\n    Object getEaseAgent$$DynamicField$$Data();\n\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/field/NullObject.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.field;\n\n/**\n * default value for Agent Dynamic Field,\n * avoiding NullPointerException when serialized\n */\npublic class NullObject {\n    public static final Object NULL = new Object();\n\n    public String toString() {\n        return \"null\";\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/field/TypeFieldGetter.java",
    "content": "package com.megaease.easeagent.plugin.field;\n\npublic interface TypeFieldGetter {\n    <T> T getEaseAgent$$TypeField$$Data();\n\n    static <T> T get(Object o) {\n        if (o instanceof TypeFieldGetter) {\n            return ((TypeFieldGetter) o).getEaseAgent$$TypeField$$Data();\n        }\n        return null;\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/interceptor/AgentInterceptorChain.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.interceptor;\n\nimport com.megaease.easeagent.plugin.Ordered;\nimport com.megaease.easeagent.plugin.api.InitializeContext;\nimport com.megaease.easeagent.plugin.api.logging.Logger;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\n\nimport java.util.ArrayList;\nimport java.util.Comparator;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\npublic class AgentInterceptorChain {\n    private static Logger log = EaseAgent.loggerFactory.getLogger(AgentInterceptorChain.class);\n    public ArrayList<Interceptor> interceptors;\n\n    public AgentInterceptorChain(List<Interceptor> interceptors) {\n        this.interceptors = new ArrayList<>(interceptors);\n    }\n\n    public AgentInterceptorChain(ArrayList<Interceptor> interceptors) {\n        this.interceptors = interceptors;\n    }\n\n    public void doBefore(MethodInfo methodInfo, int pos, InitializeContext context) {\n        if (pos == this.interceptors.size()) {\n            return;\n        }\n        Interceptor interceptor = interceptors.get(pos);\n        try {\n            interceptor.before(methodInfo, context);\n        } catch (Throwable e) {\n            // set error message to context;\n            log.debug(\"Interceptor before execute exception:\", e);\n        }\n        this.doBefore(methodInfo, pos + 1, context);\n    }\n\n    public Object doAfter(MethodInfo methodInfo, int pos, InitializeContext context) {\n        if (pos < 0) {\n            return methodInfo.getRetValue();\n        }\n        Interceptor interceptor = interceptors.get(pos);\n        try {\n            interceptor.after(methodInfo, context);\n        } catch (Throwable e) {\n            // set error message to context;\n            log.debug(\"Interceptor exit execute exception:\", e);\n        }\n        return this.doAfter(methodInfo, pos - 1, context);\n    }\n\n    public void merge(AgentInterceptorChain other) {\n        if (other == null) {\n            return;\n        }\n        interceptors.addAll(other.interceptors);\n        this.interceptors = interceptors.stream()\n            .sorted(Comparator.comparing(Ordered::order))\n            .collect(Collectors.toCollection(ArrayList::new));\n    }\n\n    public int size() {\n        return this.interceptors.size();\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/interceptor/Interceptor.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.interceptor;\n\nimport com.megaease.easeagent.plugin.Ordered;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\n\npublic interface Interceptor extends Ordered {\n    /**\n     * @param methodInfo instrumented method info\n     * @param context    Interceptor can pass data, method `after` of interceptor can receive context data\n     */\n    void before(MethodInfo methodInfo, Context context);\n\n    /**\n     * @param methodInfo instrumented method info\n     * @param context    Interceptor can pass data, method `after` of interceptor can receive context data\n     */\n    default void after(MethodInfo methodInfo, Context context) {\n    };\n\n    /**\n     * Interceptor can get interceptor config thought Config API :\n     * EaseAgent.configFactory.getConfig\n     * Config API require 3 params: domain, nameSpace, name\n     * domain and namespace are defined by plugin, the third param, name is defined here\n     *\n     * @return name, eg. tracing, metric, etc.\n     */\n    default String getType() {\n        return Order.TRACING.getName();\n    }\n\n    /**\n     * Initialization method for the interceptor,\n     * This method will be called and only be called once for every method which is injected by this interceptor,\n     * which means this method may be called several times, when there are several methods matched\n     *\n     * @param config interceptor configuration\n     * @param className injected method's class name\n     * @param methodName injected method name\n     * @param methodDescriptor injected method descriptor\n     */\n    default void init(IPluginConfig config, String className, String methodName, String methodDescriptor) {\n    }\n\n    /**\n     * Initialization method for the interceptor,\n     * This method will be called and only be called once for every method which is injected by this interceptor,\n     * which means this method may be called several times, when there are several methods matched\n     *\n     * @param config    interceptor configuration\n     * @param uniqueIndex an Integer unique index generated by agent\n     *                    for each combination of className, method and methodDescriptor\n     */\n    default void init(IPluginConfig config, int uniqueIndex) {\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/interceptor/InterceptorProvider.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.interceptor;\n\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\n\nimport java.util.function.Supplier;\n\n/**\n * used in autogenerate code\n */\npublic interface InterceptorProvider {\n    Supplier<Interceptor> getInterceptorProvider();\n\n    String getAdviceTo();\n\n    String getPluginClassName();\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/interceptor/MethodInfo.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.interceptor;\n\nimport lombok.Builder;\n\nimport java.util.Objects;\n\n@Builder\npublic class MethodInfo {\n\n    /**\n     * The this reference of the instrumented method\n     */\n    private Object invoker;\n\n    /**\n     * instrumented type name\n     */\n    private String type;\n\n    /**\n     * instrumented method name\n     */\n    private String method;\n\n    /**\n     * The arguments of instrumented method. If no args exist,args=null\n     */\n    private Object[] args;\n\n    /**\n     * Throwable is existed if method throws exception. Otherwise, it is null.\n     */\n    private Throwable throwable;\n\n    /**\n     * The return value of instrumented method\n     */\n    private Object retValue;\n\n    private boolean changed;\n\n    public boolean isChanged() {\n        return changed;\n    }\n\n    public boolean isSuccess() {\n        return this.throwable == null;\n    }\n\n    public Object getInvoker() {\n        return this.invoker;\n    }\n\n    public String getType() {\n        return this.type;\n    }\n\n    public String getMethod() {\n        return this.method;\n    }\n\n    public Object[] getArgs() {\n        return this.args;\n    }\n\n    public int argSize() {\n        return this.args == null ? 0 : this.args.length;\n    }\n\n    public Throwable getThrowable() {\n        return this.throwable;\n    }\n\n    public Object getRetValue() {\n        return this.retValue;\n    }\n\n    public void setMethod(String method) {\n        this.method = method;\n    }\n\n    public void setType(String type) {\n        this.type = type;\n    }\n\n    public void setInvoker(Object invoker) {\n        this.invoker = invoker;\n        this.changed = true;\n    }\n\n    public void changeArg(int index, Object arg) {\n        this.args[index] = arg;\n        this.changed = true;\n    }\n\n    public void setArgs(Object[] args) {\n        this.args = args;\n        this.changed = true;\n    }\n\n    public void setThrowable(Throwable throwable) {\n        this.throwable = throwable;\n        this.changed = true;\n    }\n\n    public void setRetValue(Object retValue) {\n        this.retValue = retValue;\n        this.changed = true;\n    }\n\n    public void markChanged() {\n        this.changed = true;\n    }\n\n\n    public void throwable(Throwable throwable) {\n        this.throwable = throwable;\n    }\n\n    public void retValue(Object retValue) {\n        this.retValue = retValue;\n    }\n\n    public boolean equals(final Object o) {\n        if (o == this) {\n            return true;\n        }\n        if (!(o instanceof MethodInfo)) {\n            return false;\n        }\n        final MethodInfo other = (MethodInfo) o;\n        if (!Objects.equals(this.getInvoker(), other.getInvoker())) {\n            return false;\n        }\n\n        if (!Objects.equals(this.getType(), other.getType())) {\n            return false;\n        }\n\n        if (!Objects.equals(this.getMethod(), other.getMethod())) {\n            return false;\n        }\n        if (!java.util.Arrays.deepEquals(this.getArgs(), other.getArgs())) {\n            return false;\n        }\n        if (!Objects.equals(this.getThrowable(), other.getThrowable())) {\n            return false;\n        }\n\n        return Objects.equals(this.getRetValue(), other.getRetValue());\n    }\n\n    public int hashCode() {\n        final int PRIME = 59;\n        int result = 1;\n        final Object invoker = this.getInvoker();\n        result = result * PRIME + (invoker == null ? 43 : invoker.hashCode());\n        final Object method = this.getMethod();\n        result = result * PRIME + (method == null ? 43 : method.hashCode());\n        result = result * PRIME + java.util.Arrays.deepHashCode(this.getArgs());\n        final Object throwable = this.getThrowable();\n        result = result * PRIME + (throwable == null ? 43 : throwable.hashCode());\n        final Object retValue = this.getRetValue();\n        result = result * PRIME + (retValue == null ? 43 : retValue.hashCode());\n        return result;\n    }\n\n    public String toString() {\n        return \"MethodInfo(invoker=\" + this.getInvoker()\n            + \", type:\" + this.getType() + \", method=\" + this.getMethod()\n            + \", args=\" + java.util.Arrays.deepToString(this.getArgs())\n            + \", throwable=\" + this.getThrowable() + \", retValue=\" + this.getRetValue() + \")\";\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/interceptor/NonReentrantInterceptor.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.interceptor;\n\nimport com.megaease.easeagent.plugin.api.Context;\n\n/**\n *\n */\npublic interface NonReentrantInterceptor extends Interceptor {\n\n    @Override\n    default void before(MethodInfo methodInfo, Context context) {\n        if (!context.enter(getEnterKey(methodInfo, context), 1)) {\n            return;\n        }\n        doBefore(methodInfo, context);\n    }\n\n    @Override\n    default void after(MethodInfo methodInfo, Context context) {\n        Object key = getEnterKey(methodInfo, context);\n        if (!context.exit(key, 1)) {\n            return;\n        }\n        try {\n            context.enter(key);\n            doAfter(methodInfo, context);\n        } finally {\n            context.exit(key);\n        }\n    }\n\n    default Object getEnterKey(MethodInfo methodInfo, Context context) {\n        return this.getClass();\n    }\n\n    default void doBefore(MethodInfo methodInfo, Context context) {\n\n    }\n\n    default void doAfter(MethodInfo methodInfo, Context context) {\n\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/matcher/ClassMatcher.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.matcher;\n\nimport com.megaease.easeagent.plugin.asm.Modifier;\nimport com.megaease.easeagent.plugin.enums.ClassMatch;\nimport com.megaease.easeagent.plugin.enums.Operator;\nimport com.megaease.easeagent.plugin.matcher.operator.AndClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.operator.OrClassMatcher;\nimport lombok.Data;\n\n@Data\n@SuppressWarnings(\"unused\")\npublic class ClassMatcher implements IClassMatcher {\n    private String name;\n    private ClassMatch matchType;\n    private int modifier = Modifier.ACC_NONE;\n    private int notModifier = Modifier.ACC_NONE;\n\n    public static final int MODIFIER_MASK = Modifier.ACC_ABSTRACT | Modifier.ACC_INTERFACE\n        | Modifier.ACC_PRIVATE | Modifier.ACC_PUBLIC | Modifier.ACC_PROTECTED;\n\n    protected ClassMatcher() {\n    }\n\n    private ClassMatcher(String name, ClassMatch type, int modifier, int notModifier) {\n        this.name = name;\n        this.matchType = type;\n        this.modifier = modifier;\n        this.notModifier = notModifier;\n    }\n\n    public static ClassMatcherBuilder builder() {\n        return new ClassMatcherBuilder();\n    }\n\n    public static class ClassMatcherBuilder {\n        private String name;\n        private ClassMatch matchType;\n        private int modifier;\n        private int notModifier;\n\n        private IClassMatcher left;\n        private Operator operator = Operator.AND;\n        private boolean  isNegate = false;\n\n        ClassMatcherBuilder() {\n        }\n\n        public ClassMatcherBuilder or() {\n            return operate(Operator.OR);\n        }\n\n        public ClassMatcherBuilder and() {\n            return operate(Operator.AND);\n        }\n\n        private ClassMatcherBuilder operate(Operator opt) {\n            ClassMatcherBuilder builder = new ClassMatcherBuilder();\n            builder.left = this.build();\n            builder.operator = opt;\n\n            return builder;\n        }\n\n        public ClassMatcherBuilder negate() {\n            this.isNegate = true;\n            return this;\n        }\n\n        public ClassMatcherBuilder hasSuperClass(String className) {\n            if (this.name != null && this.name.length() > 0) {\n                if (this.matchType.equals(ClassMatch.SUPER_CLASS)) {\n                    // replace\n                    return this.name(className).matchType(ClassMatch.SUPER_CLASS);\n                } else {\n                    // and operate\n                    ClassMatcherBuilder builder = new ClassMatcherBuilder();\n                    builder.hasSuperClass(className).matchType(ClassMatch.SUPER_CLASS);\n                    builder.left = this.build();\n                    builder.operator = Operator.AND;\n                    return builder;\n                }\n            }\n            return this.name(className).matchType(ClassMatch.SUPER_CLASS);\n        }\n\n        public ClassMatcherBuilder hasClassName(String className) {\n            return this.name(className).matchType(ClassMatch.NAMED);\n        }\n\n        public ClassMatcherBuilder hasAnnotation(String className) {\n            if (this.name != null && this.name.length() > 0) {\n                // and operate\n                ClassMatcherBuilder builder = new ClassMatcherBuilder();\n                builder.hasSuperClass(className).matchType(ClassMatch.ANNOTATION);\n                builder.left = this.build();\n                builder.operator = Operator.AND;\n                return builder;\n            }\n            return this.name(className).matchType(ClassMatch.ANNOTATION);\n        }\n\n        public ClassMatcherBuilder hasInterface(String className) {\n            if (this.name != null && this.name.length() > 0) {\n                // and operate\n                ClassMatcherBuilder builder = new ClassMatcherBuilder();\n                builder.hasSuperClass(className).matchType(ClassMatch.INTERFACE);\n                builder.left = this.build();\n                builder.operator = Operator.AND;\n                return builder;\n            }\n            return this.name(className).matchType(ClassMatch.INTERFACE);\n        }\n\n        public ClassMatcherBuilder matchType(ClassMatch matchType) {\n            this.matchType = matchType;\n            return this;\n        }\n\n        public ClassMatcherBuilder modifier(int modifier) {\n            this.modifier = modifier;\n            return this;\n        }\n\n        public ClassMatcherBuilder notModifier(int notModifier) {\n            this.notModifier = notModifier;\n            return this;\n        }\n\n        public ClassMatcherBuilder isPublic() {\n            this.modifier |= Modifier.ACC_PUBLIC;\n            return this;\n        }\n\n        public ClassMatcherBuilder isPrivate() {\n            this.modifier |= Modifier.ACC_PRIVATE;\n            return this;\n        }\n\n        public ClassMatcherBuilder isAbstract() {\n            this.modifier |= Modifier.ACC_ABSTRACT;\n            return this;\n        }\n\n        public ClassMatcherBuilder isInterface() {\n            this.modifier |= Modifier.ACC_INTERFACE;\n            return this;\n        }\n\n        public ClassMatcherBuilder notPrivate() {\n            this.notModifier |= Modifier.ACC_PRIVATE;\n            return this;\n        }\n\n        public ClassMatcherBuilder notAbstract() {\n            this.notModifier |= Modifier.ACC_ABSTRACT;\n            return this;\n        }\n\n        public ClassMatcherBuilder notInterface() {\n            this.notModifier |= Modifier.ACC_INTERFACE;\n            return this;\n        }\n\n        protected ClassMatcherBuilder name(String name) {\n            this.name = name;\n            return this;\n        }\n\n        public IClassMatcher build() {\n            IClassMatcher matcher = new ClassMatcher(name, matchType, modifier, notModifier);\n\n            if (this.isNegate) {\n                matcher = matcher.negate();\n            }\n\n            if (this.left == null || this.operator == null) {\n                return matcher;\n            }\n\n            IClassMatcher points;\n            switch (this.operator) {\n                case OR:\n                    return new OrClassMatcher(this.left, matcher);\n                case AND:\n                    return new AndClassMatcher(this.left, matcher);\n                default:\n                    return matcher;\n            }\n        }\n\n        public String toString() {\n            return \"ClassMatcher.ClassMatcherBuilder(name=\" + this.name\n                + \", matchType=\" + this.matchType\n                + \", modifier=\" + this.modifier\n                + \", notModifier=\" + this.notModifier\n                + \", isNegate=\" + this.isNegate\n                + \", operate=\" + this.operator\n                + \", left=\" + this.left + \")\";\n        }\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/matcher/IClassMatcher.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.matcher;\n\nimport com.megaease.easeagent.plugin.matcher.operator.AndClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.operator.NegateClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.operator.Operator;\nimport com.megaease.easeagent.plugin.matcher.operator.OrClassMatcher;\n\npublic interface IClassMatcher extends Operator<IClassMatcher>, Matcher {\n    default IClassMatcher and(IClassMatcher m) {\n        return new AndClassMatcher(this, m);\n    }\n\n    default IClassMatcher or(IClassMatcher m) {\n        return new OrClassMatcher(this, m);\n    }\n\n    default IClassMatcher negate() {\n        return new NegateClassMatcher(this);\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/matcher/IMethodMatcher.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.matcher;\n\nimport com.megaease.easeagent.plugin.matcher.operator.AndMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.operator.NegateMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.operator.Operator;\nimport com.megaease.easeagent.plugin.matcher.operator.OrMethodMatcher;\n\nimport java.util.HashSet;\nimport java.util.Set;\n\npublic interface IMethodMatcher extends Operator<IMethodMatcher>, Matcher {\n    String DEFAULT_QUALIFIER = \"default\";\n\n    String getQualifier();\n\n    default boolean isDefaultQualifier() {\n        return this.getQualifier().equals(DEFAULT_QUALIFIER);\n    }\n\n    default Set<IMethodMatcher> toSet() {\n        Set<IMethodMatcher> set = new HashSet<>();\n        set.add(this);\n        return set;\n    }\n\n    default IMethodMatcher and(IMethodMatcher other) {\n        return new AndMethodMatcher(this, other);\n    }\n\n    default IMethodMatcher or(IMethodMatcher other) {\n        return new OrMethodMatcher(this, other);\n    }\n\n    default IMethodMatcher negate() {\n        return new NegateMethodMatcher(this);\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/matcher/Matcher.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.matcher;\n\npublic interface Matcher {\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/matcher/MethodMatcher.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.matcher;\n\nimport com.megaease.easeagent.plugin.asm.Modifier;\nimport com.megaease.easeagent.plugin.enums.Operator;\nimport com.megaease.easeagent.plugin.enums.StringMatch;\nimport com.megaease.easeagent.plugin.matcher.operator.AndMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.operator.OrMethodMatcher;\nimport lombok.Data;\n\nimport java.util.Arrays;\nimport java.util.LinkedHashSet;\nimport java.util.Set;\n\n@Data\n@SuppressWarnings(\"unused\")\npublic class MethodMatcher implements IMethodMatcher {\n    // method name\n    private String name;\n\n    // the match type of method name: equals, startWith...\n    private StringMatch nameMatchType;\n\n    // ignored when with default value\n    private String returnType = null;\n    // types of method arguments\n    private String[] args;\n    private int argsLength = -1;\n    private int modifier = Modifier.ACC_NONE;\n    private int notModifier = Modifier.ACC_NONE;\n\n    private IClassMatcher overriddenFrom = null;\n\n    private String qualifier;\n\n    public static final int MODIFIER_MASK = Modifier.ACC_ABSTRACT | Modifier.ACC_STATIC\n        | Modifier.ACC_PRIVATE | Modifier.ACC_PUBLIC | Modifier.ACC_PROTECTED;\n\n    protected MethodMatcher() {\n    }\n\n    private MethodMatcher(String name, StringMatch type, String returnType,\n                          String[] args, int argLength, int modifier,\n                          int notModifier, String qualifier, IClassMatcher overriddenFrom) {\n        this.name = name;\n        this.nameMatchType = type;\n        this.returnType = returnType;\n        this.args = args;\n        this.argsLength = argLength;\n        this.modifier = modifier;\n        this.notModifier = notModifier;\n        this.qualifier = qualifier;\n        this.overriddenFrom = overriddenFrom;\n    }\n\n    @Override\n    public boolean isDefaultQualifier() {\n        return this.qualifier.equals(IMethodMatcher.DEFAULT_QUALIFIER);\n    }\n\n    public static MethodMatcherBuilder builder() {\n        return new MethodMatcherBuilder();\n    }\n\n    public static class MethodMatcherBuilder {\n        private String name;\n        private StringMatch nameMatchType;\n        private String returnType;\n        private String[] args;\n        private int argsLength = -1;\n        private int modifier;\n        private int notModifier;\n\n        private IClassMatcher isOverriddenFrom;\n        private String qualifier = IMethodMatcher.DEFAULT_QUALIFIER;\n\n        private Operator operator = Operator.AND;\n        private boolean  isNegate = false;\n\n        private IMethodMatcher left;\n\n        MethodMatcherBuilder() {\n        }\n\n        public MethodMatcherBuilder or() {\n            return operate(Operator.OR);\n        }\n\n        public MethodMatcherBuilder and() {\n            return operate(Operator.AND);\n        }\n\n        public MethodMatcherBuilder negate() {\n            this.isNegate = true;\n            return this;\n        }\n\n        public MethodMatcherBuilder named(String methodName) {\n            if (methodName.equals(\"<init>\")) {\n                return isConstruct();\n            }\n            return this.name(methodName).nameMatchType(StringMatch.EQUALS);\n        }\n\n        public MethodMatcherBuilder isConstruct() {\n            return this.name(\"<init>\").nameMatchType(StringMatch.EQUALS);\n        }\n\n        public MethodMatcherBuilder nameStartWith(String methodName) {\n            return this.name(methodName).nameMatchType(StringMatch.START_WITH);\n        }\n\n        public MethodMatcherBuilder nameEndWith(String methodName) {\n            return this.name(methodName).nameMatchType(StringMatch.END_WITH);\n        }\n\n        public MethodMatcherBuilder nameContains(String methodName) {\n            return this.name(methodName).nameMatchType(StringMatch.CONTAINS);\n        }\n\n        public MethodMatcherBuilder isPublic() {\n            this.modifier |= Modifier.ACC_PUBLIC;\n            return this;\n        }\n\n        public MethodMatcherBuilder isPrivate() {\n            this.modifier |= Modifier.ACC_PRIVATE;\n            return this;\n        }\n\n        public MethodMatcherBuilder isAbstract() {\n            this.modifier |= Modifier.ACC_ABSTRACT;\n            return this;\n        }\n\n        public MethodMatcherBuilder isStatic() {\n            this.modifier |= Modifier.ACC_STATIC;\n            return this;\n        }\n\n        public MethodMatcherBuilder notPublic() {\n            this.notModifier |= Modifier.ACC_PUBLIC;\n            return this;\n        }\n\n        public MethodMatcherBuilder notPrivate() {\n            this.notModifier |= Modifier.ACC_PRIVATE;\n            return this;\n        }\n\n        public MethodMatcherBuilder notAbstract() {\n            this.notModifier |= Modifier.ACC_ABSTRACT;\n            return this;\n        }\n\n        public MethodMatcherBuilder notStatic() {\n            this.notModifier|= Modifier.ACC_STATIC;\n            return this;\n        }\n\n        protected MethodMatcherBuilder name(String name) {\n            this.name = name;\n            return this;\n        }\n\n        public MethodMatcherBuilder nameMatchType(StringMatch nameMatchType) {\n            this.nameMatchType = nameMatchType;\n            return this;\n        }\n\n        public MethodMatcherBuilder returnType(String returnType) {\n            this.returnType = returnType;\n            return this;\n        }\n\n        public MethodMatcherBuilder args(String[] args) {\n            this.args = args;\n            return this;\n        }\n\n        public MethodMatcherBuilder arg(int idx, String argType) {\n            if (args == null || args.length < 4) {\n                this.args = new String[idx > 4 ? idx + 1 : 5];\n            } else if (this.args.length < idx + 1) {\n                this.args = Arrays.copyOf(this.args, idx + 1);\n            }\n            this.args[idx] = argType;\n\n            return this;\n        }\n\n        public MethodMatcherBuilder argsLength(int length) {\n            this.argsLength = length;\n\n            if (length <= 0) {\n                this.args = null;\n            } else if (this.args == null) {\n                this.args = new String[length];\n            } else if (this.args.length < length) {\n                this.args = Arrays.copyOf(this.args, length);\n            }\n\n            return this;\n        }\n\n        public MethodMatcherBuilder modifier(int modifier) {\n            this.modifier = modifier;\n            return this;\n        }\n\n        public MethodMatcherBuilder qualifier(String qualifier) {\n            // each builder can only assigned a qualifier\n            if (!this.qualifier.equals(IMethodMatcher.DEFAULT_QUALIFIER)) {\n                throw new RuntimeException(\"Qualifier has already been assigned\");\n            }\n            this.qualifier = qualifier;\n            return this;\n        }\n\n        public MethodMatcherBuilder isOverriddenFrom(IClassMatcher cMatcher) {\n            this.isOverriddenFrom = cMatcher;\n            return this;\n        }\n\n        public IMethodMatcher build() {\n            IMethodMatcher matcher = new MethodMatcher(name, nameMatchType, returnType,\n                args, argsLength, modifier, notModifier, qualifier, isOverriddenFrom);\n\n            if (this.isNegate) {\n                matcher = matcher.negate();\n            }\n\n            if (this.left == null || this.operator == null) {\n                return matcher;\n            }\n            switch (this.operator) {\n                case OR:\n                    return new OrMethodMatcher(this.left, matcher);\n                case AND:\n                    return new AndMethodMatcher(this.left, matcher);\n                default:\n                    return matcher;\n            }\n        }\n\n        private MethodMatcherBuilder operate(Operator opt) {\n            MethodMatcherBuilder builder = new MethodMatcherBuilder();\n            builder.left = this.build();\n            builder.operator = opt;\n\n            if (!builder.left.isDefaultQualifier()) {\n                builder.qualifier(builder.left.getQualifier());\n            }\n            return builder;\n        }\n\n        public String toString() {\n            return \"MethodMatcher.MethodMatcherBuilder(name=\" + this.name\n                + \", nameMatchType=\" + this.nameMatchType + \", returnType=\" + this.returnType\n                + \", args=\" + Arrays.deepToString(this.args) + \", argsLength=\" + this.argsLength\n                + \", modifier=\" + this.modifier\n                + \", notModifier=\" + this.notModifier\n                + \", operator=\" + this.operator\n                + \", isNegate=\" + this.isNegate\n                + \", left=\" + this.left\n                + \")\";\n        }\n    }\n\n    public static MethodMatchersBuilder multiBuilder() {\n        return new MethodMatchersBuilder();\n    }\n\n    public static class MethodMatchersBuilder {\n        private Set<IMethodMatcher> methodMatchers;\n\n        MethodMatchersBuilder() {\n        }\n\n        public MethodMatchersBuilder methodMatchers(Set<IMethodMatcher> methodMatchers) {\n            this.methodMatchers = methodMatchers;\n            return this;\n        }\n\n        public MethodMatchersBuilder match(IMethodMatcher matcher) {\n            if (matcher == null) {\n                return this;\n            }\n            if (this.methodMatchers == null) {\n                this.methodMatchers = new LinkedHashSet<>();\n            }\n            this.methodMatchers.add(matcher);\n            return this;\n        }\n\n        public Set<IMethodMatcher> build() {\n            return this.methodMatchers;\n        }\n\n        public String toString() {\n            return \"MethodMatchers.MethodMatchersBuilder(methodMatchers=\" + this.methodMatchers + \")\";\n        }\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/matcher/loader/ClassLoaderMatcher.java",
    "content": "/*\n * Copyright (c) 2022, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.megaease.easeagent.plugin.matcher.loader;\n\nimport com.megaease.easeagent.plugin.utils.common.StringUtils;\n\n@SuppressWarnings(\"unused\")\npublic class ClassLoaderMatcher implements IClassLoaderMatcher {\n    public static final String BOOTSTRAP_NAME = \"bootstrap\";\n    public static final String EXTERNAL_NAME = \"external\";\n    public static final String SYSTEM_NAME = \"system\";\n    public static final String AGENT_NAME = \"agent\";\n\n    // predefined classloader name and matcher\n    public static final ClassLoaderMatcher ALL = new ClassLoaderMatcher(\"all\");\n    public static final ClassLoaderMatcher BOOTSTRAP = new ClassLoaderMatcher(BOOTSTRAP_NAME);\n    public static final ClassLoaderMatcher EXTERNAL = new ClassLoaderMatcher(EXTERNAL_NAME);\n    public static final ClassLoaderMatcher SYSTEM = new ClassLoaderMatcher(SYSTEM_NAME);\n    public static final ClassLoaderMatcher AGENT = new ClassLoaderMatcher(AGENT_NAME);\n\n    // classloader class name or Predefined names\n    String classLoaderName;\n\n    public ClassLoaderMatcher(String loaderName) {\n        if (StringUtils.isEmpty(loaderName)) {\n            this.classLoaderName = BOOTSTRAP_NAME;\n        } else {\n            this.classLoaderName = loaderName;\n        }\n    }\n\n    @Override\n    public String getClassLoaderName() {\n        return this.classLoaderName;\n    }\n\n    @Override\n    public IClassLoaderMatcher negate() {\n        return new NegateClassLoaderMatcher(this);\n    }\n\n    @Override\n    public int hashCode() {\n        return this.classLoaderName.hashCode();\n    }\n\n    public boolean equals(Object o) {\n        if (!(o instanceof ClassLoaderMatcher)) {\n            return false;\n        }\n        return this.classLoaderName.equals(((ClassLoaderMatcher) o).classLoaderName);\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/matcher/loader/IClassLoaderMatcher.java",
    "content": "/*\n * Copyright (c) 2022, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.megaease.easeagent.plugin.matcher.loader;\n\nimport com.megaease.easeagent.plugin.matcher.Matcher;\n\npublic interface IClassLoaderMatcher extends Matcher {\n    String getClassLoaderName();\n\n    IClassLoaderMatcher negate();\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/matcher/loader/NegateClassLoaderMatcher.java",
    "content": "/*\n * Copyright (c) 2022, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.plugin.matcher.loader;\n\npublic class NegateClassLoaderMatcher implements IClassLoaderMatcher {\n    IClassLoaderMatcher matcher;\n\n    public NegateClassLoaderMatcher(IClassLoaderMatcher matcher) {\n        this.matcher = matcher;\n    }\n\n    @Override\n    public String getClassLoaderName() {\n        return this.matcher.getClassLoaderName();\n    }\n\n    @Override\n    public IClassLoaderMatcher negate() {\n        return this.matcher;\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/matcher/operator/AndClassMatcher.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.matcher.operator;\n\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport lombok.Getter;\n\n@Getter\npublic class AndClassMatcher implements IClassMatcher {\n    protected IClassMatcher left;\n    protected IClassMatcher right;\n\n    public AndClassMatcher(IClassMatcher left, IClassMatcher right) {\n        this.left = left;\n        this.right = right;\n    }\n}\n\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/matcher/operator/AndMethodMatcher.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.matcher.operator;\n\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport lombok.Getter;\n\n@Getter\npublic class AndMethodMatcher implements IMethodMatcher {\n    private String qualifier = DEFAULT_QUALIFIER;\n    protected IMethodMatcher left;\n    protected IMethodMatcher right;\n\n    public AndMethodMatcher(IMethodMatcher left, IMethodMatcher right) {\n        this.left = left;\n        this.right = right;\n\n        this.qualifier(left.getQualifier());\n        this.qualifier(right.getQualifier());\n    }\n\n    public IMethodMatcher qualifier(String q) {\n        if (this.qualifier.equals(DEFAULT_QUALIFIER)) {\n            this.qualifier = q;\n        }\n\n        return this;\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/matcher/operator/NegateClassMatcher.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.matcher.operator;\n\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport lombok.Getter;\n\n@Getter\npublic class NegateClassMatcher implements IClassMatcher {\n    protected IClassMatcher matcher;\n\n    public NegateClassMatcher(IClassMatcher matcher) {\n        this.matcher = matcher;\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/matcher/operator/NegateMethodMatcher.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.matcher.operator;\n\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport lombok.Getter;\n\n@Getter\npublic class NegateMethodMatcher implements IMethodMatcher {\n    private String qualifier = DEFAULT_QUALIFIER;\n    protected IMethodMatcher matcher;\n\n    public NegateMethodMatcher(IMethodMatcher matcher) {\n        this.matcher = matcher;\n        this.qualifier(matcher.getQualifier());\n    }\n\n    public IMethodMatcher qualifier(String q) {\n        this.qualifier = q;\n        return this;\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/matcher/operator/Operator.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.matcher.operator;\n\npublic interface Operator<S> {\n    S and(S matcher);\n    S or(S matcher);\n    S negate();\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/matcher/operator/OrClassMatcher.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.matcher.operator;\n\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport lombok.Getter;\n\n@Getter\npublic class OrClassMatcher implements IClassMatcher {\n    protected IClassMatcher left;\n    protected IClassMatcher right;\n\n    public OrClassMatcher(IClassMatcher left, IClassMatcher right) {\n        this.left = left;\n        this.right = right;\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/matcher/operator/OrMethodMatcher.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.matcher.operator;\n\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport lombok.Getter;\n\n@Getter\npublic class OrMethodMatcher implements IMethodMatcher {\n    private String qualifier = DEFAULT_QUALIFIER;\n    protected IMethodMatcher left;\n    protected IMethodMatcher right;\n\n    public OrMethodMatcher(IMethodMatcher left, IMethodMatcher right) {\n        this.left = left;\n        this.right = right;\n\n        this.qualifier(left.getQualifier());\n        this.qualifier(right.getQualifier());\n    }\n\n    public IMethodMatcher qualifier(String q) {\n        if (this.qualifier.equals(DEFAULT_QUALIFIER)) {\n            this.qualifier = q;\n        }\n        return this;\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/processor/BeanUtils.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.processor;\n\nimport com.squareup.javapoet.ClassName;\n\nimport javax.annotation.processing.ProcessingEnvironment;\nimport javax.lang.model.element.Element;\nimport javax.lang.model.element.TypeElement;\nimport javax.lang.model.type.MirroredTypeException;\nimport javax.lang.model.type.TypeMirror;\nimport javax.lang.model.util.Elements;\nimport javax.lang.model.util.Types;\nimport java.util.function.Supplier;\n\nabstract class BeanUtils {\n    static BeanUtils of(final ProcessingEnvironment pe) {\n        return new BeanUtils(pe) {\n        };\n    }\n\n    private final Elements elements;\n    private final Types types;\n\n    private BeanUtils(ProcessingEnvironment pe) {\n        elements = pe.getElementUtils();\n        types = pe.getTypeUtils();\n    }\n\n    Element asElement(TypeMirror t) {\n        return types.asElement(t);\n    }\n\n    ClassName classNameOf(TypeElement e) {\n        return ClassName.get(e);\n    }\n\n    String packageNameOf(TypeElement e) {\n        return ClassName.get(e).packageName();\n    }\n\n    TypeElement asTypeElement(Supplier<Class<?>> supplier) {\n        try {\n            Class<?> clazz = supplier.get();\n            return getTypeElement(clazz.getCanonicalName());\n        } catch (MirroredTypeException e) {\n            return (TypeElement) asElement(e.getTypeMirror());\n        }\n    }\n\n    boolean isSameType(TypeMirror t, String canonical) {\n        return isSameType(t, getTypeElement(canonical).asType());\n    }\n\n    boolean isSameType(TypeMirror t1, TypeMirror t2) {\n        return types.isSameType(t1, t2);\n    }\n\n    boolean isAssignable(TypeElement t1, TypeElement t2) {\n        TypeMirror tm1 = t1.asType();\n        TypeMirror tm2 = t2.asType();\n        return types.isAssignable(tm1, tm2);\n    }\n\n    public TypeElement getTypeElement(CharSequence name) {\n        return elements.getTypeElement(name);\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/processor/ElementVisitor8.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.processor;\n\nimport javax.lang.model.element.Element;\nimport javax.lang.model.element.PackageElement;\nimport javax.lang.model.element.TypeElement;\nimport javax.lang.model.util.SimpleElementVisitor8;\n\npublic class ElementVisitor8 extends SimpleElementVisitor8<TypeElement, TypeElement> {\n    private final BeanUtils utils;\n    public ElementVisitor8(BeanUtils utils) {\n        this.utils = utils;\n    }\n    @Override\n    public TypeElement visitPackage(PackageElement packageElement, TypeElement p) {\n        return null;\n    }\n\n    @Override\n    public TypeElement visitType(TypeElement enclosingClass, TypeElement p) {\n        if (utils.isAssignable(enclosingClass, p)) {\n            return enclosingClass;\n        }\n        return null;\n    }\n\n    @Override\n    public TypeElement visitUnknown(Element unknown, TypeElement p) {\n        return null;\n    }\n\n    @Override\n    public TypeElement defaultAction(Element enclosingElement, TypeElement p) {\n        throw new IllegalArgumentException(\"Unexpected type nesting: \" + p);\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/processor/GenerateProviderBean.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.processor;\n\nimport com.megaease.easeagent.plugin.interceptor.InterceptorProvider;\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.squareup.javapoet.JavaFile;\nimport com.squareup.javapoet.MethodSpec;\nimport com.squareup.javapoet.ParameterizedTypeName;\nimport com.squareup.javapoet.TypeSpec;\n\nimport javax.lang.model.element.Element;\nimport javax.lang.model.element.ExecutableElement;\nimport javax.lang.model.element.Modifier;\nimport javax.lang.model.element.TypeElement;\nimport java.util.LinkedHashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.function.Supplier;\n\nclass GenerateProviderBean {\n    private final String packageName;\n    private final String interceptorClass;\n    private final String pluginClass;\n    private final String point;\n    private final String qualifier;\n    private final String providerClassExtension;\n    private final TypeElement interceptor;\n    private final BeanUtils utils;\n\n    GenerateProviderBean(TypeElement plugin, TypeElement interceptor,\n                         Map<String, String> to,\n                         BeanUtils utils) {\n        this.interceptor = interceptor;\n        this.packageName = utils.packageNameOf(interceptor);\n        this.pluginClass = utils.classNameOf(plugin).canonicalName();\n        this.point = to.get(\"value\");\n        this.qualifier = to.get(\"qualifier\") == null ? \"default\" : to.get(\"qualifier\");\n        this.providerClassExtension = \"$Provider\" + to.get(\"seq\");\n        this.interceptorClass = utils.classNameOf(interceptor).simpleName();\n        this.utils = utils;\n    }\n\n    public String getProviderClass() {\n        return utils.classNameOf(interceptor).canonicalName()  + this.providerClassExtension;\n    }\n\n    JavaFile apply() {\n        List<? extends Element> executes = utils.asTypeElement(() -> InterceptorProvider.class).getEnclosedElements();\n\n        Set<MethodSpec> methods = new LinkedHashSet<>();\n        for (Element e : executes) {\n            ExecutableElement pExecute = (ExecutableElement)e;\n            if (pExecute.toString().startsWith(\"getInterceptorProvider\")) {\n                // getInterceptorProvider method generate\n                ParameterizedTypeName returnType = ParameterizedTypeName.get(Supplier.class, Interceptor.class);\n                // final MethodSpec getInterceptorProvider = MethodSpec.methodBuilder(\"getInterceptorProvider\")\n                final MethodSpec getInterceptorProvider = MethodSpec.overriding(pExecute)\n                    .addModifiers(Modifier.PUBLIC)\n                    .returns(returnType)\n                    .addCode(\"return \" + this.interceptorClass + \"::new;\")\n                    .build();\n                methods.add(getInterceptorProvider);\n            } else if (pExecute.toString().startsWith(\"getAdviceTo\")) {\n                // getAdviceTo method generate\n                final MethodSpec getAdviceTo = MethodSpec.overriding(pExecute)\n                    .addModifiers(Modifier.PUBLIC)\n                    .returns(String.class)\n                    .addStatement(\"return \\\"$L\\\" + \\\"$L\\\" + \\\"$L\\\"\", this.point, \":\", this.qualifier)\n                    .build();\n                methods.add(getAdviceTo);\n            } else if (pExecute.toString().startsWith(\"getPluginClassName\")) {\n                // getPluginClassName method generate\n                final MethodSpec getPluginClassName = MethodSpec.overriding(pExecute)\n                    .addModifiers(Modifier.PUBLIC)\n                    .returns(String.class)\n                    .addStatement(\"return \\\"$L\\\"\", this.pluginClass)\n                    .build();\n                methods.add(getPluginClassName);\n            }\n        }\n\n        final TypeSpec.Builder specBuild = TypeSpec\n            .classBuilder(this.interceptorClass + this.providerClassExtension)\n            .addSuperinterface(InterceptorProvider.class)\n            .addModifiers(Modifier.PUBLIC);\n        for (MethodSpec method : methods) {\n            specBuild.addMethod(method);\n        }\n\n        final TypeSpec spec = specBuild.build();\n\n        return JavaFile.builder(packageName, spec).build();\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/processor/PluginProcessor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.processor;\n\nimport com.google.auto.service.AutoService;\nimport com.megaease.easeagent.plugin.AgentPlugin;\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.interceptor.InterceptorProvider;\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.annotation.AdvicesTo;\nimport com.squareup.javapoet.ClassName;\nimport com.squareup.javapoet.JavaFile;\n\nimport javax.annotation.processing.AbstractProcessor;\nimport javax.annotation.processing.Filer;\nimport javax.annotation.processing.Processor;\nimport javax.annotation.processing.RoundEnvironment;\nimport javax.lang.model.SourceVersion;\nimport javax.lang.model.element.*;\nimport javax.lang.model.util.Elements;\nimport javax.tools.Diagnostic;\nimport javax.tools.Diagnostic.Kind;\nimport javax.tools.FileObject;\nimport javax.tools.StandardLocation;\nimport java.io.IOException;\nimport java.io.OutputStreamWriter;\nimport java.io.PrintWriter;\nimport java.lang.annotation.Annotation;\nimport java.nio.charset.StandardCharsets;\nimport java.util.*;\n\nimport static java.util.Objects.isNull;\nimport static javax.lang.model.element.Modifier.ABSTRACT;\n\n@AutoService(Processor.class)\npublic class PluginProcessor extends AbstractProcessor {\n    TreeSet<String> processAnnotations = new TreeSet<>();\n\n    public PluginProcessor() {\n        super();\n        processAnnotations.add(AdviceTo.class.getCanonicalName());\n        processAnnotations.add(AdvicesTo.class.getCanonicalName());\n    }\n\n    @Override\n    public SourceVersion getSupportedSourceVersion() {\n        return SourceVersion.latestSupported();\n    }\n\n    @Override\n    public Set<String> getSupportedAnnotationTypes() {\n        return processAnnotations;\n    }\n\n    private Set<TypeElement> process(Set<Class<? extends Annotation>> annotationClasses,\n                            Elements elements,\n                            RoundEnvironment roundEnv) {\n        TreeSet<String> services = new TreeSet<>();\n        Set<TypeElement> types = new HashSet<>();\n        Class<?> dstClass = Interceptor.class;\n\n        Set<Element> roundElements = new HashSet<>();\n        for (Class<? extends Annotation> annotationClass : annotationClasses) {\n            Set<? extends Element> es = roundEnv.getElementsAnnotatedWith(annotationClass);\n            roundElements.addAll(es);\n        }\n\n        for (Element e : roundElements) {\n            if (!e.getKind().isClass() || e.getModifiers().contains(ABSTRACT)) {\n                continue;\n            }\n            TypeElement type = (TypeElement)e;\n            types.add(type);\n            services.add(elements.getBinaryName(type).toString());\n        }\n        if (services.isEmpty()) {\n            return types;\n        }\n        writeToMetaInf(dstClass, services);\n\n        return types;\n    }\n\n    private void writeToMetaInf(Class<?> dstClass, Collection<String> services) {\n        String fileName = \"META-INF/services/\" + dstClass.getCanonicalName();\n\n        if (services.isEmpty()) {\n            return;\n        }\n\n        Filer filer = processingEnv.getFiler();\n        PrintWriter pw = null;\n        try {\n            processingEnv.getMessager().printMessage(Kind.NOTE,\"Writing \" + fileName);\n            FileObject f = filer.createResource(StandardLocation.CLASS_OUTPUT, \"\", fileName);\n            pw = new PrintWriter(new OutputStreamWriter(f.openOutputStream(), StandardCharsets.UTF_8));\n            services.forEach(pw::println);\n        } catch (IOException x) {\n            processingEnv.getMessager().printMessage(Kind.ERROR, \"Failed to write generated files: \" + x);\n        } finally {\n            if (pw != null) {\n                pw.close();\n            }\n        }\n    }\n\n    @Override\n    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {\n        if (roundEnv.processingOver()) {\n            return false;\n        }\n        final BeanUtils utils = BeanUtils.of(processingEnv);\n        Elements elements = processingEnv.getElementUtils();\n        LinkedHashMap<String, TypeElement> plugins = searchPluginClass(roundEnv.getRootElements(), utils);\n        if (plugins == null || plugins.isEmpty()) {\n            processingEnv.getMessager().printMessage(Kind.WARNING, \"Can't find AgentPlugin class!\");\n            return false;\n        }\n        Set<Class<? extends Annotation>> classes = new HashSet<>();\n        classes.add(AdvicesTo.class);\n        classes.add(AdviceTo.class);\n        Set<TypeElement> interceptors = process(classes, elements, roundEnv);\n        // generate providerBean\n        generateProviderBeans(plugins, interceptors, utils);\n\n        return false;\n    }\n\n    LinkedHashMap<String, TypeElement> searchPluginClass(Set<? extends Element> elements, BeanUtils utils) {\n        TypeElement findInterface = utils.getTypeElement(AgentPlugin.class.getCanonicalName());\n        TypeElement found;\n\n        ArrayList<TypeElement> plugins = new ArrayList<>();\n        ElementVisitor8 visitor = new ElementVisitor8(utils);\n        for (Element e : elements) {\n            found = e.accept(visitor, findInterface);\n            if (found != null) {\n                plugins.add(found);\n            }\n        }\n        LinkedHashMap<String, TypeElement> pluginNames = new LinkedHashMap<>();\n        for (TypeElement p : plugins) {\n            ClassName className = utils.classNameOf(p);\n            pluginNames.put(className.canonicalName(), p);\n        }\n        writeToMetaInf(AgentPlugin.class, pluginNames.keySet());\n\n        return pluginNames;\n    }\n\n    private void generateProviderBeans(LinkedHashMap<String, TypeElement> plugins,\n                                       Set<TypeElement> interceptors, BeanUtils utils) {\n        TreeSet<String> providers = new TreeSet<>();\n        TreeSet<String> points = new TreeSet<>();\n        for (TypeElement type : interceptors) {\n            if(isNull(type.getAnnotation(AdviceTo.class))\n                && isNull(type.getAnnotation(AdvicesTo.class))) {\n                continue;\n            }\n            List<? extends AnnotationMirror> annotations = type.getAnnotationMirrors();\n            Set<AnnotationMirror> adviceToAnnotations = new HashSet<>();\n            for (AnnotationMirror annotation : annotations) {\n                if (utils.isSameType(annotation.getAnnotationType(), AdviceTo.class.getCanonicalName())) {\n                    adviceToAnnotations.add(annotation);\n                    continue;\n                }\n                if (!utils.isSameType(annotation.getAnnotationType(), AdvicesTo.class.getCanonicalName())) {\n                    continue;\n                }\n                Map<? extends ExecutableElement, ? extends AnnotationValue> values = annotation.getElementValues();\n                RepeatedAnnotationVisitor visitor = new RepeatedAnnotationVisitor();\n                for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> e : values.entrySet()) {\n                    String key = e.getKey().getSimpleName().toString();\n                    if (key.equals(\"value\")) {\n                        AnnotationValue av = e.getValue();\n                        Set<AnnotationMirror> as = av.accept(visitor, AdvicesTo.class);\n                        adviceToAnnotations.addAll(as);\n                        break;\n                    }\n                }\n            }\n\n            int seq = 0;\n            TypeElement plugin = plugins.values().toArray(new TypeElement[0])[0];\n            for (AnnotationMirror annotation : adviceToAnnotations) {\n                Map<? extends ExecutableElement, ? extends AnnotationValue> values = annotation.getElementValues();\n                Map<String, String> to = new HashMap<>();\n                for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> e : values.entrySet()) {\n                    String key = e.getKey().getSimpleName().toString();\n                    AnnotationValue av = e.getValue();\n                    String value;\n                    if (av.getValue() == null) {\n                        value = \"default\";\n                    } else {\n                        value = av.getValue().toString();\n                    }\n                    to.put(key, value);\n                    if (key.equals(\"value\")) {\n                        points.add(value);\n                    } else if (key.equals(\"plugin\") && plugins.get(value) != null) {\n                        plugin = plugins.get(value);\n                    }\n                }\n                to.put(\"seq\", Integer.toString(seq));\n                GenerateProviderBean gb = new GenerateProviderBean(plugin, type, to, utils);\n                JavaFile file = gb.apply();\n                try {\n                    file.toBuilder().indent(\"    \")\n                        .addFileComment(\"This ia a generated file.\")\n                        .build().writeTo(processingEnv.getFiler());\n                    providers.add(gb.getProviderClass());\n                    seq += 1;\n                } catch (IOException e) {\n                    processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, e.getLocalizedMessage());\n                }\n            }\n        }\n        writeToMetaInf(Points.class, points);\n        writeToMetaInf(InterceptorProvider.class, providers);\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/processor/RepeatedAnnotationVisitor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.processor;\n\nimport javax.lang.model.element.AnnotationMirror;\nimport javax.lang.model.element.AnnotationValue;\nimport javax.lang.model.element.AnnotationValueVisitor;\nimport javax.lang.model.element.VariableElement;\nimport javax.lang.model.type.TypeMirror;\nimport java.lang.annotation.Annotation;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\n\npublic class RepeatedAnnotationVisitor\n    implements AnnotationValueVisitor<Set<AnnotationMirror>, Class<? extends Annotation>>  {\n\n    @Override\n    public Set<AnnotationMirror> visit(AnnotationValue av, Class<? extends Annotation> p) {\n        return Collections.emptySet();\n    }\n\n    @Override\n    public Set<AnnotationMirror> visit(AnnotationValue av) {\n        return Collections.emptySet();\n    }\n\n    @Override\n    public Set<AnnotationMirror> visitBoolean(boolean b, Class<? extends Annotation> p) {\n        return Collections.emptySet();\n    }\n\n    @Override\n    public Set<AnnotationMirror> visitByte(byte b, Class<? extends Annotation> aClass) {\n        return Collections.emptySet();\n    }\n\n    @Override\n    public Set<AnnotationMirror> visitChar(char c, Class<? extends Annotation> aClass) {\n        return Collections.emptySet();\n    }\n\n    @Override\n    public Set<AnnotationMirror> visitDouble(double d, Class<? extends Annotation> aClass) {\n        return Collections.emptySet();\n    }\n\n    @Override\n    public Set<AnnotationMirror> visitFloat(float f, Class<? extends Annotation> aClass) {\n        return Collections.emptySet();\n    }\n\n    @Override\n    public Set<AnnotationMirror> visitInt(int i, Class<? extends Annotation> aClass) {\n        return Collections.emptySet();\n    }\n\n    @Override\n    public Set<AnnotationMirror> visitLong(long i, Class<? extends Annotation> aClass) {\n        return Collections.emptySet();\n    }\n\n    @Override\n    public Set<AnnotationMirror> visitShort(short s, Class<? extends Annotation> aClass) {\n        return Collections.emptySet();\n    }\n\n    @Override\n    public Set<AnnotationMirror> visitString(String s, Class<? extends Annotation> aClass) {\n        return Collections.emptySet();\n    }\n\n    @Override\n    public Set<AnnotationMirror> visitType(TypeMirror t, Class<? extends Annotation> aClass) {\n        return Collections.emptySet();\n    }\n\n    @Override\n    public Set<AnnotationMirror> visitEnumConstant(VariableElement c, Class<? extends Annotation> aClass) {\n        return Collections.emptySet();\n    }\n\n    @Override\n    public Set<AnnotationMirror> visitAnnotation(AnnotationMirror a, Class<? extends Annotation> aClass) {\n        return Collections.singleton(a);\n    }\n\n    @Override\n    public Set<AnnotationMirror> visitArray(List<? extends AnnotationValue> values, Class<? extends Annotation> p) {\n        final Set<AnnotationMirror> accept = new HashSet<>();\n\n        for (AnnotationValue v : values) {\n            accept.addAll(v.accept(this, p));\n        }\n\n        return accept;\n    }\n\n    @Override\n    public Set<AnnotationMirror> visitUnknown(AnnotationValue av, Class<? extends Annotation> aClass) {\n        return Collections.emptySet();\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/report/AgentReport.java",
    "content": "/*\n * Copyright (c) 2022, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.report;\n\nimport com.megaease.easeagent.plugin.api.logging.AccessLogInfo;\nimport com.megaease.easeagent.plugin.api.otlp.common.AgentLogData;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport com.megaease.easeagent.plugin.report.metric.MetricReporterFactory;\n\n/**\n * report interface:\n * trace/metric/accessLog\n */\npublic interface AgentReport {\n    /**\n     * report trace span\n     * @param span trace span\n     */\n    void report(ReportSpan span);\n\n    /**\n     * report access-log\n     * @param log log info\n     */\n    void report(AccessLogInfo log);\n\n    /**\n     * report application log\n     * @param log log info\n     */\n    void report(AgentLogData log);\n\n    /**\n     * Metric reporters factory\n     * @return metric reporters factory\n     */\n    MetricReporterFactory metricReporter();\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/report/ByteWrapper.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.plugin.report;\n\npublic class ByteWrapper implements EncodedData {\n    byte[] data;\n\n    public ByteWrapper(byte[] data) {\n        this.data = data;\n    }\n\n    @Override\n    public byte[] getData() {\n        return data;\n    }\n\n    @Override\n    public int size() {\n        return this.data.length;\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/report/Call.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.megaease.easeagent.plugin.report;\n\nimport java.io.IOException;\n\n@SuppressWarnings(\"unused\")\npublic interface Call<V> {\n    V execute() throws IOException;\n\n    default void enqueue(Callback<V> cb) {\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/report/Callback.java",
    "content": "package com.megaease.easeagent.plugin.report;\n\n/*\n * Copyright 2015-2019 The OpenZipkin Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except\n * in compliance with the License. You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under the License\n * is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express\n * or implied. See the License for the specific language governing permissions and limitations under\n * the License.\n */\n\n/**\n * A callback of a single result or error.\n *\n * <p>This is a bridge to async libraries such as CompletableFuture complete, completeExceptionally.\n *\n * <p>Implementations will call either {@link #onSuccess} or {@link #onError}, but not both.\n */\npublic interface Callback<V> {\n\n    /**\n     * Invoked when computation produces its potentially null value successfully.\n     *\n     * <p>When this is called, {@link #onError} won't be.\n     */\n    void onSuccess(V value);\n\n    /**\n     * Invoked when computation produces a possibly null value successfully.\n     *\n     * <p>When this is called, {@link #onSuccess} won't be.\n     */\n    void onError(Throwable t);\n}\n\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/report/EncodedData.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.plugin.report;\n\npublic interface EncodedData {\n    int size();\n\n    byte[] getData();\n\n    EncodedData EMPTY = new EncodedData() {\n        @Override\n        public int size() {\n            return 0;\n        }\n\n        @Override\n        public byte[] getData() {\n            return new byte[0];\n        }\n    };\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/report/Encoder.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.plugin.report;\n\nimport com.megaease.easeagent.plugin.api.config.Config;\n\n/**\n * borrow from zipkin's BytesEncoder\n * Removing Encoding enum, and add encoderName method, allow define any kind of encoder with a unique name.\n * When the name is conflict with others, it will fail when load.\n * @param <T>\n */\npublic interface Encoder<T> extends Packer {\n    /**\n     * encoder init method, called when load\n     * @param config report plugin configuration\n     */\n    void init(Config config);\n\n    /** The byte length of its encoded binary form */\n    int sizeInBytes(T input);\n\n    /** Serializes an object into its binary form. */\n    EncodedData encode(T input);\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/report/Packer.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.plugin.report;\n\nimport java.util.List;\nimport java.util.stream.Collectors;\n\n/**\n * Pack a list of encoded items into a message package.\n */\npublic interface Packer {\n    /** The encoder name */\n    String name();\n\n    /**\n     * Combines a list of encoded items into an encoded list. For example, in thrift, this would be\n     * length-prefixed, whereas in json, this would be comma-separated and enclosed by brackets.\n     *\n     * @param encodedItems encoded item\n     * @return encoded list\n     */\n    EncodedData encodeList(List<EncodedData> encodedItems);\n\n    /**\n     * Calculate the size of a message package combined by a list of item\n     * @param encodedItems encodes item\n     * @return size of packaged message\n     */\n    default int messageSizeInBytes(List<EncodedData> encodedItems) {\n        return packageSizeInBytes(encodedItems.stream().map(EncodedData::size).collect(Collectors.toList()));\n    }\n\n    /**\n     * Calculate the increase size when append a new message\n     * @param newMsgSize the size of encoded message to append\n     * @return the increase size of a whole message package\n     */\n    int appendSizeInBytes(int newMsgSize);\n\n    /**\n     * Calculate the whole message package size combined of items\n     * @param sizes the size list of encoded items\n     * @return the size of a whole message package\n     */\n    int packageSizeInBytes(List<Integer> sizes);\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/report/Sender.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.plugin.report;\n\nimport com.megaease.easeagent.plugin.api.config.Config;\n\nimport java.io.Closeable;\nimport java.util.Map;\n\n/**\n * borrow from OpenZipkin's Sender.\n */\npublic interface Sender extends Closeable {\n    /**\n     * Define an unique name for the sender\n     */\n    String name();\n\n    /**\n     * Initialize the sender with the configuration\n     * @param config configuration with the prefix of \"plugin.reporter.sender.[name]\n     * @param prefix sender prefix : \"reporter.tracing.\" is the prefix of \"reporter.tracing.sender.[name]\"\n     */\n    void init(Config config, String prefix);\n\n    /**\n     * Sends encoded data to a transport such as http or Kafka.\n     *\n     * @param encodedData encoded data, such as encoded spans.\n     * @throws IllegalStateException if {@link #close() close} was called.\n     */\n    Call<Void> send(EncodedData encodedData);\n\n    /**\n     * If sender is available( not closed), return true, otherwise false.\n     */\n    boolean isAvailable();\n\n    /**\n     * when the configuration of sender changed, this method will be called\n     * @param changes changed configuration KVs\n     */\n    void updateConfigs(Map<String, String> changes);\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/report/encoder/JsonEncoder.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.plugin.report.encoder;\n\nimport com.megaease.easeagent.plugin.report.ByteWrapper;\nimport com.megaease.easeagent.plugin.report.EncodedData;\nimport com.megaease.easeagent.plugin.report.Encoder;\n\nimport java.util.List;\n\n/**\n * JSON Encoder\n * @param <T> abstract type\n */\npublic abstract class JsonEncoder<T> implements Encoder<T> {\n    @Override\n    public EncodedData encodeList(List<EncodedData> encodedItems) {\n        int sizeOfArray = 2;\n        int length = encodedItems.size();\n        for (int i = 0; i < length; ) {\n            sizeOfArray += encodedItems.get(i++).size();\n            if (i < length) sizeOfArray++;\n        }\n\n        byte[] buf = new byte[sizeOfArray];\n        int pos = 0;\n        buf[pos++] = '[';\n        for (int i = 0; i < length; ) {\n            byte[] v = encodedItems.get(i++).getData();\n            System.arraycopy(v, 0, buf, pos, v.length);\n            pos += v.length;\n            if (i < length) buf[pos++] = ',';\n        }\n        buf[pos] = ']';\n        return new ByteWrapper(buf);\n    }\n\n    @Override\n    public int packageSizeInBytes(List<Integer> sizes) {\n        int sizeInBytes = 2; // brackets\n\n        if (sizes != null && !sizes.isEmpty()) {\n            for (Integer size : sizes) {\n                sizeInBytes += size;\n                sizeInBytes++;\n            }\n            sizeInBytes--;\n        }\n\n        return sizeInBytes;\n    }\n\n    @Override\n    public int appendSizeInBytes(int newMsgSize) {\n        return newMsgSize + 1;\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/report/metric/MetricReporterFactory.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.report.metric;\n\nimport com.megaease.easeagent.plugin.api.Reporter;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\n\n/**\n * Metric plugin get reporter from metricReporterFactory\n */\npublic interface MetricReporterFactory {\n    Reporter reporter(IPluginConfig config);\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/report/tracing/Annotation.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.plugin.report.tracing;\n\n/**\n * form zipkin2.Annotation\n */\npublic class Annotation implements Comparable<Annotation> {\n    long timestamp;\n    String value;\n\n    public Annotation(long timestamp, String v) {\n        this.timestamp = timestamp;\n        this.value = v;\n    }\n\n    public long timestamp() {\n        return timestamp;\n    }\n\n    public String value() {\n        return value;\n    }\n\n    @Override\n    public int compareTo(Annotation that) {\n        if (this == that) return 0;\n        int byTimestamp = Long.compare(timestamp(), that.timestamp());\n        if (byTimestamp != 0) {\n            return byTimestamp;\n        }\n        return value().compareTo(that.value());\n    }\n\n    @Override\n    public int hashCode() {\n        int h = 1;\n        h *= 1000003;\n        h ^= (int) ((timestamp >>> 32) ^ timestamp);\n        h *= 1000003;\n        h ^= value.hashCode();\n        return h;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (o == this) {\n            return true;\n        }\n        if (!(o instanceof Annotation)) {\n            return false;\n        }\n        Annotation that = (Annotation) o;\n        return timestamp == that.timestamp() && value.equals(that.value());\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/report/tracing/Endpoint.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.plugin.report.tracing;\n\nimport lombok.Setter;\n\nimport java.util.Objects;\n\n/**\n * from zipkin2.Endpoint\n */\n@Setter\npublic class Endpoint {\n    String serviceName;\n    String ipv4;\n    String ipv6;\n    int port;\n\n    /**\n     * Lower-case label of this node in the service graph, such as \"favstar\". Leave absent if\n     * unknown.\n     *\n     * <p>This is a primary label for trace lookup and aggregation, so it should be intuitive and\n     * consistent. Many use a name from service discovery.\n     */\n    public String serviceName() {\n        return serviceName;\n    }\n\n    /**\n     * The text representation of the primary IPv4 address associated with this a connection. Ex.\n     * 192.168.99.100 Absent if unknown.\n     */\n    public String ipv4() {\n        return ipv4;\n    }\n\n    /**\n     * The text representation of the primary IPv6 address associated with this a connection. Ex.\n     * 2001:db8::c001 Absent if unknown.\n     *\n     * @see #ipv4() for mapped addresses\n     */\n    public String ipv6() {\n        return ipv6;\n    }\n\n    /**\n     * Port of the IP's socket or null, if not known.\n     *\n     * @see java.net.InetSocketAddress#getPort()\n     */\n    public int port() {\n        return port;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (o == this) return true;\n        if (!(o instanceof Endpoint)) {\n            return false;\n        }\n        Endpoint that = (Endpoint) o;\n        return (Objects.equals(serviceName, that.serviceName))\n            && (Objects.equals(ipv4, that.ipv4))\n            && (Objects.equals(ipv6, that.ipv6))\n            && port == that.port;\n    }\n\n    @Override\n    public int hashCode() {\n        int h = 1;\n        h *= 1000003;\n        h ^= (serviceName == null) ? 0 : serviceName.hashCode();\n        h *= 1000003;\n        h ^= (ipv4 == null) ? 0 : ipv4.hashCode();\n        h *= 1000003;\n        h ^= (ipv6 == null) ? 0 : ipv6.hashCode();\n        h *= 1000003;\n        h ^= port;\n        return h;\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/report/tracing/ReportSpan.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.plugin.report.tracing;\n\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * borrow form zipkin2.Span\n */\n@SuppressWarnings(\"unused\")\npublic interface ReportSpan {\n    /**\n     * span base\n     */\n    String traceId();\n\n    String parentId();\n\n    /** spanId */\n    String id();\n\n    /** Span.Kind.name */\n    String kind();\n\n    /**\n     * Span name in lowercase, rpc method for example.\n     *\n     * <p>Conventionally, when the span name isn't known, name = \"unknown\".\n     */\n    String name();\n\n    /**\n    * Epoch microseconds of the start of this span, possibly zero if this an incomplete span.\n    */\n    long timestamp();\n\n    /**\n    * Measurement in microseconds of the critical path, if known. Durations of less than one\n    * microsecond must be rounded up to 1 microsecond.\n    */\n    long duration();\n\n    /**\n     * True if we are contributing to a span started by another tracer (ex on a different host).\n     * Defaults to null. When set, it is expected for {@link #kind()} to be Kind#SERVER}.\n     */\n    boolean shared();\n\n    /** True is a request to store this span even if it overrides sampling policy. */\n    boolean debug();\n\n    /**\n    * The host that recorded this span, primarily for query by service name.\n    */\n    Endpoint localEndpoint();\n\n    /**\n    * The host that recorded this span, primarily for query by service name.\n    */\n    Endpoint remoteEndpoint();\n\n    /**\n     * annotation\n     */\n    List<Annotation> annotations();\n\n    /**\n     * tags\n     */\n    Map<String, String> tags();\n\n    String tag(String key);\n\n    default boolean hasError() {\n        return tags().containsKey(\"error\");\n    }\n\n    default String errorInfo() {\n        return tags().get(\"error\");\n    }\n\n    /**\n     * global\n     */\n    String type();\n    String service();\n    String system();\n\n    String localServiceName();\n    String remoteServiceName();\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/report/tracing/ReportSpanImpl.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.plugin.report.tracing;\n\nimport java.util.*;\n\npublic class ReportSpanImpl implements ReportSpan {\n    String traceId;\n    String parentId;\n    String id;\n    String kind;\n    String name;\n    long timestamp;\n    long duration;\n    boolean shared;\n    boolean debug;\n    Endpoint localEndpoint;\n    Endpoint remoteEndpoint;\n    List<Annotation> annotations;\n    Map<String, String> tags;\n\n    String type;\n    String service;\n    String system;\n\n    @Override\n    public String traceId() {\n        return traceId;\n    }\n\n    @Override\n    public String parentId() {\n        return parentId;\n    }\n\n    @Override\n    public String id() {\n        return id;\n    }\n\n    @Override\n    public String kind() {\n        return kind;\n    }\n\n    @Override\n    public String name() {\n        return name;\n    }\n\n    @Override\n    public long timestamp() {\n        return timestamp;\n    }\n\n    @Override\n    public long duration() {\n        return duration;\n    }\n\n    @Override\n    public boolean shared() {\n        return shared;\n    }\n\n    @Override\n    public boolean debug() {\n        return debug;\n    }\n\n    @Override\n    public Endpoint localEndpoint() {\n        return localEndpoint;\n    }\n\n    @Override\n    public Endpoint remoteEndpoint() {\n        return remoteEndpoint;\n    }\n\n    @Override\n    public List<Annotation> annotations() {\n        return annotations;\n    }\n\n    @Override\n    public Map<String, String> tags() {\n        return tags;\n    }\n\n    @Override\n    public String tag(String key) {\n        return tags == null ? null : tags.get(key);\n    }\n\n    @Override\n    public String type() {\n        return type;\n    }\n\n    @Override\n    public String service() {\n        return service;\n    }\n\n    @Override\n    public String localServiceName() {\n        return localEndpoint != null ? localEndpoint.serviceName() : null;\n    }\n\n    @Override\n    public String remoteServiceName() {\n        return remoteEndpoint != null ? remoteEndpoint.serviceName() : null;\n    }\n\n    @Override\n    public String system() {\n        return system;\n    }\n\n    public ReportSpanImpl(Builder builder) {\n        traceId = builder.traceId;\n        // prevent self-referencing spans\n        parentId = builder.id.equals(builder.parentId) ? null : builder.parentId;\n        id = builder.id;\n        kind = builder.kind;\n        name = builder.name;\n        timestamp = builder.timestamp;\n        duration = builder.duration;\n        localEndpoint = builder.localEndpoint;\n        remoteEndpoint = builder.remoteEndpoint;\n        annotations = builder.annotations;\n        tags = builder.tags == null ? Collections.emptyMap() : new TreeMap<>(builder.tags);\n        debug = builder.debug;\n        shared = builder.shared;\n    }\n\n    public abstract static class Builder {\n        protected String traceId;\n        protected String parentId;\n        protected String id;\n        protected String kind;\n        protected String name;\n        protected long timestamp;   // zero means null\n        protected long duration;    // zero means null\n        protected Endpoint localEndpoint;\n        protected Endpoint remoteEndpoint;\n        protected List<Annotation> annotations;\n        protected TreeMap<String, String> tags;\n        protected boolean shared;\n        protected boolean debug;\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/tools/config/NameAndSystem.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.tools.config;\n\nimport com.megaease.easeagent.plugin.api.config.ChangeItem;\nimport com.megaease.easeagent.plugin.api.config.ConfigChangeListener;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\n\nimport java.util.List;\n\npublic class NameAndSystem {\n    public static final NameAndSystem INSTANCE;\n\n    static {\n        INSTANCE = new NameAndSystem();\n        INSTANCE.name = EaseAgent.getConfig(ConfigConst.SERVICE_NAME);\n        INSTANCE.system = EaseAgent.getConfig(ConfigConst.SYSTEM_NAME);\n        EaseAgent.getConfig().addChangeListener(new ConfigChangeListener() {\n            @Override\n            public void onChange(List<ChangeItem> list) {\n                for (ChangeItem changeItem : list) {\n                    if (ConfigConst.SERVICE_NAME.equals(changeItem.getFullName())) {\n                        INSTANCE.name = changeItem.getNewValue();\n                    }\n                    if (ConfigConst.SYSTEM_NAME.equals(changeItem.getFullName())) {\n                        INSTANCE.system = changeItem.getNewValue();\n                    }\n                }\n            }\n        });\n    }\n\n    public static String system() {\n        return INSTANCE.system;\n    }\n\n    public static String name() {\n        return INSTANCE.name;\n    }\n\n    private volatile String name;\n    private volatile String system;\n\n    private NameAndSystem() {\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public String getSystem() {\n        return system;\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/tools/loader/AgentHelperClassLoader.java",
    "content": "/*\n * Copyright (c) 2022, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.megaease.easeagent.plugin.tools.loader;\n\nimport com.megaease.easeagent.plugin.utils.common.WeakConcurrentMap;\n\nimport java.net.URL;\nimport java.net.URLClassLoader;\nimport java.util.concurrent.ConcurrentHashMap;\n\n/**\n * if there are classes with the same classname in user classloaders and Agent classloader,\n * to avoid class cast exception in plugins, only load these classes by user classloaders in plugin context.\n * Other related plugin classes loaded by this classloader.\n */\npublic class AgentHelperClassLoader extends URLClassLoader {\n    private static final ConcurrentHashMap<URL, URL> helpUrls = new ConcurrentHashMap<>();\n    private static final WeakConcurrentMap<ClassLoader, AgentHelperClassLoader> helpLoaders = new WeakConcurrentMap<>();\n\n    private final URLClassLoader agentClassLoader;\n\n    public AgentHelperClassLoader(URL[] urls, ClassLoader parent, URLClassLoader agent) {\n        // may lead to classloader leak here\n        super(urls, parent);\n        this.agentClassLoader = agent;\n    }\n\n    public static void registryUrls(Class<?> clazz) {\n        URL url = clazz.getProtectionDomain().getCodeSource().getLocation();\n        helpUrls.putIfAbsent(url, url);\n    }\n\n    public static AgentHelperClassLoader getClassLoader(ClassLoader parent, URLClassLoader agent) {\n        AgentHelperClassLoader help = helpLoaders.getIfPresent(parent);\n        if (help != null) {\n            return help;\n        } else {\n            URL[] urls;\n            if (helpUrls.isEmpty()) {\n                urls = new URL[0];\n            } else {\n                urls = helpUrls.keySet().toArray(new URL[1]);\n            }\n            help = new AgentHelperClassLoader(urls, parent, agent);\n            if (helpLoaders.putIfProbablyAbsent(parent, help) == null) {\n                return help;\n            } else {\n                return helpLoaders.getIfPresent(parent);\n            }\n        }\n    }\n\n    @Override\n    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {\n        try {\n            return super.loadClass(name, resolve);\n        } catch (ClassNotFoundException e) {\n            try {\n                final Class<?> aClass = this.agentClassLoader.loadClass(name);\n                if (resolve) {\n                    resolveClass(aClass);\n                }\n                return aClass;\n            } catch (ClassNotFoundException ignored) {\n                // ignored\n            }\n            throw e;\n        }\n    }\n\n    @Override\n    public URL findResource(String name) {\n        URL url = super.findResource(name);\n        try {\n            url = this.agentClassLoader.getResource(name);\n            if (url != null) {\n                return url;\n            }\n        } catch (Exception ignored) {\n            // ignored\n        }\n        return url;\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/tools/matcher/ClassMatcherUtils.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.tools.matcher;\n\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\n\npublic class ClassMatcherUtils {\n    public static IClassMatcher name(String name) {\n        return ClassMatcher.builder().hasClassName(name)\n            .build();\n    }\n\n    public static IClassMatcher hasSuperType(String name) {\n        return ClassMatcher.builder().hasSuperClass(name)\n            .build();\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/tools/matcher/MethodMatcherUtils.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.megaease.easeagent.plugin.tools.matcher;\n\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\npublic class MethodMatcherUtils {\n    public static IMethodMatcher constructor() {\n        return MethodMatcher.builder().named(\"<init>\")\n            .qualifier(\"constructor\")\n            .build();\n    }\n\n    public static IMethodMatcher name(String name) {\n        return MethodMatcher.builder().named(name)\n            .build();\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/tools/metrics/AccessLogServerInfo.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.tools.metrics;\n\nimport java.util.Map;\n\npublic interface AccessLogServerInfo {\n    String getMethod();\n\n    String getHeader(String key);\n\n    String getRemoteAddr();\n\n    String getRequestURI();\n\n    int getResponseBufferSize();\n\n    String getMatchURL();\n\n    Map<String, String> findHeaders();\n\n    Map<String, String> findQueries();\n\n    String getStatusCode();\n\n    default String getClientIP() {\n        return AccessLogServerInfo.getRemoteHost(this);\n    }\n\n    static String getRemoteHost(AccessLogServerInfo serverInfo) {\n        if (serverInfo == null) {\n            return \"unknown\";\n        }\n        String ip = serverInfo.getHeader(\"x-forwarded-for\");\n        if (ip == null || ip.length() == 0 || \"unknown\".equalsIgnoreCase(ip)) {\n            ip = serverInfo.getHeader(\"Proxy-Client-IP\");\n        }\n        if (ip == null || ip.length() == 0 || \"unknown\".equalsIgnoreCase(ip)) {\n            ip = serverInfo.getHeader(\"WL-Proxy-Client-IP\");\n        }\n        if (ip == null || ip.length() == 0 || \"unknown\".equalsIgnoreCase(ip)) {\n            ip = serverInfo.getRemoteAddr();\n        }\n        return ip.equals(\"0:0:0:0:0:0:0:1\") ? \"127.0.0.1\" : ip;\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/tools/metrics/ErrorPercentModelGauge.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.tools.metrics;\n\nimport com.megaease.easeagent.plugin.utils.ImmutableMap;\n\nimport java.math.BigDecimal;\nimport java.util.Map;\n\npublic class ErrorPercentModelGauge implements GaugeMetricModel {\n    private BigDecimal m1ErrorPercent;\n    private BigDecimal m5ErrorPercent;\n    private BigDecimal m15ErrorPercent;\n\n    public ErrorPercentModelGauge(BigDecimal m1ErrorPercent, BigDecimal m5ErrorPercent, BigDecimal m15ErrorPercent) {\n        this.m1ErrorPercent = m1ErrorPercent;\n        this.m5ErrorPercent = m5ErrorPercent;\n        this.m15ErrorPercent = m15ErrorPercent;\n    }\n\n    @Override\n    public Map<String, Object> toHashMap() {\n        return ImmutableMap.<String, Object>builder()\n            .put(\"m1errpct\", m1ErrorPercent)\n            .put(\"m5errpct\", m5ErrorPercent)\n            .put(\"m15errpct\", m15ErrorPercent)\n            .build();\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/tools/metrics/GaugeMetricModel.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.tools.metrics;\n\nimport java.util.Map;\n\n/**\n * GaugeMetricModel is dedicated to producing gauge data with label.\n * <p>Each gauge metric must to produce a object which implement\n * {@code GaugeMetricModel} interface</p> in our metric collecting\n * framework.\n */\npublic interface GaugeMetricModel {\n    /**\n     * Returning gauge data and its label.\n     *\n     * @return a map which contains field-value pairs of gauge data to serialize JSON data.\n     */\n    Map<String, Object> toHashMap();\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/tools/metrics/HttpLog.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.tools.metrics;\n\nimport com.megaease.easeagent.plugin.api.ProgressFields;\nimport com.megaease.easeagent.plugin.api.logging.AccessLogInfo;\nimport com.megaease.easeagent.plugin.api.middleware.RedirectProcessor;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.utils.SystemClock;\nimport com.megaease.easeagent.plugin.utils.common.HostAddress;\nimport com.megaease.easeagent.plugin.utils.common.JsonUtil;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\npublic class HttpLog {\n\n    public AccessLogInfo prepare(String system, String serviceName, Long beginTime, Span span, AccessLogServerInfo serverInfo) {\n        AccessLogInfo accessLog = prepare(system, serviceName, beginTime, serverInfo);\n        if (span == null) {\n            return accessLog;\n        }\n        accessLog.setTraceId(span.traceIdString());\n        accessLog.setSpanId(span.spanIdString());\n        accessLog.setParentSpanId(span.parentIdString());\n        return accessLog;\n    }\n\n    private AccessLogInfo prepare(String system, String serviceName, Long beginTime, AccessLogServerInfo serverInfo) {\n        AccessLogInfo accessLog = new AccessLogInfo();\n        accessLog.setSystem(system);\n        accessLog.setService(serviceName);\n        accessLog.setHostName(HostAddress.localhost());\n        accessLog.setHostIpv4(HostAddress.getHostIpv4());\n        accessLog.setUrl(serverInfo.getMethod() + \" \" + serverInfo.getRequestURI());\n        accessLog.setMethod(serverInfo.getMethod());\n        accessLog.setHeaders(serverInfo.findHeaders());\n        accessLog.setBeginTime(beginTime);\n        accessLog.setQueries(getQueries(serverInfo));\n        accessLog.setClientIP(serverInfo.getClientIP());\n        accessLog.setBeginCpuTime(System.nanoTime());\n        return accessLog;\n    }\n\n    private Map<String, String> getQueries(AccessLogServerInfo serverInfo) {\n        Map<String, String> serviceTags = ProgressFields.getServiceTags();\n        Map<String, String> meshTags = RedirectProcessor.tags();\n        if (serviceTags.isEmpty() && meshTags.isEmpty()) {\n            return serverInfo.findQueries();\n        }\n        Map<String, String> queries = new HashMap<>(meshTags);\n        queries.putAll(serviceTags);\n        queries.putAll(serverInfo.findQueries());\n        return queries;\n    }\n\n    public String getLogString(AccessLogInfo accessLog, boolean success, Long beginTime, AccessLogServerInfo serverInfo) {\n        this.finish(accessLog, success, beginTime, serverInfo);\n\n        List<AccessLogInfo> list = new ArrayList<>(1);\n        list.add(accessLog);\n\n        return JsonUtil.toJson(list);\n    }\n\n\n    public void finish(AccessLogInfo accessLog, boolean success, Long beginTime, AccessLogServerInfo serverInfo) {\n        accessLog.setStatusCode(serverInfo.getStatusCode());\n        if (!success) {\n            accessLog.setStatusCode(\"500\");\n        }\n        long now = SystemClock.now();\n        accessLog.setTimestamp(now);\n        accessLog.setRequestTime(now - beginTime);\n        accessLog.setCpuElapsedTime(System.nanoTime() - accessLog.getBeginCpuTime());\n        accessLog.setResponseSize(serverInfo.getResponseBufferSize());\n        accessLog.setMatchUrl(serverInfo.getMatchURL());\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/tools/metrics/LastMinutesCounterGauge.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.tools.metrics;\n\nimport com.megaease.easeagent.plugin.utils.ImmutableMap;\nimport lombok.Builder;\nimport lombok.Data;\n\nimport java.util.Map;\n\n@Builder\n@Data\npublic class LastMinutesCounterGauge implements GaugeMetricModel {\n    private final long m1Count;\n    private final long m5Count;\n    private final long m15Count;\n    private final String prefix;\n\n    @Override\n    public Map<String, Object> toHashMap() {\n        String px = this.prefix == null ? \"\" : this.prefix;\n        return ImmutableMap.<String, Object>builder()\n                .put(px + \"m1cnt\", m1Count)\n                .put(px + \"m5cnt\", m5Count)\n                .put(px + \"m15cnt\", m15Count)\n                .build();\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/tools/metrics/NameFactorySupplier.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.tools.metrics;\n\nimport com.megaease.easeagent.plugin.api.metric.name.NameFactory;\n\n/**\n * A {@link NameFactory} Supplier\n */\npublic interface NameFactorySupplier {\n    /**\n     * new a NameFactory\n     *\n     * @return {@link NameFactory}\n     */\n    NameFactory newInstance();\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/tools/metrics/RedisMetric.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.tools.metrics;\n\nimport com.megaease.easeagent.plugin.api.metric.*;\nimport com.megaease.easeagent.plugin.api.metric.name.*;\nimport com.megaease.easeagent.plugin.utils.ImmutableMap;\n\nimport javax.annotation.Nonnull;\nimport java.util.HashMap;\nimport java.util.concurrent.TimeUnit;\n\npublic class RedisMetric extends ServiceMetric {\n    public static final ServiceMetricSupplier<RedisMetric> REDIS_METRIC_SUPPLIER = new ServiceMetricSupplier<RedisMetric>() {\n        @Override\n        public NameFactory newNameFactory() {\n            return RedisMetric.nameFactory();\n        }\n\n        @Override\n        public RedisMetric newInstance(MetricRegistry metricRegistry, NameFactory nameFactory) {\n            return new RedisMetric(metricRegistry, nameFactory);\n        }\n    };\n\n    public RedisMetric(@Nonnull MetricRegistry metricRegistry, @Nonnull NameFactory nameFactory) {\n        super(metricRegistry, nameFactory);\n    }\n\n    public void collect(String key, long duration, boolean success) {\n        metricRegistry.timer(this.nameFactory.timerName(key, MetricSubType.DEFAULT)).update(duration, TimeUnit.MILLISECONDS);\n        final Meter defaultMeter = metricRegistry.meter(nameFactory.meterName(key, MetricSubType.DEFAULT));\n        final Counter defaultCounter = metricRegistry.counter(nameFactory.counterName(key, MetricSubType.DEFAULT));\n        final Meter errorMeter = metricRegistry.meter(nameFactory.meterName(key, MetricSubType.ERROR));\n        final Counter errorCounter = metricRegistry.counter(nameFactory.counterName(key, MetricSubType.ERROR));\n\n        if (!success) {\n            errorMeter.mark();\n            errorCounter.inc();\n        }\n        defaultMeter.mark();\n        defaultCounter.inc();\n\n        MetricName gaugeName = nameFactory.gaugeNames(key).get(MetricSubType.DEFAULT);\n        metricRegistry.gauge(gaugeName.name(), () -> () ->\n            LastMinutesCounterGauge.builder()\n                .m1Count((long) (defaultMeter.getOneMinuteRate() * 60))\n                .m5Count((long) (defaultMeter.getFiveMinuteRate() * 60 * 5))\n                .m15Count((long) (defaultMeter.getFifteenMinuteRate() * 60 * 15))\n                .build());\n    }\n\n    @Nonnull\n    public static NameFactory nameFactory() {\n        return NameFactory.createBuilder()\n            .timerType(MetricSubType.DEFAULT,\n                ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                    .put(MetricField.MIN_EXECUTION_TIME, MetricValueFetcher.SnapshotMinValue)\n                    .put(MetricField.MAX_EXECUTION_TIME, MetricValueFetcher.SnapshotMaxValue)\n                    .put(MetricField.MEAN_EXECUTION_TIME, MetricValueFetcher.SnapshotMeanValue)\n                    .put(MetricField.P25_EXECUTION_TIME, MetricValueFetcher.Snapshot25Percentile)\n                    .put(MetricField.P50_EXECUTION_TIME, MetricValueFetcher.Snapshot50PercentileValue)\n                    .put(MetricField.P75_EXECUTION_TIME, MetricValueFetcher.Snapshot75PercentileValue)\n                    .put(MetricField.P95_EXECUTION_TIME, MetricValueFetcher.Snapshot95PercentileValue)\n                    .put(MetricField.P98_EXECUTION_TIME, MetricValueFetcher.Snapshot98PercentileValue)\n                    .put(MetricField.P99_EXECUTION_TIME, MetricValueFetcher.Snapshot99PercentileValue)\n                    .put(MetricField.P999_EXECUTION_TIME, MetricValueFetcher.Snapshot999PercentileValue)\n                    .build())\n            .gaugeType(MetricSubType.DEFAULT, new HashMap<>())\n            .meterType(MetricSubType.DEFAULT,\n                ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                    .put(MetricField.M1_RATE, MetricValueFetcher.MeteredM1Rate)\n                    .put(MetricField.M5_RATE, MetricValueFetcher.MeteredM5Rate)\n                    .put(MetricField.M15_RATE, MetricValueFetcher.MeteredM15Rate)\n                    .put(MetricField.MEAN_RATE, MetricValueFetcher.MeteredMeanRate)\n                    .build())\n            .meterType(MetricSubType.ERROR,\n                ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                    .put(MetricField.M1_ERROR_RATE, MetricValueFetcher.MeteredM1Rate)\n                    .put(MetricField.M5_ERROR_RATE, MetricValueFetcher.MeteredM5Rate)\n                    .put(MetricField.M15_ERROR_RATE, MetricValueFetcher.MeteredM15Rate)\n                    .build())\n            .counterType(MetricSubType.ERROR, ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                .put(MetricField.EXECUTION_ERROR_COUNT, MetricValueFetcher.CountingCount)\n                .build())\n            .counterType(MetricSubType.DEFAULT, ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                .put(MetricField.EXECUTION_COUNT, MetricValueFetcher.CountingCount)\n                .build())\n            .build();\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/tools/metrics/ServerMetric.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.tools.metrics;\n\nimport com.megaease.easeagent.plugin.api.metric.*;\nimport com.megaease.easeagent.plugin.api.metric.name.MetricField;\nimport com.megaease.easeagent.plugin.api.metric.name.MetricSubType;\nimport com.megaease.easeagent.plugin.api.metric.name.MetricValueFetcher;\nimport com.megaease.easeagent.plugin.api.metric.name.NameFactory;\nimport com.megaease.easeagent.plugin.utils.ImmutableMap;\n\nimport javax.annotation.Nonnull;\nimport java.math.BigDecimal;\nimport java.time.Duration;\nimport java.util.HashMap;\n\npublic class ServerMetric extends ServiceMetric {\n    public static final ServiceMetricSupplier<ServerMetric> SERVICE_METRIC_SUPPLIER = new ServiceMetricSupplier<ServerMetric>() {\n        @Override\n        public NameFactory newNameFactory() {\n            return ServerMetric.nameFactory();\n        }\n\n        @Override\n        public ServerMetric newInstance(MetricRegistry metricRegistry, NameFactory nameFactory) {\n            return new ServerMetric(metricRegistry, nameFactory);\n        }\n    };\n\n    public ServerMetric(@Nonnull MetricRegistry metricRegistry, @Nonnull NameFactory nameFactory) {\n        super(metricRegistry, nameFactory);\n    }\n\n    public void collectMetric(String key, int statusCode, Throwable throwable, long startMillis, long endMillis) {\n        Timer timer = timer(key, MetricSubType.DEFAULT);\n        timer.update(Duration.ofMillis(endMillis - startMillis));\n        final Meter errorMeter = meter(key, MetricSubType.ERROR);\n        final Meter meter = meter(key, MetricSubType.DEFAULT);\n        Counter errorCounter = counter(key, MetricSubType.ERROR);\n        Counter counter = counter(key, MetricSubType.DEFAULT);\n        boolean hasException = throwable != null;\n        if (statusCode >= 400 || hasException) {\n            errorMeter.mark();\n            errorCounter.inc();\n        }\n        counter.inc();\n        meter.mark();\n\n        gauge(key, MetricSubType.DEFAULT, () -> () -> {\n            BigDecimal m1ErrorPercent = BigDecimal.ZERO;\n            BigDecimal m5ErrorPercent = BigDecimal.ZERO;\n            BigDecimal m15ErrorPercent = BigDecimal.ZERO;\n            BigDecimal error = BigDecimal.valueOf(errorMeter.getOneMinuteRate()).setScale(5, BigDecimal.ROUND_HALF_DOWN);\n            BigDecimal n = BigDecimal.valueOf(meter.getOneMinuteRate());\n            if (n.compareTo(BigDecimal.ZERO) != 0) {\n                m1ErrorPercent = error.divide(n, 2, BigDecimal.ROUND_HALF_UP);\n            }\n            error = BigDecimal.valueOf(errorMeter.getFiveMinuteRate()).setScale(5, BigDecimal.ROUND_HALF_DOWN);\n            n = BigDecimal.valueOf(meter.getFiveMinuteRate());\n            if (n.compareTo(BigDecimal.ZERO) != 0) {\n                m5ErrorPercent = error.divide(n, 2, BigDecimal.ROUND_HALF_UP);\n            }\n\n            error = BigDecimal.valueOf(errorMeter.getFifteenMinuteRate()).setScale(5, BigDecimal.ROUND_HALF_DOWN);\n            n = BigDecimal.valueOf(meter.getFifteenMinuteRate());\n            if (n.compareTo(BigDecimal.ZERO) != 0) {\n                m15ErrorPercent = error.divide(n, 2, BigDecimal.ROUND_HALF_UP);\n            }\n            return new ErrorPercentModelGauge(m1ErrorPercent, m5ErrorPercent, m15ErrorPercent);\n        });\n    }\n\n    @Nonnull\n    public static NameFactory nameFactory() {\n        return NameFactory.createBuilder()\n            .counterType(MetricSubType.DEFAULT, ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                .put(MetricField.EXECUTION_COUNT, MetricValueFetcher.CountingCount)\n                .build())\n            .meterType(MetricSubType.DEFAULT, ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                .put(MetricField.M1_RATE, MetricValueFetcher.MeteredM1RateIgnoreZero)\n                .put(MetricField.M5_RATE, MetricValueFetcher.MeteredM5Rate)\n                .put(MetricField.M15_RATE, MetricValueFetcher.MeteredM15Rate)\n                .put(MetricField.MEAN_RATE, MetricValueFetcher.MeteredMeanRate)\n                .build())\n            .counterType(MetricSubType.ERROR, ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                .put(MetricField.EXECUTION_ERROR_COUNT, MetricValueFetcher.CountingCount)\n                .build())\n            .meterType(MetricSubType.ERROR, ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                .put(MetricField.M1_ERROR_RATE, MetricValueFetcher.MeteredM1Rate)\n                .put(MetricField.M5_ERROR_RATE, MetricValueFetcher.MeteredM5Rate)\n                .put(MetricField.M15_ERROR_RATE, MetricValueFetcher.MeteredM15Rate)\n                .put(MetricField.MEAN_RATE, MetricValueFetcher.MeteredMeanRate)\n                .build())\n            .gaugeType(MetricSubType.DEFAULT, new HashMap<>())\n            .timerType(MetricSubType.DEFAULT,\n                ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                    .put(MetricField.MIN_EXECUTION_TIME, MetricValueFetcher.SnapshotMinValue)\n                    .put(MetricField.MAX_EXECUTION_TIME, MetricValueFetcher.SnapshotMaxValue)\n                    .put(MetricField.MEAN_EXECUTION_TIME, MetricValueFetcher.SnapshotMeanValue)\n                    .put(MetricField.P25_EXECUTION_TIME, MetricValueFetcher.Snapshot25Percentile)\n                    .put(MetricField.P50_EXECUTION_TIME, MetricValueFetcher.Snapshot50PercentileValue)\n                    .put(MetricField.P75_EXECUTION_TIME, MetricValueFetcher.Snapshot75PercentileValue)\n                    .put(MetricField.P95_EXECUTION_TIME, MetricValueFetcher.Snapshot95PercentileValue)\n                    .put(MetricField.P98_EXECUTION_TIME, MetricValueFetcher.Snapshot98PercentileValue)\n                    .put(MetricField.P99_EXECUTION_TIME, MetricValueFetcher.Snapshot99PercentileValue)\n                    .put(MetricField.P999_EXECUTION_TIME, MetricValueFetcher.Snapshot999PercentileValue)\n                    .build())\n            .build();\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/tools/trace/BaseHttpClientTracingInterceptor.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.tools.trace;\n\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.RequestContext;\nimport com.megaease.easeagent.plugin.interceptor.NonReentrantInterceptor;\n\npublic abstract class BaseHttpClientTracingInterceptor implements NonReentrantInterceptor {\n\n    @Override\n    public void doBefore(MethodInfo methodInfo, Context context) {\n        HttpRequest request = getRequest(methodInfo, context);\n        RequestContext requestContext = context.clientRequest(request);\n        HttpUtils.handleReceive(requestContext.span().start(), request);\n        context.put(getProgressKey(), requestContext);\n    }\n\n    @Override\n    public void doAfter(MethodInfo methodInfo, Context context) {\n        RequestContext requestContext = context.remove(getProgressKey());\n        if (requestContext == null) {\n            return;\n        }\n        try {\n            HttpResponse responseWrapper = getResponse(methodInfo, context);\n            HttpUtils.save(requestContext.span(), responseWrapper);\n            requestContext.finish(responseWrapper);\n        } finally {\n            requestContext.scope().close();\n        }\n    }\n\n    @Override\n    public int order() {\n        return Order.TRACING.getOrder();\n    }\n\n    public abstract Object getProgressKey();\n\n    protected abstract HttpRequest getRequest(MethodInfo methodInfo, Context context);\n\n    protected abstract HttpResponse getResponse(MethodInfo methodInfo, Context context);\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/tools/trace/HttpRequest.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.tools.trace;\n\nimport com.megaease.easeagent.plugin.api.trace.Request;\n\npublic interface HttpRequest extends Request {\n    @Override\n    default String name() {\n        return method();\n    }\n\n    String method();\n\n    String path();\n\n    String route();\n\n    String getRemoteAddr();\n\n    int getRemotePort();\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/tools/trace/HttpResponse.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.tools.trace;\n\nimport com.megaease.easeagent.plugin.api.trace.Response;\n\npublic interface HttpResponse extends Response {\n\n    String method();\n\n    String route();\n\n    int statusCode();\n\n    Throwable maybeError();\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/tools/trace/HttpUtils.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.tools.trace;\n\nimport com.megaease.easeagent.plugin.api.trace.Span;\n\nimport static com.megaease.easeagent.plugin.tools.trace.TraceConst.HTTP_HEADER_X_FORWARDED_FOR;\n\npublic class HttpUtils {\n    private HttpUtils() {}\n\n    public static void handleReceive(Span span, HttpRequest httpRequest) {\n        span.name(httpRequest.name());\n        span.tag(TraceConst.HTTP_TAG_ROUTE, httpRequest.route());\n        span.tag(TraceConst.HTTP_TAG_METHOD, httpRequest.method());\n        span.tag(TraceConst.HTTP_TAG_PATH, httpRequest.path());\n        if (!parseHttpClientIpFromXForwardedFor(span, httpRequest)) {\n            span.remoteIpAndPort(httpRequest.getRemoteAddr(), httpRequest.getRemotePort());\n        }\n        span.start();\n    }\n\n    private static boolean parseHttpClientIpFromXForwardedFor(Span span, HttpRequest httpRequest) {\n        String forwardedFor = httpRequest.header(HTTP_HEADER_X_FORWARDED_FOR);\n        if (forwardedFor == null) return false;\n        int indexOfComma = forwardedFor.indexOf(',');\n        if (indexOfComma != -1) forwardedFor = forwardedFor.substring(0, indexOfComma);\n        return span.remoteIpAndPort(forwardedFor, 0);\n    }\n\n    public static void finish(Span span, HttpResponse httpResponse) {\n        save(span, httpResponse);\n        span.finish();\n    }\n\n    public static void save(Span span, HttpResponse httpResponse) {\n        Throwable error = httpResponse.maybeError();\n        if (error != null) {\n            span.error(error); // Ensures MutableSpan.error() for SpanHandler\n        }\n        int statusCode = httpResponse.statusCode();\n        if (statusCode != 0) {\n            String nameFromRoute = spanNameFromRoute(httpResponse, statusCode);\n            if (nameFromRoute != null) span.name(nameFromRoute);\n            if (statusCode < 200 || statusCode > 299) { // not success code\n                span.tag(TraceConst.HTTP_TAG_STATUS_CODE, String.valueOf(statusCode));\n            }\n        }\n        if (error == null && (statusCode < 100 || statusCode > 399)) {\n            span.tag(TraceConst.HTTP_TAG_ERROR, String.valueOf(statusCode));\n        }\n    }\n\n    static String spanNameFromRoute(HttpResponse httpRequest, int statusCode) {\n        String method = httpRequest.method();\n        if (method == null) return null; // don't undo a valid name elsewhere\n        String route = httpRequest.route();\n        if (route == null) return null; // don't undo a valid name elsewhere\n        if (!\"\".equals(route)) return method + \" \" + route;\n        return catchAllName(method, statusCode);\n    }\n\n    static String catchAllName(String method, int statusCode) {\n        switch (statusCode) {\n            // from https://tools.ietf.org/html/rfc7231#section-6.4\n            case 301:\n            case 302:\n            case 303:\n            case 305:\n            case 306:\n            case 307:\n                return method + \" redirected\";\n            case 404:\n                return method + \" not_found\";\n            default:\n                return null;\n        }\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/tools/trace/TraceConst.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.tools.trace;\n\npublic interface TraceConst {\n    String HTTP_HEADER_X_FORWARDED_FOR = \"X-Forwarded-For\";\n    String HTTP_ATTRIBUTE_ROUTE = \"http.route\";\n    String HTTP_TAG_ROUTE = HTTP_ATTRIBUTE_ROUTE;\n    String HTTP_TAG_METHOD = \"http.method\";\n    String HTTP_TAG_PATH = \"http.path\";\n    String HTTP_TAG_STATUS_CODE = \"http.status_code\";\n    String HTTP_TAG_ERROR = \"error\";\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/utils/AdditionalAttributes.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.utils;\n\n\nimport com.megaease.easeagent.plugin.api.logging.Logger;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\n\nimport java.net.Inet4Address;\nimport java.net.InetAddress;\nimport java.net.NetworkInterface;\nimport java.net.UnknownHostException;\nimport java.util.Enumeration;\nimport java.util.HashMap;\nimport java.util.Map;\n\n\n/**\n * AdditionalAttributes hold the global attributes field values which are\n * always same in an instance, such as</p>\n * <b>host_ipv4</b>: an ip address to identical instance address\n * <b>hostname</b>: a name to identical instance name\n * <b>serviceName</b> service name\n */\npublic class AdditionalAttributes {\n    private final static Logger LOGGER = EaseAgent.getLogger(AdditionalAttributes.class);\n    public final Map<String, Object> attributes;\n\n    public AdditionalAttributes(String serviceName, String systemName) {\n        attributes = new HashMap<>();\n        attributes.put(\"host_ipv4\", getHostIpv4());\n        attributes.put(\"service\", serviceName);\n        attributes.put(\"system\", systemName);\n        attributes.put(\"host_name\", getHostName());\n    }\n\n    public AdditionalAttributes(String serviceName) {\n        this(serviceName, \"none\");\n    }\n\n    static String getHostIpV4(Enumeration<NetworkInterface> networkInterfaces) throws Exception {\n        String ip;\n        String secondaryIP = \"\";\n        while (networkInterfaces.hasMoreElements()) {\n            NetworkInterface i = networkInterfaces.nextElement();\n            if (i.isLoopback()) {\n                continue;\n            }\n\n            if (isPrimaryInterface(i)) {\n                // We treat interface name which started with \"en\" or \"eth\" as primary interface.\n                // We prefer to use address of primary interface as value of the `host_ipv4`\n                ip = ipAddressFromInetAddress(i);\n                if (!isEmpty(ip)) {\n                    return ip;\n                }\n            } else if (isEmpty(secondaryIP)) {\n                secondaryIP = ipAddressFromInetAddress(i);\n            }\n        }\n\n        return !isEmpty(secondaryIP) ? secondaryIP : \"UnknownIP\";\n    }\n\n    private static boolean isEmpty(String text) {\n        return text == null || text.trim().length() == 0;\n    }\n\n    private static boolean isPrimaryInterface(NetworkInterface i) {\n        return i.getName().startsWith(\"en\") || i.getName().startsWith(\"eth\");\n    }\n\n    private static String ipAddressFromInetAddress(NetworkInterface i) {\n        String ip = \"\";\n        Enumeration<InetAddress> ee = i.getInetAddresses();\n        while (ee.hasMoreElements()) {\n            InetAddress a = ee.nextElement();\n            if (a instanceof Inet4Address) {\n                if (!a.isMulticastAddress() && !a.isLoopbackAddress()) {\n                    ip = a.getHostAddress();\n                    break;\n                }\n            }\n        }\n        return ip;\n    }\n\n    public Map<String, Object> getAdditionalAttributes() {\n        return attributes;\n    }\n\n    public static String getHostName() {\n        try {\n            return (InetAddress.getLocalHost()).getHostName();\n        } catch (UnknownHostException uhe) {\n            // host = \"hostname: hostname\"\n            String host = uhe.getMessage();\n            if (host != null) {\n                int colon = host.indexOf(':');\n                if (colon > 0) {\n                    return host.substring(0, colon);\n                }\n            }\n            return \"UnknownHost\";\n        }\n    }\n\n    private String getHostIpv4() {\n        try {\n            return getHostIpV4(NetworkInterface.getNetworkInterfaces());\n        } catch (Exception e) {\n//            LOGGER.warn(\"can't fetch ip address \", e);\n        }\n        return \"UnknownIP\";\n    }\n\n\n    public static String getLocalIP() {\n        try {\n            return getHostIpV4(NetworkInterface.getNetworkInterfaces());\n        } catch (Exception e) {\n            return null;\n        }\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/utils/ClassInstance.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.utils;\n\nimport java.lang.reflect.ParameterizedType;\nimport java.lang.reflect.Type;\n\npublic abstract class ClassInstance<T> {\n    private final Class<?> type;\n\n    public ClassInstance() {\n        Type superClass = getClass().getGenericSuperclass();\n        if (superClass instanceof Class<?>) { // sanity check, should never happen\n            throw new IllegalArgumentException(\"Internal error: MetricInstance constructed without actual type information\");\n        }\n        Type t = ((ParameterizedType) superClass).getActualTypeArguments()[0];\n        if (!(t instanceof Class)) {\n            throw new IllegalArgumentException(\"Internal error: MetricInstance constructed without actual type information\");\n        }\n        type = (Class<?>) t;\n    }\n\n    public boolean isInstance(Object o) {\n        return type.isInstance(o);\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    public T to(Object o) {\n        return (T) o;\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/utils/ClassUtils.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.utils;\n\nimport java.lang.reflect.Field;\n\npublic class ClassUtils {\n    public static boolean hasClass(String className) {\n        try {\n            Thread.currentThread().getContextClassLoader().loadClass(className);\n            return true;\n        } catch (ClassNotFoundException e) {\n            return false;\n        }\n    }\n\n    public static boolean isInstance(String className, Object obj) {\n        try {\n            Class<?> c = Thread.currentThread().getContextClassLoader().loadClass(className);\n            return c.isInstance(obj);\n        } catch (ClassNotFoundException e) {\n            return false;\n        }\n    }\n\n    public static Object getStaticField(String className, String fieldName) {\n        try {\n            Class<?> c = Thread.currentThread().getContextClassLoader().loadClass(className);\n            Field field = c.getDeclaredField(fieldName);\n            return field.get(null);\n        } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException e) {\n            return null;\n        }\n    }\n\n    public static abstract class TypeChecker {\n        protected final String className;\n        private final boolean hasClass;\n\n        public TypeChecker(String className) {\n            this.className = className;\n            this.hasClass = ClassUtils.hasClass(className);\n        }\n\n        public boolean isHasClass() {\n            return hasClass;\n        }\n\n        public boolean hasClassAndIsType(Object o) {\n            if (!isHasClass()) {\n                return false;\n            }\n            return isType(o);\n        }\n\n        protected abstract boolean isType(Object o);\n\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/utils/ImmutableMap.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.utils;\n\nimport javax.annotation.Nonnull;\nimport java.util.Collection;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Set;\n\npublic class ImmutableMap<K, V> implements Map<K, V> {\n    Map<K, V> delegate;\n\n    public ImmutableMap(Map<K, V> map) {\n        this.delegate = map;\n    }\n\n    @Override\n    public int size() {\n        return delegate.size();\n    }\n\n    @Override\n    public boolean isEmpty() {\n        return delegate.isEmpty();\n    }\n\n    @Override\n    public boolean containsKey(Object key) {\n        return delegate.containsKey(key);\n    }\n\n    @Override\n    public boolean containsValue(Object value) {\n        return delegate.containsValue(value);\n    }\n\n    @Override\n    public V get(Object key) {\n        return delegate.get(key);\n    }\n\n    @Override\n    public V put(Object key, Object value) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public V remove(Object key) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void putAll(@Nonnull Map m) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void clear() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    @Nonnull\n    public Set<K> keySet() {\n        return delegate.keySet();\n    }\n\n    @Override\n    @Nonnull\n    public Collection<V> values() {\n        return delegate.values();\n    }\n\n    @Override\n    @Nonnull\n    public Set<Entry<K, V>> entrySet() {\n        return delegate.entrySet();\n    }\n\n    public static <K, V> Builder<K, V> builder() {\n        return new Builder<K, V>();\n    }\n\n    public static class Builder<K, V> {\n        Map<K, V> result = new HashMap<>();\n\n        public Builder<K, V> put(K k, V v) {\n            result.put(k, v);\n            return this;\n        }\n\n        public Map<K, V> build() {\n            return new ImmutableMap<>(result);\n        }\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/utils/NoNull.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.utils;\n\npublic class NoNull {\n\n    /**\n     * Checks that the specified object reference is not {@code null} and\n     * return a default value if it is. This method is designed primarily\n     * for doing verify the return value in methods that returns a\n     * non-empty instance,  as demonstrated below:\n     * <blockquote><pre>\n     * public String getFoo() {\n     *     return NoNull.of(this.bar, \"default\");\n     * }\n     * </pre></blockquote>\n     *\n     * @param o            the object reference to check for nullity\n     * @param defaultValue default value to be used in the event\n     *                     that {@code o} is {@code null}\n     * @param <O>          the type of the reference\n     * @return {@code o} if not {@code null} else {@code defaultValue}\n     */\n    public static <O> O of(O o, O defaultValue) {\n        return o == null ? defaultValue : o;\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/utils/Pair.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.utils;\n\npublic class Pair<K, V> {\n    private final K key;\n    private final V value;\n\n    public Pair(K key, V value) {\n        this.key = key;\n        this.value = value;\n    }\n\n    public K getKey() {\n        return key;\n    }\n\n    public V getValue() {\n        return value;\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/utils/SystemClock.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.utils;\n\nimport com.megaease.easeagent.plugin.async.AgentThreadFactory;\n\nimport java.sql.Timestamp;\nimport java.util.concurrent.ScheduledThreadPoolExecutor;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicLong;\n\n/**\n * <p>\n * Optimization of System.currentTimeMillis() performance problem in high concurrency scenario\n *\n * reference:\n * https://pzemtsov.github.io/2017/07/23/the-slow-currenttimemillis.html\n * https://programmer.group/5e85bd0cc8b52.html\n * </p>\n */\npublic class SystemClock {\n    // update frequency\n    private final long period;\n    // currentTimeMillis\n    private final AtomicLong now;\n\n    private SystemClock(long period) {\n        this.period = period;\n        this.now = new AtomicLong(System.currentTimeMillis());\n        scheduleClockUpdating();\n    }\n\n    private static SystemClock instance() {\n        return InstanceHolder.INSTANCE;\n    }\n\n    public static long now() {\n        return instance().currentTimeMillis();\n    }\n\n    @SuppressWarnings(\"unused\")\n    public static String nowDate() {\n        return new Timestamp(instance().currentTimeMillis()).toString();\n    }\n\n    private void scheduleClockUpdating() {\n        ScheduledThreadPoolExecutor scheduler = new ScheduledThreadPoolExecutor(1,\n            new AgentThreadFactory() {\n            @Override\n            public Thread newThread(Runnable r) {\n                Thread thread = new Thread(r, \"System Clock\");\n                thread.setDaemon(true);\n                return thread;\n            }\n        });\n\n        scheduler.scheduleAtFixedRate(\n            () -> now.set(System.currentTimeMillis()),\n            period, period, TimeUnit.MILLISECONDS);\n    }\n\n    private long currentTimeMillis() {\n        return now.get();\n    }\n\n    private static class InstanceHolder {\n        public static final SystemClock INSTANCE = new SystemClock(1);\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/utils/SystemEnv.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.utils;\n\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\n\npublic class SystemEnv {\n    private static final Map<String, String> ENVIRONMENTS = new ConcurrentHashMap<>();\n\n    public static String get(String name) {\n        String value = ENVIRONMENTS.get(name);\n        if (value != null) {\n            return value;\n        }\n        String result = System.getenv(name);\n        if (result == null) {\n            return null;\n        }\n        synchronized (ENVIRONMENTS) {\n            value = ENVIRONMENTS.get(name);\n            if (value != null) {\n                return value;\n            }\n            value = result;\n            ENVIRONMENTS.put(name, value);\n            return value;\n        }\n    }\n\n    public static void set(String name, String value) {\n        ENVIRONMENTS.put(name, value);\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/utils/common/DataSize.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.utils.common;\n\npublic class DataSize {\n    /**\n     * Bytes per Kilobyte.\n     */\n    private static final long BYTES_PER_KB = 1024;\n\n    /**\n     * Bytes per Megabyte.\n     */\n    private static final long BYTES_PER_MB = BYTES_PER_KB * 1024;\n\n    /**\n     * Bytes per Gigabyte.\n     */\n    private static final long BYTES_PER_GB = BYTES_PER_MB * 1024;\n\n    /**\n     * Bytes per Terabyte.\n     */\n    private static final long BYTES_PER_TB = BYTES_PER_GB * 1024;\n\n\n    private final long bytes;\n\n\n    private DataSize(long bytes) {\n        this.bytes = bytes;\n    }\n\n\n    /**\n     * Obtain a {@link DataSize} representing the specified number of bytes.\n     *\n     * @param bytes the number of bytes, positive or negative\n     * @return a {@link DataSize}\n     */\n    public static DataSize ofBytes(long bytes) {\n        return new DataSize(bytes);\n    }\n\n    /**\n     * Obtain a {@link DataSize} representing the specified number of kilobytes.\n     *\n     * @param kilobytes the number of kilobytes, positive or negative\n     * @return a {@link DataSize}\n     */\n    public static DataSize ofKilobytes(long kilobytes) {\n        return new DataSize(Math.multiplyExact(kilobytes, BYTES_PER_KB));\n    }\n\n    /**\n     * Obtain a {@link DataSize} representing the specified number of megabytes.\n     *\n     * @param megabytes the number of megabytes, positive or negative\n     * @return a {@link DataSize}\n     */\n    public static DataSize ofMegabytes(long megabytes) {\n        return new DataSize(Math.multiplyExact(megabytes, BYTES_PER_MB));\n    }\n\n    /**\n     * Obtain a {@link DataSize} representing the specified number of gigabytes.\n     *\n     * @param gigabytes the number of gigabytes, positive or negative\n     * @return a {@link DataSize}\n     */\n    public static DataSize ofGigabytes(long gigabytes) {\n        return new DataSize(Math.multiplyExact(gigabytes, BYTES_PER_GB));\n    }\n\n    /**\n     * Obtain a {@link DataSize} representing the specified number of terabytes.\n     *\n     * @param terabytes the number of terabytes, positive or negative\n     * @return a {@link DataSize}\n     */\n    public static DataSize ofTerabytes(long terabytes) {\n        return new DataSize(Math.multiplyExact(terabytes, BYTES_PER_TB));\n    }\n\n    /**\n     * Obtain a {@link DataSize} representing an amount in the specified {@link DataUnit}.\n     *\n     * @param amount the amount of the size, measured in terms of the unit,\n     *               positive or negative\n     * @return a corresponding {@link DataSize}\n     */\n    public static DataSize of(long amount, DataUnit unit) {\n        assertNotNull(unit, \"Unit must not be null\");\n        return new DataSize(Math.multiplyExact(amount, unit.size().toBytes()));\n    }\n\n    /**\n     * Checks if this size is negative, excluding zero.\n     *\n     * @return true if this size has a size less than zero bytes\n     */\n    public boolean isNegative() {\n        return this.bytes < 0;\n    }\n\n    /**\n     * Return the number of bytes in this instance.\n     *\n     * @return the number of bytes\n     */\n    public long toBytes() {\n        return this.bytes;\n    }\n\n    /**\n     * Return the number of kilobytes in this instance.\n     *\n     * @return the number of kilobytes\n     */\n    public long toKilobytes() {\n        return this.bytes / BYTES_PER_KB;\n    }\n\n    /**\n     * Return the number of megabytes in this instance.\n     *\n     * @return the number of megabytes\n     */\n    public long toMegabytes() {\n        return this.bytes / BYTES_PER_MB;\n    }\n\n    /**\n     * Return the number of gigabytes in this instance.\n     *\n     * @return the number of gigabytes\n     */\n    public long toGigabytes() {\n        return this.bytes / BYTES_PER_GB;\n    }\n\n    /**\n     * Return the number of terabytes in this instance.\n     *\n     * @return the number of terabytes\n     */\n    public long toTerabytes() {\n        return this.bytes / BYTES_PER_TB;\n    }\n\n    @Override\n    public String toString() {\n        return String.format(\"%dB\", this.bytes);\n    }\n\n\n    @Override\n    public boolean equals(Object other) {\n        if (this == other) {\n            return true;\n        }\n        if (other == null || getClass() != other.getClass()) {\n            return false;\n        }\n        DataSize otherSize = (DataSize) other;\n        return (this.bytes == otherSize.bytes);\n    }\n\n    @Override\n    public int hashCode() {\n        return Long.hashCode(this.bytes);\n    }\n\n    private static void assertNotNull(Object object, String message) {\n        if (object == null) {\n            throw new IllegalArgumentException(message);\n        }\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/utils/common/DataUnit.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.utils.common;\n\npublic enum DataUnit {\n\n    /**\n     * Bytes, represented by suffix {@code B}.\n     */\n    BYTES(\"B\", DataSize.ofBytes(1)),\n\n    /**\n     * Kilobytes, represented by suffix {@code KB}.\n     */\n    KILOBYTES(\"KB\", DataSize.ofKilobytes(1)),\n\n    /**\n     * Megabytes, represented by suffix {@code MB}.\n     */\n    MEGABYTES(\"MB\", DataSize.ofMegabytes(1)),\n\n    /**\n     * Gigabytes, represented by suffix {@code GB}.\n     */\n    GIGABYTES(\"GB\", DataSize.ofGigabytes(1)),\n\n    /**\n     * Terabytes, represented by suffix {@code TB}.\n     */\n    TERABYTES(\"TB\", DataSize.ofTerabytes(1));\n\n\n    private final String suffix;\n\n    private final DataSize size;\n\n\n    DataUnit(String suffix, DataSize size) {\n        this.suffix = suffix;\n        this.size = size;\n    }\n\n    DataSize size() {\n        return this.size;\n    }\n\n    /**\n     * Return the {@link DataUnit} matching the specified {@code suffix}.\n     *\n     * @param suffix one of the standard suffixes\n     * @return the {@link DataUnit} matching the specified {@code suffix}\n     * @throws IllegalArgumentException if the suffix does not match the suffix\n     *                                  of any of this enum's constants\n     */\n    public static DataUnit fromSuffix(String suffix) {\n        for (DataUnit candidate : values()) {\n            if (candidate.suffix.equals(suffix)) {\n                return candidate;\n            }\n        }\n        throw new IllegalArgumentException(\"Unknown data unit suffix '\" + suffix + \"'\");\n    }\n\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/utils/common/ExceptionUtil.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.utils.common;\n\nimport java.io.PrintWriter;\nimport java.io.StringWriter;\n\n@SuppressWarnings(\"unused\")\npublic class ExceptionUtil {\n    public static String getExceptionMessage(Throwable throwable) {\n        return throwable.getMessage() != null ? throwable.getMessage() : throwable.toString();\n    }\n\n    public static String getExceptionStacktrace(Throwable e) {\n        if (e == null) {\n            return \"\";\n        }\n        StringWriter sw = new StringWriter();\n        PrintWriter pw = new PrintWriter(sw);\n        e.printStackTrace(pw);\n\n        return sw.toString();\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/utils/common/HostAddress.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.utils.common;\n\nimport java.net.*;\nimport java.util.Enumeration;\n\npublic abstract class HostAddress {\n    private static final String LOCALHOST_NAME;\n    private static final String IPV4;\n    private static final String UNKNOWN_LOCALHOST = \"UNKNOWN_LOCALHOST\";\n\n    static {\n        IPV4 = initHostIpv4();\n        LOCALHOST_NAME = initLocalHostname();\n    }\n\n    public static String localhost() {\n        return LOCALHOST_NAME;\n    }\n\n    public static String getHostIpv4() {\n        return IPV4;\n    }\n\n    public static String initHostIpv4() {\n        try {\n            return getHostIpV4(NetworkInterface.getNetworkInterfaces());\n        } catch (Exception ignored) {\n        }\n        return \"UnknownIP\";\n    }\n\n    private static String getHostIpV4(Enumeration<NetworkInterface> networkInterfaces) throws Exception {\n        String ip;\n        String secondaryIP = \"\";\n        while (networkInterfaces.hasMoreElements()) {\n            NetworkInterface i = networkInterfaces.nextElement();\n            if (i.isLoopback()) {\n                continue;\n            }\n            if (isPrimaryInterface(i)) {\n                // We treat interface name which started with \"en\" or \"eth\" as primary interface.\n                // We prefer to use address of primary interface as value of the `host_ipv4`\n                ip = ipAddressFromInetAddress(i);\n                if (!StringUtils.isEmpty(ip)) {\n                    return ip;\n                }\n            } else if (StringUtils.isEmpty(secondaryIP)) {\n                secondaryIP = ipAddressFromInetAddress(i);\n            }\n\n        }\n        return !StringUtils.isEmpty(secondaryIP) ? secondaryIP : \"UnknownIP\";\n    }\n\n    private static boolean isPrimaryInterface(NetworkInterface i) {\n        return i.getName().startsWith(\"en\") || i.getName().startsWith(\"eth\");\n    }\n\n    private static String ipAddressFromInetAddress(NetworkInterface i) {\n        Enumeration<InetAddress> ee = i.getInetAddresses();\n        while (ee.hasMoreElements()) {\n            InetAddress a = ee.nextElement();\n            if (a instanceof Inet4Address) {\n                if (!a.isMulticastAddress() && !a.isLoopbackAddress()) {\n                    return a.getHostAddress();\n                }\n            }\n        }\n        return \"\";\n    }\n\n    public static String initLocalHostname() {\n        // copy from log4j NetUtils\n        try {\n            final InetAddress addr = InetAddress.getLocalHost();\n            return addr == null ? UNKNOWN_LOCALHOST : addr.getHostName();\n        } catch (final UnknownHostException uhe) {\n            try {\n                final Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();\n                if (interfaces != null) {   // NetworkInterface.getNetworkInterfaces impl is different in different jdk\n                    while (interfaces.hasMoreElements()) {\n                        final NetworkInterface nic = interfaces.nextElement();\n                        final Enumeration<InetAddress> addresses = nic.getInetAddresses();\n                        while (addresses.hasMoreElements()) {\n                            final InetAddress address = addresses.nextElement();\n                            if (!address.isLoopbackAddress()) {\n                                final String hostname = address.getHostName();\n                                if (hostname != null) {\n                                    return hostname;\n                                }\n                            }\n                        }\n                    }\n                }\n            } catch (final SocketException se) {\n                return UNKNOWN_LOCALHOST;\n            }\n            return UNKNOWN_LOCALHOST;\n        }\n    }\n\n    private HostAddress() {\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/utils/common/JsonUtil.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.utils.common;\n\nimport com.fasterxml.jackson.core.JsonParser;\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.core.type.TypeReference;\nimport com.fasterxml.jackson.databind.DeserializationFeature;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.megaease.easeagent.plugin.api.logging.Logger;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\n\nimport java.util.List;\nimport java.util.Map;\n\npublic class JsonUtil {\n    static final ObjectMapper mapper = new ObjectMapper();\n    private static final Logger logger = EaseAgent.loggerFactory.getLogger(JsonUtil.class);\n\n    static {\n        mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);\n        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);\n    }\n\n    // expensive call\n    public static String toJson(Object obj) {\n        try {\n            return mapper.writeValueAsString(obj);\n        } catch (JsonProcessingException e) {\n            logger.warn(\"data to json error: {}\", e.getMessage());\n            return null;\n        }\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    public static Map<String, Object> toMap(String json) {\n        try {\n            return mapper.readValue(json, Map.class);\n        } catch (JsonProcessingException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    public static <T> List<T> toList(String json) {\n        try {\n            return mapper.readValue(json, new TypeReference<List<T>>() {\n            });\n        } catch (JsonProcessingException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    public static <T> T toObject(String json, TypeReference<T> valueTypeRef) {\n        try {\n            return mapper.readValue(json, valueTypeRef);\n        } catch (JsonProcessingException e) {\n            throw new RuntimeException(e);\n        }\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/utils/common/StringUtils.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.utils.common;\n\nimport java.nio.charset.StandardCharsets;\nimport java.util.ArrayList;\nimport java.util.List;\n\n@SuppressWarnings(\"unused\")\npublic class StringUtils {\n\n    private StringUtils() {\n    }\n\n    /**\n     * <p> If the first one is empty, return the alternative value</p>\n     *\n     * @param first     first\n     * @param alternate alternative value\n     * @return String\n     */\n    public static String noEmptyOf(String first, String alternate) {\n        if (isEmpty(first)) {\n            return alternate;\n        }\n        return first;\n    }\n\n    public static String noEmptyOf(String first, String alternate, String defaultValue) {\n        if (isEmpty(first)) {\n            return noEmptyOf(alternate, defaultValue);\n        }\n        return first;\n    }\n\n    /**\n     * <p>Checks if a CharSequence is empty (\"\") or null.</p>\n     *\n     * <pre>\n     * StringUtils.isEmpty(null)      = true\n     * StringUtils.isEmpty(\"\")        = true\n     * StringUtils.isEmpty(\" \")       = false\n     * StringUtils.isEmpty(\"bob\")     = false\n     * StringUtils.isEmpty(\"  bob  \") = false\n     * </pre>\n     *\n     * <p>NOTE: This method changed in Lang version 2.0.\n     * It no longer trims the CharSequence.\n     * That functionality is available in isBlank().</p>\n     *\n     * @param cs the CharSequence to check, may be null\n     * @return {@code true} if the CharSequence is empty or null\n     * @since 3.0 Changed signature from isEmpty(String) to isEmpty(CharSequence)\n     */\n    public static boolean isEmpty(final CharSequence cs) {\n        return cs == null || cs.length() == 0;\n    }\n\n    /**\n     * <p>Checks if a CharSequence is not empty (\"\") and not null.</p>\n     *\n     * <pre>\n     * StringUtils.isNotEmpty(null)      = false\n     * StringUtils.isNotEmpty(\"\")        = false\n     * StringUtils.isNotEmpty(\" \")       = true\n     * StringUtils.isNotEmpty(\"bob\")     = true\n     * StringUtils.isNotEmpty(\"  bob  \") = true\n     * </pre>\n     *\n     * @param cs the CharSequence to check, may be null\n     * @return {@code true} if the CharSequence is not empty and not null\n     * @since 3.0 Changed signature from isNotEmpty(String) to isNotEmpty(CharSequence)\n     */\n    public static boolean isNotEmpty(final CharSequence cs) {\n        return !isEmpty(cs);\n    }\n\n    /**\n     * <p>Checks if the CharSequence contains only Unicode digits.\n     * A decimal point is not a Unicode digit and returns false.</p>\n     *\n     * <p>{@code null} will return {@code false}.\n     * An empty CharSequence (length()=0) will return {@code false}.</p>\n     *\n     * <p>Note that the method does not allow for a leading sign, either positive or negative.\n     * Also, if a String passes the numeric test, it may still generate a NumberFormatException\n     * when parsed by Integer.parseInt or Long.parseLong, e.g. if the value is outside the range\n     * for int or long respectively.</p>\n     *\n     * <pre>\n     * StringUtils.isNumeric(null)   = false\n     * StringUtils.isNumeric(\"\")     = false\n     * StringUtils.isNumeric(\"  \")   = false\n     * StringUtils.isNumeric(\"123\")  = true\n     * StringUtils.isNumeric(\"\\u0967\\u0968\\u0969\")  = true\n     * StringUtils.isNumeric(\"12 3\") = false\n     * StringUtils.isNumeric(\"ab2c\") = false\n     * StringUtils.isNumeric(\"12-3\") = false\n     * StringUtils.isNumeric(\"12.3\") = false\n     * StringUtils.isNumeric(\"-123\") = false\n     * StringUtils.isNumeric(\"+123\") = false\n     * </pre>\n     *\n     * @param cs the CharSequence to check, may be null\n     * @return {@code true} if only contains digits, and is non-null\n     * @since 3.0 Changed signature from isNumeric(String) to isNumeric(CharSequence)\n     * @since 3.0 Changed \"\" to return false and not true\n     */\n    public static boolean isNumeric(final CharSequence cs) {\n        if (isEmpty(cs)) {\n            return false;\n        }\n        final int sz = cs.length();\n        for (int i = 0; i < sz; i++) {\n            if (!Character.isDigit(cs.charAt(i))) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    /**\n     * <p>Checks if the CharSequence contains only Unicode digits or space\n     * ({@code ' '}).\n     * A decimal point is not a Unicode digit and returns false.</p>\n     *\n     * <p>{@code null} will return {@code false}.\n     * An empty CharSequence (length()=0) will return {@code true}.</p>\n     *\n     * <pre>\n     * StringUtils.isNumericSpace(null)   = false\n     * StringUtils.isNumericSpace(\"\")     = true\n     * StringUtils.isNumericSpace(\"  \")   = true\n     * StringUtils.isNumericSpace(\"123\")  = true\n     * StringUtils.isNumericSpace(\"12 3\") = true\n     * StringUtils.isNumeric(\"\\u0967\\u0968\\u0969\")  = true\n     * StringUtils.isNumeric(\"\\u0967\\u0968 \\u0969\")  = true\n     * StringUtils.isNumericSpace(\"ab2c\") = false\n     * StringUtils.isNumericSpace(\"12-3\") = false\n     * StringUtils.isNumericSpace(\"12.3\") = false\n     * </pre>\n     *\n     * @param cs the CharSequence to check, may be null\n     * @return {@code true} if only contains digits or space,\n     * and is non-null\n     * @since 3.0 Changed signature from isNumericSpace(String) to isNumericSpace(CharSequence)\n     */\n    public static boolean isNumericSpace(final CharSequence cs) {\n        if (cs == null) {\n            return false;\n        }\n        final int sz = cs.length();\n        for (int i = 0; i < sz; i++) {\n            if (!Character.isDigit(cs.charAt(i)) && cs.charAt(i) != ' ') {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    /**\n     * <p>Checks if the CharSequence contains only whitespace.</p>\n     *\n     * <p>Whitespace is defined by {@link Character#isWhitespace(char)}.</p>\n     *\n     * <p>{@code null} will return {@code false}.\n     * An empty CharSequence (length()=0) will return {@code true}.</p>\n     *\n     * <pre>\n     * StringUtils.isWhitespace(null)   = false\n     * StringUtils.isWhitespace(\"\")     = true\n     * StringUtils.isWhitespace(\"  \")   = true\n     * StringUtils.isWhitespace(\"abc\")  = false\n     * StringUtils.isWhitespace(\"ab2c\") = false\n     * StringUtils.isWhitespace(\"ab-c\") = false\n     * </pre>\n     *\n     * @param cs the CharSequence to check, may be null\n     * @return {@code true} if only contains whitespace, and is non-null\n     * @since 2.0\n     * @since 3.0 Changed signature from isWhitespace(String) to isWhitespace(CharSequence)\n     */\n    public static boolean isWhitespace(final CharSequence cs) {\n        if (cs == null) {\n            return false;\n        }\n        final int sz = cs.length();\n        for (int i = 0; i < sz; i++) {\n            if (!Character.isWhitespace(cs.charAt(i))) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    public static String cutStrByDataSize(String str, DataSize size) {\n        byte[] now = str.getBytes(StandardCharsets.UTF_8);\n        if (now.length <= size.toBytes()) {\n            return str;\n        }\n        String tmp = new String(now, 0, (int) size.toBytes(), StandardCharsets.UTF_8);\n        char unstable = tmp.charAt(tmp.length() - 1);\n        char old = str.charAt(tmp.length() - 1);\n        if (unstable == old) {\n            return tmp;\n        }\n        return new String(tmp.toCharArray(), 0, tmp.length() - 1);\n    }\n\n    public static boolean hasText(String val) {\n        return val != null && val.trim().length() > 0;\n    }\n\n    public static String[] split(final String str, final String separatorChars) {\n        if (isEmpty(str)) {\n            return null;\n        }\n        return str.split(separatorChars);\n    }\n\n    public static String replaceSuffix(String origin, String suffix) {\n        int idx = origin.lastIndexOf('.');\n        if (idx == 0) {\n            return suffix;\n        } else {\n            return origin.substring(0, idx + 1) + suffix;\n        }\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/utils/common/WeakConcurrentMap.java",
    "content": "\npackage com.megaease.easeagent.plugin.utils.common;\n\n/*\n * Copyright 2013-2020 The OpenZipkin Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except\n * in compliance with the License. You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under the License\n * is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express\n * or implied. See the License for the specific language governing permissions and limitations under\n * the License.\n */\n\nimport java.lang.ref.Reference;\nimport java.lang.ref.ReferenceQueue;\nimport java.lang.ref.WeakReference;\nimport java.util.AbstractMap;\nimport java.util.Iterator;\nimport java.util.Map;\nimport java.util.NoSuchElementException;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.ConcurrentMap;\n\n/**\n * This borrows heavily from Rafael Winterhalter's {@code com.blogspot.mydailyjava.weaklockfree.WeakConcurrentMap}\n * with the following major changes:\n *\n * <p>The biggest change is this removes LatentKey. Instead it relies on keys known to have a\n * stable {@link #hashCode} and who are {@linkplain #equals(Object) equal to} a weak reference of\n * itself. We allow lookups using externally created contexts, yet don't want to incur overhead of\n * key allocation or classloader problems sharing keys with a thread local.\n *\n * <p>Other changes mostly remove features (to reduce the bytecode size) and address style:\n * <ul>\n *   <li>Inline expunction only as we have no thread to use anyway</li>\n *   <li>Stylistic changes including different javadoc and removal of private modifiers</li>\n *   <li>toString: derived only from keys</li>\n * </ul>\n *\n * <p>See https://github.com/raphw/weak-lock-free\n */\n@SuppressWarnings(\"unused\")\npublic class WeakConcurrentMap<K, V> extends ReferenceQueue<K> implements Iterable<Map.Entry<K, V>> {\n    final ConcurrentMap<WeakKey<K>, V> target = new ConcurrentHashMap<>();\n\n    public V getIfPresent(K key) {\n        if (key == null) throw new NullPointerException(\"key == null\");\n        expungeStaleEntries();\n\n        return target.get(new WrapKeyForGet(key));\n    }\n\n    /** Replaces the entry with the indicated key and returns the old value or {@code null}. */\n    public V putIfProbablyAbsent(K key, V value) {\n        if (key == null) throw new NullPointerException(\"key == null\");\n        if (value == null) throw new NullPointerException(\"value == null\");\n        expungeStaleEntries();\n\n        return target.putIfAbsent(new WeakKey<>(key, this), value);\n    }\n\n    /** Removes the entry with the indicated key and returns the old value or {@code null}. */\n    public V remove(K key) {\n        if (key == null) throw new NullPointerException(\"key == null\");\n        expungeStaleEntries();\n\n        return target.remove(key);\n    }\n\n    /** Iterates over the entries in this map. */\n    @Override\n    public Iterator<Map.Entry<K, V>> iterator() {\n        return new EntryIterator(target.entrySet().iterator());\n    }\n\n    /** Cleans all unused references. */\n    protected void expungeStaleEntries() {\n        Reference<?> reference;\n        while ((reference = poll()) != null) {\n            removeStaleEntry(reference);\n        }\n    }\n\n    protected V removeStaleEntry(Reference<?> reference) {\n        return target.remove(reference);\n    }\n\n    // This comment was directly verbatim from https://github.com/raphw/weak-lock-free/blob/dcbd2fa0d30571bb3ed187a42cb75323a5569d5b/src/main/java/com/blogspot/mydailyjava/weaklockfree/WeakConcurrentMap.java#L273-L302\n    /*\n     * Why this works:\n     * ---------------\n     *\n     * Note that this map only supports reference equality for keys and uses system hash codes. Also, for the\n     * WeakKey instances to function correctly, we are voluntarily breaking the Java API contract for\n     * hashCode/equals of these instances.\n     *\n     *\n     * System hash codes are immutable and can therefore be computed prematurely and are stored explicitly\n     * within the WeakKey instances. This way, we always know the correct hash code of a key and always\n     * end up in the correct bucket of our target map. This remains true even after the weakly referenced\n     * key is collected.\n     *\n     * If we are looking up the value of the current key via WeakConcurrentMap::get or any other public\n     * API method, we know that any value associated with this key must still be in the map as the mere\n     * existence of this key makes it ineligible for garbage collection. Therefore, looking up a value\n     * using another WeakKey wrapper guarantees a correct result.\n     *\n     * If we are looking up the map entry of a WeakKey after polling it from the reference queue, we know\n     * that the actual key was already collected and calling WeakKey::get returns null for both the polled\n     * instance and the instance within the map. Since we explicitly stored the identity hash code for the\n     * referenced value, it is however trivial to identify the correct bucket. From this bucket, the first\n     * weak key with a null reference is removed. Due to hash collision, we do not know if this entry\n     * represents the weak key. However, we do know that the reference queue polls at least as many weak\n     * keys as there are stale map entries within the target map. If no key is ever removed from the map\n     * explicitly, the reference queue eventually polls exactly as many weak keys as there are stale entries.\n     *\n     * Therefore, we can guarantee that there is no memory leak.\n     */\n    public static final class WeakKey<T> extends WeakReference<T> {\n        final int hashCode;\n\n        WeakKey(T key, ReferenceQueue<? super T> queue) {\n            super(key, queue);\n            this.hashCode = key.hashCode(); // cache as hashCode is used for all future operations\n        }\n\n        @Override public int hashCode() {\n            return hashCode;\n        }\n\n        @Override public String toString() {\n            T value = get();\n            return value != null ? value.toString() : \"ClearedReference()\";\n        }\n\n        /**\n         * While a lookup key will invoke equals against this, the visa versa is not true. This method\n         * is only used inside the target map to resolve hash code collisions.\n         */\n        @Override public boolean equals(Object o) { // resolves hash code collisions\n            if (o == this) return true;\n            assert o instanceof WeakReference : \"Bug: unexpected input to equals\";\n            return equal(get(), ((WeakReference) o).get());\n        }\n    }\n\n    @Override public String toString() {\n        Class<?> thisClass = getClass();\n        while (thisClass.getSimpleName().isEmpty()) {\n            thisClass = thisClass.getSuperclass();\n        }\n        expungeStaleEntries(); // Clean up so that only present references show up (unless race lost)\n        return thisClass.getSimpleName() + target.keySet();\n    }\n\n    static boolean equal(Object a, Object b) {\n        return a == null ? b == null : a.equals(b); // Java 6 can't use Objects.equals()\n    }\n\n    class EntryIterator implements Iterator<Map.Entry<K, V>> {\n\n        private final Iterator<Map.Entry<WeakKey<K>, V>> iterator;\n\n        private Map.Entry<WeakKey<K>, V> nextEntry;\n\n        private K nextKey;\n\n        private EntryIterator(Iterator<Map.Entry<WeakKey<K>, V>> iterator) {\n            this.iterator = iterator;\n            findNext();\n        }\n\n        private void findNext() {\n            while (iterator.hasNext()) {\n                nextEntry = iterator.next();\n                nextKey = nextEntry.getKey().get();\n                if (nextKey != null) {\n                    return;\n                }\n            }\n            nextEntry = null;\n            nextKey = null;\n        }\n\n        @Override\n        public boolean hasNext() {\n            return nextKey != null;\n        }\n\n        @Override\n        public Map.Entry<K, V> next() {\n            if (nextKey == null) {\n                throw new NoSuchElementException();\n            }\n            try {\n                return new AbstractMap.SimpleImmutableEntry<K, V>(nextKey, nextEntry.getValue());\n            } finally {\n                findNext();\n            }\n        }\n\n        @Override\n        public void remove() {\n            throw new UnsupportedOperationException();\n        }\n    }\n\n    public static final class WrapKeyForGet {\n        Object key;\n        WrapKeyForGet(Object key) {\n            this.key = key;\n        }\n\n        @Override\n        public int hashCode() {\n            return this.key.hashCode();\n        }\n\n        @Override\n        public boolean equals(Object o) {\n            if (o instanceof WeakKey) {\n                WeakKey<?> wk = (WeakKey<?>) o;\n                Object oo = wk.get();\n                return this.key.equals(oo);\n            } else {\n                return this.key.equals(o);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/main/java/com/megaease/easeagent/plugin/utils/jackson/annotation/JsonProperty.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.utils.jackson.annotation;\n\nimport com.fasterxml.jackson.annotation.JacksonAnnotation;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n@Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})\n@Retention(RetentionPolicy.RUNTIME)\n@JacksonAnnotation\npublic @interface JsonProperty {\n    String USE_DEFAULT_NAME = \"\";\n    int INDEX_UNKNOWN = -1;\n\n    String value() default \"\";\n\n    String namespace() default \"\";\n\n    boolean required() default false;\n\n    int index() default -1;\n\n    String defaultValue() default \"\";\n\n    com.fasterxml.jackson.annotation.JsonProperty.Access access() default com.fasterxml.jackson.annotation.JsonProperty.Access.AUTO;\n\n    public static enum Access {\n        AUTO,\n        READ_ONLY,\n        WRITE_ONLY,\n        READ_WRITE;\n\n        private Access() {\n        }\n    }\n}\n\n"
  },
  {
    "path": "plugin-api/src/main/java/io/opentelemetry/sdk/resources/EaseAgentResource.java",
    "content": "package io.opentelemetry.sdk.resources;\n\nimport com.megaease.easeagent.plugin.api.config.ChangeItem;\nimport com.megaease.easeagent.plugin.api.config.ConfigChangeListener;\nimport com.megaease.easeagent.plugin.api.otlp.common.AgentAttributes;\nimport com.megaease.easeagent.plugin.api.otlp.common.SemanticKey;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport io.opentelemetry.api.common.Attributes;\n\nimport javax.annotation.Nullable;\nimport java.util.List;\n\nimport static io.opentelemetry.semconv.resource.attributes.ResourceAttributes.SERVICE_NAME;\nimport static io.opentelemetry.semconv.resource.attributes.ResourceAttributes.SERVICE_NAMESPACE;\n\npublic class EaseAgentResource extends Resource implements ConfigChangeListener {\n    static volatile EaseAgentResource agentResource = null;\n\n    private EaseAgentResource() {\n        super();\n        this.system = EaseAgent.getConfig(\"system\", \"demo-system\");\n        this.service = EaseAgent.getConfig(\"name\", \"demo-service\");\n        EaseAgent.getConfig().addChangeListener(this);\n\n        this.resource = Resource.getDefault()\n            .merge(Resource.create(\n                AgentAttributes.builder()\n                    .put(SERVICE_NAME, this.service)\n                    .put(SERVICE_NAMESPACE, this.system)\n                    .build()));\n    }\n\n    private final Resource resource;\n    private String service;\n    private String system;\n\n    public String getService() {\n        return this.service;\n    }\n\n    public String getSystem() {\n        return this.system;\n    }\n\n    public static EaseAgentResource getResource() {\n        if (agentResource == null) {\n            synchronized (EaseAgentResource.class) {\n                if (agentResource == null) {\n                    agentResource = new EaseAgentResource();\n                }\n            }\n        }\n\n        return agentResource;\n    }\n\n    @Nullable\n    @Override\n    public String getSchemaUrl() {\n        return SemanticKey.SCHEMA_URL;\n    }\n\n    @Override\n    public Attributes getAttributes() {\n        return this.resource.getAttributes();\n    }\n\n    @Override\n    public void onChange(List<ChangeItem> list) {\n        list.forEach(change -> {\n            if (change.getFullName().equals(\"name\")) {\n                this.service = change.getNewValue();\n            } else if (change.getFullName().equals(\"system\")) {\n                this.system = change.getNewValue();\n            }\n\n            this.resource.merge(Resource.create(\n                Attributes.builder()\n                    .put(SERVICE_NAME, this.service)\n                    .put(SERVICE_NAMESPACE, this.system)\n                    .build()));\n        });\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/test/java/com/megaease/easeagent/plugin/api/MockSystemEnv.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api;\n\nimport java.lang.reflect.Field;\nimport java.util.Map;\n\npublic class MockSystemEnv {\n    private static final Map<String, String> SETTER = getEnvironments();\n\n\n    @SuppressWarnings(\"all\")\n    private static Map<String, String> getEnvironments() {\n        try {\n            Class systemEnv = Thread.currentThread().getContextClassLoader().loadClass(\"com.megaease.easeagent.plugin.utils.SystemEnv\");\n            Field environments = systemEnv.getDeclaredField(\"ENVIRONMENTS\");\n            environments.setAccessible(true);\n            return (Map<String, String>) environments.get(null);\n        } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException e) {\n            e.printStackTrace();\n        }\n        return null;\n    }\n\n    public static void set(String name, String value) {\n        if (SETTER != null) {\n            SETTER.put(name, value);\n        }\n    }\n\n    public static void remove(String name) {\n        if (SETTER != null) {\n            SETTER.remove(name);\n        }\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/test/java/com/megaease/easeagent/plugin/api/ProgressFieldsTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api;\n\nimport org.junit.Test;\n\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static com.megaease.easeagent.plugin.api.ProgressFields.*;\nimport static org.junit.Assert.*;\n\npublic class ProgressFieldsTest {\n\n    @Test\n    public void changeListener() {\n        getForwardedHeaders();\n    }\n\n    @Test\n    public void isProgressFields() {\n        assertFalse(ProgressFields.isProgressFields(EASEAGENT_PROGRESS_FORWARDED_HEADERS_CONFIG + \"abc\"));\n        assertTrue(ProgressFields.isProgressFields(EASEAGENT_PROGRESS_FORWARDED_HEADERS_CONFIG));\n        assertTrue(ProgressFields.isProgressFields(OBSERVABILITY_TRACINGS_TAG_RESPONSE_HEADERS_CONFIG + \"abc\"));\n        assertTrue(ProgressFields.isProgressFields(OBSERVABILITY_TRACINGS_SERVICE_TAGS_CONFIG + \"abc\"));\n        assertFalse(ProgressFields.isProgressFields(\"c\" + OBSERVABILITY_TRACINGS_SERVICE_TAGS_CONFIG + \"abc\"));\n    }\n\n    @Test\n    public void isEmpty() {\n        assertTrue(true);\n        assertTrue(ProgressFields.isEmpty(new String[0]));\n        assertFalse(ProgressFields.isEmpty(new String[1]));\n    }\n\n    @Test\n    public void getForwardedHeaders() {\n        String key = EASEAGENT_PROGRESS_FORWARDED_HEADERS_CONFIG;\n        assertTrue(ProgressFields.getForwardedHeaders().isEmpty());\n        ProgressFields.changeListener().accept(Collections.singletonMap(key, \"a,b,c\"));\n        assertFalse(ProgressFields.getForwardedHeaders().isEmpty());\n        assertEquals(3, ProgressFields.getForwardedHeaders().size());\n        assertTrue(ProgressFields.getForwardedHeaders().contains(\"b\"));\n        ProgressFields.changeListener().accept(Collections.singletonMap(key, \"a,b\"));\n        assertFalse(ProgressFields.getForwardedHeaders().isEmpty());\n        assertTrue(ProgressFields.getForwardedHeaders().contains(\"a\"));\n        assertTrue(ProgressFields.getForwardedHeaders().contains(\"b\"));\n        assertFalse(ProgressFields.getForwardedHeaders().contains(\"c\"));\n        ProgressFields.changeListener().accept(Collections.singletonMap(key, \"\"));\n        assertTrue(ProgressFields.getForwardedHeaders().isEmpty());\n    }\n\n    @Test\n    public void getResponseHoldTagFields() {\n        String keyPrefix = OBSERVABILITY_TRACINGS_TAG_RESPONSE_HEADERS_CONFIG;\n        assertTrue(ProgressFields.isEmpty(ProgressFields.getResponseHoldTagFields()));\n        ProgressFields.changeListener().accept(Collections.singletonMap(keyPrefix + \"aaa\", \"bbb\"));\n        assertFalse(ProgressFields.isEmpty(ProgressFields.getResponseHoldTagFields()));\n        assertEquals(1, ProgressFields.getResponseHoldTagFields().length);\n        assertEquals(\"bbb\", ProgressFields.getResponseHoldTagFields()[0]);\n        ProgressFields.changeListener().accept(Collections.singletonMap(keyPrefix + \"aaa\", \"\"));\n        assertTrue(ProgressFields.isEmpty(ProgressFields.getResponseHoldTagFields()));\n        Map<String, String> map = new HashMap<>();\n        map.put(keyPrefix + \"aaa\", \"bbb\");\n        map.put(keyPrefix + \"ccc\", \"ddd\");\n        map.put(keyPrefix + \"ffff\", \"fff\");\n        ProgressFields.changeListener().accept(map);\n        assertEquals(3, ProgressFields.getResponseHoldTagFields().length);\n        map.replaceAll((s, v) -> \"\");\n        ProgressFields.changeListener().accept(map);\n        assertTrue(ProgressFields.isEmpty(ProgressFields.getResponseHoldTagFields()));\n    }\n\n    @Test\n    public void getServerTags() {\n        String keyPrefix = OBSERVABILITY_TRACINGS_SERVICE_TAGS_CONFIG;\n        assertTrue(ProgressFields.getServiceTags().isEmpty());\n        ProgressFields.changeListener().accept(Collections.singletonMap(keyPrefix + \"aaa\", \"bbb\"));\n        assertFalse(ProgressFields.getServiceTags().isEmpty());\n        assertEquals(1, ProgressFields.getServiceTags().size());\n        assertEquals(\"bbb\", ProgressFields.getServiceTags().get(\"aaa\"));\n        ProgressFields.changeListener().accept(Collections.singletonMap(keyPrefix + \"aaa\", \"\"));\n        assertTrue(ProgressFields.getServiceTags().isEmpty());\n        Map<String, String> map = new HashMap<>();\n        map.put(keyPrefix + \"aaa\", \"bbb\");\n        map.put(keyPrefix + \"ccc\", \"ddd\");\n        map.put(keyPrefix + \"ffff\", \"fff\");\n        ProgressFields.changeListener().accept(map);\n        assertEquals(3, ProgressFields.getServiceTags().size());\n        assertEquals(\"bbb\", ProgressFields.getServiceTags().get(\"aaa\"));\n        assertEquals(\"ddd\", ProgressFields.getServiceTags().get(\"ccc\"));\n        assertEquals(\"fff\", ProgressFields.getServiceTags().get(\"ffff\"));\n        map.replaceAll((s, v) -> \"\");\n        ProgressFields.changeListener().accept(map);\n        assertTrue(ProgressFields.getServiceTags().isEmpty());\n    }\n\n\n}\n"
  },
  {
    "path": "plugin-api/src/test/java/com/megaease/easeagent/plugin/api/metric/name/NameFactoryTest.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.metric.name;\n\nimport com.megaease.easeagent.plugin.utils.ImmutableMap;\nimport org.junit.Test;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\npublic class NameFactoryTest {\n    Map<Key, ? extends A> keyTMap = new HashMap<>();\n\n    @Test\n    public void createBuilder() {\n        NameFactory nameFactory = NameFactory.createBuilder().counterType(MetricSubType.DEFAULT, ImmutableMap.<MetricField, MetricValueFetcher>builder()\n            .put(MetricField.EXECUTION_COUNT, MetricValueFetcher.CountingCount)\n            .build()).build();\n        String key = nameFactory.counterName(\"test_key\", MetricSubType.DEFAULT);\n        System.out.println(key);\n\n        Key key1 = new Key();\n        B a = (B) keyTMap.get(key1);\n    }\n\n\n    class Key {\n\n    }\n\n    interface A {\n    }\n\n    class B implements A {\n\n    }\n\n}\n"
  },
  {
    "path": "plugin-api/src/test/java/com/megaease/easeagent/plugin/api/middleware/RedirectProcessorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.middleware;\n\nimport com.megaease.easeagent.plugin.api.MockSystemEnv;\nimport org.junit.Test;\n\nimport static com.megaease.easeagent.plugin.api.middleware.RedirectProcessor.ENV_EASEMESH_TAGS;\nimport static org.junit.Assert.*;\n\npublic class RedirectProcessorTest {\n\n    @Test\n    public void getServerTagsFromEnv() {\n        assertNotEquals(RedirectProcessor.getServiceTagsFromEnv(), null);\n        assertTrue(RedirectProcessor.getServiceTagsFromEnv().isEmpty());\n        MockSystemEnv.set(ENV_EASEMESH_TAGS, \"\");\n        assertTrue(RedirectProcessor.getServiceTagsFromEnv().isEmpty());\n        MockSystemEnv.set(ENV_EASEMESH_TAGS, \"{\\\"a\\\":\\\"b\\\"}\");\n        assertFalse(RedirectProcessor.getServiceTagsFromEnv().isEmpty());\n        assertEquals(\"b\", RedirectProcessor.getServiceTagsFromEnv().get(\"a\"));\n        MockSystemEnv.set(ENV_EASEMESH_TAGS, \"aaa\");\n        assertTrue(RedirectProcessor.getServiceTagsFromEnv().isEmpty());\n        MockSystemEnv.set(ENV_EASEMESH_TAGS, \"{\\\"a\\\":null}\");\n        assertTrue(RedirectProcessor.getServiceTagsFromEnv().isEmpty());\n\n        MockSystemEnv.set(ENV_EASEMESH_TAGS, \"\");\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/test/java/com/megaease/easeagent/plugin/api/middleware/RedirectTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.middleware;\n\nimport com.megaease.easeagent.plugin.api.MockSystemEnv;\nimport org.junit.Before;\nimport org.junit.Test;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertTrue;\n\npublic class RedirectTest {\n\n\n    @Before\n    public void before() {\n        MockSystemEnv.set(MiddlewareConstants.ENV_REDIS, \"{\\\"uris\\\":\\\"localhost:6379\\\"}\");\n        MockSystemEnv.set(MiddlewareConstants.ENV_ES, \"{\\\"uris\\\":\\\"127.0.0.1:9200\\\"}\");\n        MockSystemEnv.set(MiddlewareConstants.ENV_KAFKA, \"{\\\"uris\\\":\\\"127.0.0.1:9092\\\", \\\"tags\\\": {\\\"label.local\\\": \\\"shadow\\\"}}\");\n        MockSystemEnv.set(MiddlewareConstants.ENV_RABBITMQ, \"{\\\"uris\\\":\\\"localhost:5672\\\"}\");\n        MockSystemEnv.set(MiddlewareConstants.ENV_DATABASE, \"{\\\"uris\\\":\\\"jdbc:mysql://localhost:3306/db_demo?useUnicode=true&characterEncoding=utf-8&autoReconnectForPools=true&autoReconnect=true\\\"}\");\n    }\n\n    @Test\n    public void hasConfig() {\n        assertTrue(Redirect.REDIS.hasConfig());\n        assertTrue(Redirect.ELASTICSEARCH.hasConfig());\n        assertTrue(Redirect.KAFKA.hasConfig());\n        assertTrue(Redirect.RABBITMQ.hasConfig());\n        assertTrue(Redirect.DATABASE.hasConfig());\n    }\n\n    @Test\n    public void getConfig() {\n        assertEquals(\"localhost:6379\", Redirect.REDIS.getConfig().getUris());\n        assertEquals(\"127.0.0.1:9200\", Redirect.ELASTICSEARCH.getConfig().getUris());\n        assertEquals(\"127.0.0.1:9092\", Redirect.KAFKA.getConfig().getUris());\n        assertEquals(\"localhost:5672\", Redirect.RABBITMQ.getConfig().getUris());\n        assertEquals(\"jdbc:mysql://localhost:3306/db_demo?useUnicode=true&characterEncoding=utf-8&autoReconnectForPools=true&autoReconnect=true\", Redirect.DATABASE.getConfig().getUris());\n    }\n\n}\n"
  },
  {
    "path": "plugin-api/src/test/java/com/megaease/easeagent/plugin/api/middleware/ResourceConfigTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.api.middleware;\n\nimport com.megaease.easeagent.plugin.api.MockSystemEnv;\nimport com.megaease.easeagent.plugin.utils.SystemEnv;\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\npublic class ResourceConfigTest {\n    @Test\n    public void getResourceConfig() {\n        String env = \"TEST_RESOURCE\";\n        MockSystemEnv.set(env, \"{\\\"uris\\\":\\\"127.0.0.1:9092\\\"}\");\n        ResourceConfig resourceConfig = ResourceConfig.getResourceConfig(env, true);\n        assertEquals(\"127.0.0.1:9092\", resourceConfig.getUris());\n        assertNotNull(resourceConfig.getFirstHostAndPort());\n        assertEquals(\"127.0.0.1\", resourceConfig.getFirstHostAndPort().getHost());\n        assertEquals(9092, (int) resourceConfig.getFirstHostAndPort().getPort());\n        assertEquals(1, resourceConfig.getHostAndPorts().size());\n        assertEquals(1, resourceConfig.getUriList().size());\n        assertEquals(\"127.0.0.1:9092\", resourceConfig.getFirstUri());\n\n\n        resourceConfig = ResourceConfig.getResourceConfig(env, false);\n        assertEquals(\"127.0.0.1:9092\", resourceConfig.getUris());\n        assertNull(resourceConfig.getFirstHostAndPort());\n\n        MockSystemEnv.set(env, \"{\\\"uris\\\":\\\"127.0.0.1:9092,127.0.0.1:9093\\\"}\");\n        resourceConfig = ResourceConfig.getResourceConfig(env, true);\n        assertEquals(\"127.0.0.1:9092,127.0.0.1:9093\", resourceConfig.getUris());\n        assertNotNull(resourceConfig.getFirstHostAndPort());\n        assertEquals(\"127.0.0.1\", resourceConfig.getFirstHostAndPort().getHost());\n        assertEquals(9092, (int) resourceConfig.getFirstHostAndPort().getPort());\n        assertEquals(2, resourceConfig.getHostAndPorts().size());\n        assertEquals(\"127.0.0.1\", resourceConfig.getHostAndPorts().get(1).getHost());\n        assertEquals(9093, (int) resourceConfig.getHostAndPorts().get(1).getPort());\n\n        assertEquals(2, resourceConfig.getUriList().size());\n        assertEquals(\"127.0.0.1:9092\", resourceConfig.getFirstUri());\n        assertEquals(\"127.0.0.1:9093\", resourceConfig.getUriList().get(1));\n\n\n        MockSystemEnv.remove(env);\n    }\n\n    @Test\n    public void testMockEnv() {\n        String name = \"TEST_MIDDLEWARE_RESOURCE_xxxx\";\n        String value = \"xxxxx\";\n        MockSystemEnv.set(name, value);\n        assertEquals(value, SystemEnv.get(name));\n    }\n}\n"
  },
  {
    "path": "plugin-api/src/test/java/com/megaease/easeagent/plugin/async/ThreadLocalCurrentContextTest.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.async;\n\nimport org.junit.Test;\n\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\n\nimport static org.junit.Assert.assertEquals;\n\n@SuppressWarnings(\"unused\")\npublic class ThreadLocalCurrentContextTest {\n    @Test\n    public void should_work() {\n        final ThreadLocalCurrentContext currentContext = ThreadLocalCurrentContext.DEFAULT;\n        try (final ThreadLocalCurrentContext.Scope scope1 = currentContext.newScope(ThreadLocalCurrentContext.createContext(\"hello\", \"world\"))) {\n            try (final ThreadLocalCurrentContext.Scope scope2 = currentContext.newScope(ThreadLocalCurrentContext.createContext(\"hello\", \"internal\"))) {\n                assertEquals(\"internal\", currentContext.get().get(\"hello\"));\n            }\n            assertEquals(\"world\", currentContext.get().get(\"hello\"));\n        }\n    }\n\n    @Test\n    public void should_work_multiple() throws Exception {\n        final int size = 1;\n        CountDownLatch countDownLatch = new CountDownLatch(size);\n        final ThreadLocalCurrentContext currentContext = ThreadLocalCurrentContext.DEFAULT;\n        try (final ThreadLocalCurrentContext.Scope scope1 = currentContext.newScope(ThreadLocalCurrentContext.createContext(\"hello\", \"world\"))) {\n            new Thread(() -> {\n                assertEquals(\"world\", currentContext.get().get(\"hello\"));\n                countDownLatch.countDown();\n            }).start();\n            assertEquals(\"world\", currentContext.get().get(\"hello\"));\n        }\n        countDownLatch.await();\n    }\n\n    @Test\n    public void should_work_multiple2() throws Exception {\n        final int size = 10;\n        CountDownLatch countDownLatch = new CountDownLatch(size);\n        final ThreadLocalCurrentContext currentContext = ThreadLocalCurrentContext.DEFAULT;\n        try (final ThreadLocalCurrentContext.Scope scope1 = currentContext.newScope(ThreadLocalCurrentContext.createContext(\"hello\", \"world\"))) {\n            for (int i = 0; i < size; i++) {\n                int finalI = i;\n                new Thread(() -> {\n                    final String value = \"internal\" + finalI;\n                    try (final ThreadLocalCurrentContext.Scope scope2 = currentContext.newScope(ThreadLocalCurrentContext.createContext(\"hello\", value))) {\n                        assertEquals(value, currentContext.get().get(\"hello\"));\n                    }\n                    try {\n                        TimeUnit.MILLISECONDS.sleep(100);\n                    } catch (InterruptedException ignored) {\n\n                    }\n                    countDownLatch.countDown();\n                }).start();\n            }\n            assertEquals(\"world\", currentContext.get().get(\"hello\"));\n        }\n        countDownLatch.await();\n    }\n\n}\n"
  },
  {
    "path": "plugin-api/src/test/java/com/megaease/easeagent/plugin/tools/config/AutoRefreshConfigSupplierTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.tools.config;\n\nimport com.megaease.easeagent.plugin.api.config.AutoRefreshConfigSupplier;\nimport com.megaease.easeagent.plugin.api.config.AutoRefreshPluginConfig;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport java.lang.reflect.Type;\n\npublic class AutoRefreshConfigSupplierTest {\n    @Test\n    public void getType() {\n        AutoRefreshConfigSupplier<TestAutoRefreshPluginConfig> supplier = new AutoRefreshConfigSupplier<TestAutoRefreshPluginConfig>() {\n            @Override\n            public TestAutoRefreshPluginConfig newInstance() {\n                return new TestAutoRefreshPluginConfig();\n            }\n        };\n        AutoRefreshConfigSupplier<TestAutoRefreshPluginConfig> supplier2 = new AutoRefreshConfigSupplier<TestAutoRefreshPluginConfig>() {\n            @Override\n            public TestAutoRefreshPluginConfig newInstance() {\n                return new TestAutoRefreshPluginConfig();\n            }\n        };\n        Assert.assertEquals(supplier.getType(), supplier2.getType());\n        Type type1 = supplier.getType();\n        Type type2 = supplier2.getType();\n        Assert.assertTrue(type1.getTypeName().equalsIgnoreCase(TestAutoRefreshPluginConfig.class.getName()));\n        System.out.println(type1.equals(type2));\n    }\n\n    @Test\n    public void newInstance() {\n        AutoRefreshConfigSupplier<TestAutoRefreshPluginConfig> supplier = new AutoRefreshConfigSupplier<TestAutoRefreshPluginConfig>() {\n            @Override\n            public TestAutoRefreshPluginConfig newInstance() {\n                return new TestAutoRefreshPluginConfig();\n            }\n        };\n        AutoRefreshConfigSupplier<TestAutoRefreshPluginConfig> supplier2 = new AutoRefreshConfigSupplier<TestAutoRefreshPluginConfig>() {\n            @Override\n            public TestAutoRefreshPluginConfig newInstance() {\n                return new TestAutoRefreshPluginConfig();\n            }\n        };\n\n        Assert.assertEquals(supplier.newInstance().getClass(), supplier2.newInstance().getClass());\n    }\n\n    static class TestAutoRefreshPluginConfig implements AutoRefreshPluginConfig {\n\n        @Override\n        public void onChange(IPluginConfig oldConfig, IPluginConfig newConfig) {\n\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/async/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright (c) 2021, MegaEase\n  ~ All rights reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~   http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <parent>\n        <groupId>com.megaease.easeagent</groupId>\n        <artifactId>plugins</artifactId>\n        <version>2.3.0</version>\n    </parent>\n\n    <!--\n    <groupId>com.megaease.easeagent.plugin</groupId>\n    -->\n    <artifactId>async</artifactId>\n\n    <packaging>jar</packaging>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>plugin-api</artifactId>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>junit</groupId>\n            <artifactId>junit</artifactId>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>plugin-api-mock</artifactId>\n            <version>${project.version}</version>\n            <scope>test</scope>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-compiler-plugin</artifactId>\n            </plugin>\n\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-resources-plugin</artifactId>\n                <version>3.0.1</version>\n            </plugin>\n\n            <!--\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-jar-plugin</artifactId>\n                <configuration>\n                    <archive>\n                        <manifest>\n                            <addDefaultImplementationEntries>true</addDefaultImplementationEntries>\n                            <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>\n                        </manifest>\n                    </archive>\n                </configuration>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-release-plugin</artifactId>\n                <version>2.5.3</version>\n            </plugin>\n            -->\n        </plugins>\n    </build>\n</project>\n"
  },
  {
    "path": "plugins/async/src/main/java/com/megaease/easeagent/plugin/AsyncPlugin.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin;\n\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.enums.Order;\n\npublic class AsyncPlugin implements AgentPlugin {\n    @Override\n    public String getNamespace() {\n        return ConfigConst.Namespace.ASYNC;\n    }\n\n    @Override\n    public String getDomain() {\n        return ConfigConst.OBSERVABILITY;\n    }\n\n    @Override\n    public int order() {\n        return Order.FOUNDATION.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/async/src/main/java/com/megaease/easeagent/plugin/advice/CrossThreadAdvice.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.advice;\n\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.Set;\n\npublic class CrossThreadAdvice implements Points {\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return ClassMatcher.builder()\n            .hasClassName(\"java.util.concurrent.ThreadPoolExecutor\")\n            .or().hasClassName(\"reactor.core.scheduler.Schedulers\")\n            .build();\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.builder()\n            .named(\"execute\")\n            .argsLength(1)\n            .arg(0, \"java.lang.Runnable\")\n            .build().toSet();\n    }\n}\n"
  },
  {
    "path": "plugins/async/src/main/java/com/megaease/easeagent/plugin/advice/ReactSchedulersAdvice.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.advice;\n\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.Set;\n\npublic class ReactSchedulersAdvice implements Points {\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return ClassMatcher.builder()\n            .hasClassName(\"reactor.core.scheduler.Schedulers\")\n            .build();\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.builder().named(\"onSchedule\")\n            .argsLength(1)\n            .arg(0, \"java.lang.Runnable\")\n            .build().toSet();\n    }\n}\n"
  },
  {
    "path": "plugins/async/src/main/java/com/megaease/easeagent/plugin/interceptor/RunnableInterceptor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.interceptor;\n\nimport com.megaease.easeagent.plugin.advice.CrossThreadAdvice;\nimport com.megaease.easeagent.plugin.advice.ReactSchedulersAdvice;\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.logging.Logger;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\n\n@AdviceTo(CrossThreadAdvice.class)\n@AdviceTo(ReactSchedulersAdvice.class)\npublic class RunnableInterceptor implements Interceptor {\n    private static final Logger logger = EaseAgent.loggerFactory.getLogger(RunnableInterceptor.class);\n\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n        try {\n            Object[] args = methodInfo.getArgs();\n            Runnable task = (Runnable) args[0];\n            if (!context.isWrapped(task)) {\n                Runnable wrap = context.wrap(task);\n                methodInfo.changeArg(0, wrap);\n            }\n        } catch (Throwable e) {\n            logger.warn(\"intercept method [{}] failure\", methodInfo.getMethod(), e);\n        }\n    }\n\n    @Override\n    public String getType() {\n        return Order.TRACING.getName();\n    }\n\n    @Override\n    public int order() {\n        return Order.TRACING.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/async/src/test/java/com/megaease/easeagent/plugin/interceptor/RunnableInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.interceptor;\n\nimport com.megaease.easeagent.mock.plugin.api.MockEaseAgent;\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class RunnableInterceptorTest {\n\n    @Test\n    public void before() throws InterruptedException {\n        Context context = EaseAgent.getContext();\n        final Span span = context.nextSpan();\n        span.start();\n        span.cacheScope();\n        RunnableInterceptor runnableInterceptor = new RunnableInterceptor();\n        AtomicInteger run = new AtomicInteger();\n        Runnable runnable = () -> {\n            Context runCont = EaseAgent.getContext();\n            assertTrue(runCont.currentTracing().hasCurrentSpan());\n            Span span1 = runCont.nextSpan();\n            assertEquals(span.traceId(), span1.traceId());\n            assertEquals(span.spanId(), span1.parentId());\n            assertNotEquals(span.spanId(), span1.spanId());\n            span1.finish();\n            run.incrementAndGet();\n        };\n        MethodInfo methodInfo = MethodInfo.builder()\n            .invoker(\"\")\n            .type(\"\")\n            .method(\"\")\n            .args(new Object[]{runnable})\n            .build();\n        runnableInterceptor.before(methodInfo, context);\n        Thread thread = new Thread((Runnable) methodInfo.getArgs()[0]);\n        thread.start();\n        thread.join();\n        assertEquals(run.get(), 1);\n        span.finish();\n\n        ReportSpan span1 = MockEaseAgent.getLastSpan();\n        assertEquals(span.traceIdString(), span1.traceId());\n        assertEquals(span.parentIdString(), span1.parentId());\n        assertEquals(span.spanIdString(), span1.id());\n        System.out.println(\"run count: \" + run.get());\n    }\n}\n"
  },
  {
    "path": "plugins/async/src/test/resources/log4j2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Configuration status=\"WARN\">\n    <Appenders>\n        <Console name=\"Console\" target=\"SYSTEM_OUT\">\n            <!--<PatternLayout pattern=\"%d{HH:mm:ss.SSS} [%10.10t] %-p %10.10c{1} - %msg%n\"/>-->\n            <PatternLayout pattern=\"%d{HH:mm:ss.SSS} %-5level %class{36}:%L (%X{testMdc}) - %msg%xEx%n\"/>\n        </Console>\n    </Appenders>\n    <Loggers>\n        <Root level=\"INFO\">\n            <AppenderRef ref=\"Console\"/>\n        </Root>\n    </Loggers>\n</Configuration>\n"
  },
  {
    "path": "plugins/dubbo/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>plugins</artifactId>\n        <groupId>com.megaease.easeagent</groupId>\n        <version>2.3.0</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>dubbo</artifactId>\n\n    <properties>\n        <maven.compiler.source>8</maven.compiler.source>\n        <maven.compiler.target>8</maven.compiler.target>\n        <alibaba-dubbo.version>2.6.12</alibaba-dubbo.version>\n        <apache-dubbo.version>2.7.3</apache-dubbo.version>\n    </properties>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>plugin-api</artifactId>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>plugin-api-mock</artifactId>\n            <version>${project.version}</version>\n            <scope>test</scope>\n        </dependency>\n\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>plugin-api-mock</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>com.alibaba</groupId>\n            <artifactId>dubbo</artifactId>\n            <version>${alibaba-dubbo.version}</version>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.apache.dubbo</groupId>\n            <artifactId>dubbo</artifactId>\n            <version>${apache-dubbo.version}</version>\n            <scope>provided</scope>\n        </dependency>\n\n    </dependencies>\n</project>\n"
  },
  {
    "path": "plugins/dubbo/src/main/java/com/megaease/easeagent/plugin/dubbo/AlibabaDubboCtxUtils.java",
    "content": "package com.megaease.easeagent.plugin.dubbo;\n\nimport com.alibaba.dubbo.common.Constants;\nimport com.alibaba.dubbo.common.URL;\nimport com.alibaba.dubbo.rpc.Invocation;\nimport com.alibaba.dubbo.rpc.Invoker;\nimport com.alibaba.dubbo.rpc.Result;\nimport com.alibaba.dubbo.rpc.RpcContext;\nimport com.alibaba.dubbo.rpc.protocol.dubbo.FutureAdapter;\nimport com.alibaba.dubbo.rpc.support.RpcUtils;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.api.context.RequestContext;\nimport com.megaease.easeagent.plugin.api.trace.Scope;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.dubbo.config.DubboTraceConfig;\nimport com.megaease.easeagent.plugin.dubbo.interceptor.DubboBaseInterceptor;\nimport com.megaease.easeagent.plugin.dubbo.interceptor.trace.alibaba.AlibabaDubboAsyncTraceInterceptor;\nimport com.megaease.easeagent.plugin.dubbo.interceptor.trace.alibaba.AlibabaDubboClientRequest;\nimport com.megaease.easeagent.plugin.dubbo.interceptor.trace.alibaba.AlibabaDubboServerRequest;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.utils.common.JsonUtil;\n\nimport java.util.concurrent.Future;\n\nimport static com.alibaba.dubbo.common.Constants.*;\nimport static com.megaease.easeagent.plugin.dubbo.DubboTraceTags.RESULT;\n\npublic class AlibabaDubboCtxUtils {\n\tpublic static final String CLIENT_REQUEST_CONTEXT = AlibabaDubboCtxUtils.class.getName() + \".CLIENT_REQUEST_CONTEXT\";\n\tprivate static final String SERVICE_REQUEST_CONTEXT = AlibabaDubboCtxUtils.class.getName() + \".SERVICE_REQUEST_CONTEXT\";\n\tpublic static final String METRICS_SERVICE_NAME = AlibabaDubboCtxUtils.class.getName() + \".METRICS_SERVICE_NAME\";\n\tpublic static final String BEGIN_TIME = AlibabaDubboCtxUtils.class.getName() + \".BEGIN_TIME\";\n\n\n\tpublic static void initSpan(MethodInfo methodInfo, Context context) {\n\t\tInvoker<?> invoker = (Invoker<?>) methodInfo.getArgs()[0];\n\t\tInvocation invocation = (Invocation) methodInfo.getArgs()[1];\n\n\t\tURL requestUrl = invoker.getUrl();\n\t\tRpcContext rpcContext = RpcContext.getContext();\n\t\tString applicationName = requestUrl.getParameter(Constants.APPLICATION_KEY);\n\t\tboolean isConsumer = requestUrl.getParameter(SIDE_KEY, PROVIDER_SIDE).equals(CONSUMER_SIDE);\n\t\tif (isConsumer) {\n\t\t\tString groupName = requestUrl.getParameter(Constants.GROUP_KEY);\n\t\t\tString version = requestUrl.getParameter(Constants.VERSION_KEY);\n\n\t\t\tAlibabaDubboClientRequest alibabaDubboClientRequest = new AlibabaDubboClientRequest(invoker, invocation);\n\t\t\tRequestContext requestContext = context.clientRequest(alibabaDubboClientRequest);\n\t\t\tSpan span = requestContext.span().start();\n\t\t\tspan.kind(alibabaDubboClientRequest.kind());\n\t\t\tspan.name(alibabaDubboClientRequest.name());\n\t\t\tspan.remoteServiceName(ConfigConst.Namespace.DUBBO);\n\t\t\tspan.remoteIpAndPort(rpcContext.getRemoteHost(), rpcContext.getRemotePort());\n\n\t\t\tString argsList = DubboBaseInterceptor.DUBBO_TRACE_CONFIG.argsCollectEnabled() ? JsonUtil.toJson(invocation.getArguments()) : null;\n\t\t\tspan.tag(DubboTraceTags.CLIENT_APPLICATION.name, applicationName);\n\t\t\tspan.tag(DubboTraceTags.GROUP.name, groupName);\n\t\t\tspan.tag(DubboTraceTags.SERVICE.name, requestUrl.getPath());\n\t\t\tspan.tag(DubboTraceTags.METHOD.name, method(invocation));\n\t\t\tspan.tag(DubboTraceTags.SERVICE_VERSION.name, version);\n\t\t\tspan.tag(DubboTraceTags.ARGS.name, argsList);\n\n\t\t\tcontext.put(CLIENT_REQUEST_CONTEXT, requestContext);\n\t\t} else {\n\t\t\tAlibabaDubboServerRequest alibabaDubboServerRequest = new AlibabaDubboServerRequest(invoker, invocation);\n\t\t\tRequestContext requestContext = context.serverReceive(alibabaDubboServerRequest);\n\n\t\t\tSpan span = requestContext.span().start();\n\t\t\tspan.kind(alibabaDubboServerRequest.kind());\n\t\t\tspan.name(alibabaDubboServerRequest.name());\n\t\t\tspan.remoteServiceName(ConfigConst.Namespace.DUBBO);\n\t\t\tspan.remoteIpAndPort(rpcContext.getRemoteHost(), rpcContext.getRemotePort());\n\t\t\tspan.tag(DubboTraceTags.SERVER_APPLICATION.name, applicationName);\n\n\t\t\tcontext.put(SERVICE_REQUEST_CONTEXT, requestContext);\n\t\t}\n\t}\n\n\tpublic static void finishSpan(Context context, MethodInfo methodInfo) {\n\t\tResult result = (Result) methodInfo.getRetValue();\n\t\tInvoker<?> invoker = (Invoker<?>) methodInfo.getArgs()[0];\n\t\tInvocation invocation = (Invocation) methodInfo.getArgs()[1];\n\t\tThrowable throwable = methodInfo.getThrowable();\n\n        /**\n         * requestContext.scope() will be closed in\n         * {@link AlibabaDubboAsyncTraceInterceptor#before(MethodInfo, Context)}\n         */\n        boolean isAsync = RpcUtils.isAsync(invoker.getUrl(), invocation);\n        Future<?> f = RpcContext.getContext().getFuture();\n        if (isAsync && f instanceof FutureAdapter) {\n            return;\n        }\n\n\t\tboolean isConsumer = invoker.getUrl().getParameter(SIDE_KEY, PROVIDER_SIDE).equals(CONSUMER_SIDE);\n        RequestContext requestContext = requestContext(isConsumer,context);\n        if (requestContext == null) {\n            return;\n        }\n        try (Scope scope = requestContext.scope()){\n            Span span = requestContext.span();\n            doFinishSpan(span, result, throwable);\n        }\n\t}\n\n    public static void finishSpan(Context context, Result result, Throwable throwable) {\n        RequestContext requestContext = context.remove(CLIENT_REQUEST_CONTEXT);\n        if (requestContext == null) {\n            return;\n        }\n        try (Scope scope = requestContext.scope()){\n            Span span = requestContext.span();\n            doFinishSpan(span, result, throwable);\n        }\n    }\n\n\n\tprivate static void doFinishSpan(Span span, Result result, Throwable throwable) {\n        if (span == null || span.isNoop()) {\n            return;\n        }\n        DubboTraceConfig config = DubboBaseInterceptor.DUBBO_TRACE_CONFIG;\n        if (throwable != null) {\n            span.error(throwable);\n        }\n        if (result != null) {\n            if (result.getException() != null) {\n                span.error(result.getException());\n            } else {\n                if (config.resultCollectEnabled() && result.getValue() != null) {\n                    span.tag(RESULT.name, config.resultCollectEnabled() ? JsonUtil.toJson(result.getValue()) : null);\n                }\n            }\n        }\n        span.finish();\n\t}\n\n    private static RequestContext requestContext(boolean isConsumer, Context context) {\n        RequestContext requestContext;\n        if (isConsumer) {\n            requestContext = context.remove(CLIENT_REQUEST_CONTEXT);\n        } else {\n            requestContext = context.remove(SERVICE_REQUEST_CONTEXT);\n        }\n        return requestContext;\n    }\n\n\n    /**\n\t * Format method name. e.g. test(String)\n\t *\n\t * @param invocation\n\t * @return method name\n\t */\n\tpublic static String method(Invocation invocation) {\n\t\tStringBuilder methodName = new StringBuilder();\n\t\tmethodName\n\t\t\t\t.append(invocation.getMethodName())\n\t\t\t\t.append(\"(\");\n\t\tfor (Class<?> classes : invocation.getParameterTypes()) {\n\t\t\tmethodName.append(classes.getSimpleName()).append(\",\");\n\t\t}\n\n\t\tif (invocation.getParameterTypes().length > 0) {\n\t\t\tmethodName.delete(methodName.length() - 1, methodName.length());\n\t\t}\n\t\tmethodName.append(\")\");\n\t\treturn methodName.toString();\n\t}\n\n\t/**\n\t * dubbo span name, e.g. TestService/test(String)\n\t *\n\t * @param invocation\n\t * @return\n\t */\n\tpublic static String name(Invocation invocation) {\n\t\tfinal URL url = invocation.getInvoker().getUrl();\n\t\tfinal String interfaceClassSimpleName = url.getPath().substring(url.getPath().lastIndexOf(\".\") + 1);\n\t\tStringBuilder argsStringBuilder = new StringBuilder();\n\t\tfinal Class<?>[] parameterTypes = invocation.getParameterTypes();\n\t\tfor (int i = 0; i < parameterTypes.length; i++) {\n\t\t\targsStringBuilder.append(parameterTypes[i].getSimpleName());\n\t\t\tif (i != parameterTypes.length - 1) {\n\t\t\t\targsStringBuilder.append(\",\");\n\t\t\t}\n\t\t}\n\t\treturn String.format(\"%s/%s(%s)\", interfaceClassSimpleName, invocation.getMethodName(), argsStringBuilder);\n\t}\n\n\t/**\n\t * check response result is success\n\t *\n\t * @param response  dubbo response result\n\t * @param throwable dubbo exception\n\t * @return if success the return true, otherwise return false.\n\t */\n\tpublic static boolean checkCallResult(Object response, Throwable throwable) {\n\t\tif (response == null || throwable != null) {\n\t\t\treturn false;\n\t\t}\n\t\tif (response instanceof Result) {\n\t\t\tResult rpcRet = (Result) response;\n\t\t\treturn rpcRet.getValue() != null;\n\t\t}\n\t\treturn false;\n\t}\n\n\n\t/**\n\t * Format interface signature. e.g. com.magaease.easeagent.service.DubboService.test(String)\n\t *\n\t * @return interface signature strings.\n\t */\n\tpublic static String interfaceSignature(Invocation invocation) {\n\t\tStringBuilder operationName = new StringBuilder();\n\t\tURL requestURL = invocation.getInvoker().getUrl();\n\t\toperationName.append(requestURL.getPath());\n\t\toperationName.append(\".\").append(invocation.getMethodName()).append(\"(\");\n\t\tfor (Class<?> classes : invocation.getParameterTypes()) {\n\t\t\toperationName.append(classes.getSimpleName()).append(\",\");\n\t\t}\n\n\t\tif (invocation.getParameterTypes().length > 0) {\n\t\t\toperationName.delete(operationName.length() - 1, operationName.length());\n\t\t}\n\t\toperationName.append(\")\");\n\n\t\treturn operationName.toString();\n\t}\n}\n"
  },
  {
    "path": "plugins/dubbo/src/main/java/com/megaease/easeagent/plugin/dubbo/ApacheDubboCtxUtils.java",
    "content": "package com.megaease.easeagent.plugin.dubbo;\n\nimport com.megaease.easeagent.log4j2.Logger;\nimport com.megaease.easeagent.log4j2.LoggerFactory;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.RequestContext;\nimport com.megaease.easeagent.plugin.api.trace.Scope;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.dubbo.config.DubboTraceConfig;\nimport com.megaease.easeagent.plugin.dubbo.interceptor.DubboBaseInterceptor;\nimport com.megaease.easeagent.plugin.dubbo.interceptor.trace.apache.ApacheDubboClientRequest;\nimport com.megaease.easeagent.plugin.dubbo.interceptor.trace.apache.ApacheDubboServerRequest;\nimport com.megaease.easeagent.plugin.dubbo.interceptor.trace.apache.ApacheDubboTraceCallback;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.utils.common.JsonUtil;\nimport org.apache.dubbo.common.URL;\nimport org.apache.dubbo.common.constants.CommonConstants;\nimport org.apache.dubbo.rpc.*;\n\nimport static org.apache.dubbo.common.constants.CommonConstants.CONSUMER_SIDE;\nimport static org.apache.dubbo.common.constants.CommonConstants.SIDE_KEY;\n\npublic class ApacheDubboCtxUtils {\n    private static final String CLIENT_REQUEST_CONTEXT = ApacheDubboCtxUtils.class.getName() + \".CLIENT_REQUEST_CONTEXT\";\n    private static final String SERVICE_REQUEST_CONTEXT = ApacheDubboCtxUtils.class.getName() + \".SERVICE_REQUEST_CONTEXT\";\n    public static final String METRICS_SERVICE_NAME = ApacheDubboCtxUtils.class.getName() + \".METRICS_SERVICE_NAME\";\n    public static final String BEGIN_TIME = ApacheDubboCtxUtils.class.getName() + \".BEGIN_TIME\";\n    //dubbo get metadata interface\n    public static final String METADATA_INTERFACE = \"org.apache.dubbo.metadata.MetadataService\";\n\n    private static final Logger log = LoggerFactory.getLogger(ApacheDubboCtxUtils.class);\n\n    /**\n     * init span\n     *\n     * @param methodInfo\n     * @param context\n     */\n    public static void initSpan(MethodInfo methodInfo, Context context) {\n        Invoker<?> invoker = (Invoker<?>) methodInfo.getArgs()[0];\n        Invocation invocation = (Invocation) methodInfo.getArgs()[1];\n        URL requestUrl = invoker.getUrl();\n\n        if (requestUrl.getServiceInterface().equals(METADATA_INTERFACE)) {\n            return;\n        }\n\n        RpcContext rpcContext = RpcContext.getContext();\n        boolean isConsumer = requestUrl.getParameter(SIDE_KEY).equals(CONSUMER_SIDE);\n        String applicationName = requestUrl.getParameter(CommonConstants.APPLICATION_KEY);\n        if (isConsumer) {\n            String groupName = requestUrl.getParameter(CommonConstants.GROUP_KEY);\n            String version = requestUrl.getParameter(CommonConstants.VERSION_KEY);\n\n            ApacheDubboClientRequest apacheDubboClientRequest = new ApacheDubboClientRequest(invoker, invocation);\n            RequestContext requestContext = context.clientRequest(apacheDubboClientRequest);\n            Span span = requestContext.span().start();\n            span.kind(apacheDubboClientRequest.kind());\n            span.name(apacheDubboClientRequest.name());\n            span.remoteServiceName(CommonConstants.DUBBO);\n            span.remoteIpAndPort(requestUrl.getIp(), requestUrl.getPort());\n\n            String argsList = DubboBaseInterceptor.DUBBO_TRACE_CONFIG.argsCollectEnabled() ? JsonUtil.toJson(invocation.getArguments()) : null;\n            span.tag(DubboTraceTags.CLIENT_APPLICATION.name, applicationName);\n            span.tag(DubboTraceTags.GROUP.name, groupName);\n            span.tag(DubboTraceTags.SERVICE.name, requestUrl.getPath());\n            span.tag(DubboTraceTags.METHOD.name, method(invocation));\n            span.tag(DubboTraceTags.SERVICE_VERSION.name, version);\n            span.tag(DubboTraceTags.ARGS.name, argsList);\n\n            context.put(CLIENT_REQUEST_CONTEXT, requestContext);\n        } else {\n            ApacheDubboServerRequest apacheDubboServerRequest = new ApacheDubboServerRequest(invoker, invocation);\n            RequestContext requestContext = context.serverReceive(apacheDubboServerRequest);\n\n            Span span = requestContext.span().start();\n            span.kind(apacheDubboServerRequest.kind());\n            span.name(apacheDubboServerRequest.name());\n            span.remoteServiceName(CommonConstants.DUBBO);\n            span.remoteIpAndPort(rpcContext.getRemoteHost(), rpcContext.getRemotePort());\n            span.tag(DubboTraceTags.SERVER_APPLICATION.name, applicationName);\n\n            context.put(SERVICE_REQUEST_CONTEXT, requestContext);\n        }\n    }\n\n    public static void finishSpan(URL url, Context context, Result result, Throwable throwable) {\n        boolean isConsumer = url.getParameter(SIDE_KEY).equals(CONSUMER_SIDE);\n        RequestContext requestContext = requestContext(isConsumer, context);\n        if (requestContext == null) {\n            return;\n        }\n        try (Scope scope = requestContext.scope()) {\n            Span span = requestContext.span();\n            if (result instanceof AsyncRpcResult) {\n                result.whenCompleteWithContext(new ApacheDubboTraceCallback(span));\n            } else {\n                doFinishSpan(span, result, throwable);\n            }\n        }\n    }\n\n    public static void doFinishSpan(Span span, Result result, Throwable throwable) {\n        if (span == null || span.isNoop()) {\n            return;\n        }\n\n        DubboTraceConfig dubboTraceConfig = DubboBaseInterceptor.DUBBO_TRACE_CONFIG;\n        if (throwable != null) {\n            span.error(throwable);\n        }\n        if (result != null) {\n            if (result.getException() != null) {\n                span.error(result.getException());\n            } else {\n                if (dubboTraceConfig.resultCollectEnabled() && result.getValue() != null) {\n                    span.tag(DubboTraceTags.RESULT.name, JsonUtil.toJson(result.getValue()));\n                }\n            }\n        }\n        span.finish();\n    }\n\n    public static RequestContext requestContext(boolean isConsumer, Context context) {\n        RequestContext requestContext;\n        if (isConsumer) {\n            requestContext = context.remove(CLIENT_REQUEST_CONTEXT);\n        } else {\n            requestContext = context.remove(SERVICE_REQUEST_CONTEXT);\n        }\n        return requestContext;\n    }\n\n    /**\n     * check response result is success\n     *\n     * @param result    dubbo response result\n     * @param throwable exception\n     * @return if success the return true, otherwise return false.\n     */\n    public static boolean checkCallResult(Result result, Throwable throwable) {\n        return result != null && result.getException() == null && throwable == null;\n    }\n\n    /**\n     * Format interface signature. e.g. com.magaease.easeagent.service.DubboService.test(String)\n     *\n     * @return interface signature strings.\n     */\n    public static String interfaceSignature(Invocation invocation) {\n        StringBuilder operationName = new StringBuilder();\n        URL requestURL = invocation.getInvoker().getUrl();\n        operationName.append(requestURL.getPath());\n        operationName.append(\".\").append(invocation.getMethodName()).append(\"(\");\n        for (Class<?> classes : invocation.getParameterTypes()) {\n            operationName.append(classes.getSimpleName()).append(\",\");\n        }\n\n        if (invocation.getParameterTypes().length > 0) {\n            operationName.delete(operationName.length() - 1, operationName.length());\n        }\n        operationName.append(\")\");\n\n        return operationName.toString();\n    }\n\n    /**\n     * Format method name. e.g. test(String)\n     *\n     * @param invocation\n     * @return method name\n     */\n    public static String method(Invocation invocation) {\n        StringBuilder methodName = new StringBuilder();\n        methodName.append(invocation.getMethodName())\n            .append(\"(\");\n        for (Class<?> classes : invocation.getParameterTypes()) {\n            methodName.append(classes.getSimpleName()).append(\",\");\n        }\n\n        if (invocation.getParameterTypes().length > 0) {\n            methodName.delete(methodName.length() - 1, methodName.length());\n        }\n        methodName.append(\")\");\n        return methodName.toString();\n    }\n\n\n    /**\n     * dubbo span name, e.g. TestService/test(String)\n     *\n     * @param invocation\n     * @return\n     */\n    public static String name(Invocation invocation) {\n        URL url = invocation.getInvoker().getUrl();\n        String interfaceClassSimpleName = url.getPath().substring(url.getPath().lastIndexOf(\".\") + 1);\n        StringBuilder argsStringBuilder = new StringBuilder();\n        Class<?>[] parameterTypes = invocation.getParameterTypes();\n        for (int i = 0; i < parameterTypes.length; i++) {\n            argsStringBuilder.append(parameterTypes[i].getSimpleName());\n            if (i != parameterTypes.length - 1) {\n                argsStringBuilder.append(\",\");\n            }\n        }\n        return String.format(\"%s/%s(%s)\", interfaceClassSimpleName, invocation.getMethodName(), argsStringBuilder);\n    }\n\n}\n"
  },
  {
    "path": "plugins/dubbo/src/main/java/com/megaease/easeagent/plugin/dubbo/DubboMetricTags.java",
    "content": "package com.megaease.easeagent.plugin.dubbo;\n\npublic enum DubboMetricTags {\n\tCATEGORY(\"application\"),\n\tTYPE(\"dubbo\"),\n\tLABEL_NAME(\"interface\"),\n\t;\n\n\tpublic final String name;\n\n\tDubboMetricTags(String name) {\n\t\tthis.name = name;\n\t}\n}\n"
  },
  {
    "path": "plugins/dubbo/src/main/java/com/megaease/easeagent/plugin/dubbo/DubboPlugin.java",
    "content": "package com.megaease.easeagent.plugin.dubbo;\n\nimport com.megaease.easeagent.plugin.AgentPlugin;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\n\npublic class DubboPlugin implements AgentPlugin {\n\t@Override\n\tpublic String getNamespace() {\n\t\treturn ConfigConst.Namespace.DUBBO;\n\t}\n\n\t@Override\n\tpublic String getDomain() {\n\t\treturn ConfigConst.OBSERVABILITY;\n\t}\n}\n"
  },
  {
    "path": "plugins/dubbo/src/main/java/com/megaease/easeagent/plugin/dubbo/DubboTraceTags.java",
    "content": "package com.megaease.easeagent.plugin.dubbo;\n\npublic enum DubboTraceTags {\n\tSERVICE(\"dubbo.service\"),\n\tMETHOD(\"dubbo.method\"),\n\tSERVICE_VERSION(\"dubbo.service.version\"),\n\tSERVER_APPLICATION(\"dubbo.server.application\"),\n\tCLIENT_APPLICATION(\"dubbo.client.application\"),\n\tGROUP(\"dubbo.group\"),\n\tARGS(\"dubbo.args\"),\n\tRESULT(\"dubbo.result\"),\n\t;\n\n\tpublic final String name;\n\n\tDubboTraceTags(String name) {\n\t\tthis.name = name;\n\t}\n}\n"
  },
  {
    "path": "plugins/dubbo/src/main/java/com/megaease/easeagent/plugin/dubbo/advice/AlibabaDubboAdvice.java",
    "content": "package com.megaease.easeagent.plugin.dubbo.advice;\n\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.Set;\n\npublic class AlibabaDubboAdvice implements Points {\n\tprivate static final String ENHANCE_CLASS_ONE = \"com.alibaba.dubbo.monitor.support.MonitorFilter\";\n\n\tprivate static final String ENHANCE_METHOD = \"invoke\";\n\tprivate static final String ENHANCE_METHOD_PARAMS_ONE = \"com.alibaba.dubbo.rpc.Invoker\";\n\tprivate static final String ENHANCE_METHOD_PARAMS_TWO = \"com.alibaba.dubbo.rpc.Invocation\";\n\tprivate static final String ENHANCE_METHOD_RETURN_TYPE = \"com.alibaba.dubbo.rpc.Result\";\n\n\n\t@Override\n\tpublic IClassMatcher getClassMatcher() {\n\t\treturn ClassMatcher.builder()\n\t\t\t\t.hasClassName(ENHANCE_CLASS_ONE)\n\t\t\t\t.build();\n\t}\n\n\t@Override\n\tpublic Set<IMethodMatcher> getMethodMatcher() {\n\t\treturn MethodMatcher.builder()\n\t\t\t\t.isPublic()\n\t\t\t\t.named(ENHANCE_METHOD)\n\t\t\t\t.arg(0, ENHANCE_METHOD_PARAMS_ONE)\n\t\t\t\t.arg(1, ENHANCE_METHOD_PARAMS_TWO)\n\t\t\t\t.returnType(ENHANCE_METHOD_RETURN_TYPE)\n\t\t\t\t.build()\n\t\t\t\t.toSet();\n\t}\n}\n"
  },
  {
    "path": "plugins/dubbo/src/main/java/com/megaease/easeagent/plugin/dubbo/advice/AlibabaDubboResponseFutureAdvice.java",
    "content": "package com.megaease.easeagent.plugin.dubbo.advice;\n\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.Set;\n\npublic class AlibabaDubboResponseFutureAdvice implements Points {\n    private static final String RESPONSE_FUTURE_CLASS_NAME = \"com.alibaba.dubbo.remoting.exchange.ResponseFuture\";\n    private static final String SET_CALLBACK_METHOD = \"setCallback\";\n\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return ClassMatcher.builder()\n            .hasSuperClass(RESPONSE_FUTURE_CLASS_NAME)\n            .build();\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.builder()\n            .named(SET_CALLBACK_METHOD)\n            .build()\n            .toSet();\n    }\n}\n"
  },
  {
    "path": "plugins/dubbo/src/main/java/com/megaease/easeagent/plugin/dubbo/advice/ApacheDubboAdvice.java",
    "content": "package com.megaease.easeagent.plugin.dubbo.advice;\n\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.Set;\n\npublic class ApacheDubboAdvice implements Points {\n\tprivate static final String ENHANCE_CLASS = \"org.apache.dubbo.monitor.support.MonitorFilter\";\n\n\tprivate static final String ENHANCE_METHOD = \"invoke\";\n\tprivate static final String ENHANCE_METHOD_PARAMS_ONE = \"org.apache.dubbo.rpc.Invoker\";\n\tprivate static final String ENHANCE_METHOD_PARAMS_TWO = \"org.apache.dubbo.rpc.Invocation\";\n\tprivate static final String ENHANCE_METHOD_RETURN_TYPE = \"org.apache.dubbo.rpc.Result\";\n\n\n\t@Override\n\tpublic IClassMatcher getClassMatcher() {\n\t\treturn ClassMatcher.builder()\n\t\t\t\t.hasClassName(ENHANCE_CLASS)\n\t\t\t\t.build();\n\t}\n\n\t@Override\n\tpublic Set<IMethodMatcher> getMethodMatcher() {\n\t\treturn MethodMatcher.builder()\n\t\t\t\t.isPublic()\n\t\t\t\t.named(ENHANCE_METHOD)\n\t\t\t\t.arg(0, ENHANCE_METHOD_PARAMS_ONE)\n\t\t\t\t.arg(1, ENHANCE_METHOD_PARAMS_TWO)\n\t\t\t\t.returnType(ENHANCE_METHOD_RETURN_TYPE)\n\t\t\t\t.build()\n\t\t\t\t.toSet();\n\t}\n}\n"
  },
  {
    "path": "plugins/dubbo/src/main/java/com/megaease/easeagent/plugin/dubbo/config/DubboTraceConfig.java",
    "content": "package com.megaease.easeagent.plugin.dubbo.config;\n\nimport com.megaease.easeagent.plugin.api.config.AutoRefreshConfigSupplier;\nimport com.megaease.easeagent.plugin.api.config.AutoRefreshPluginConfig;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.utils.Pair;\n\npublic class DubboTraceConfig implements AutoRefreshPluginConfig {\n\n\tprivate final Pair<String, Boolean> argsCollectEnabledPair = new Pair<>(\"args.collect.enabled\", false);\n    private final Pair<String, Boolean> resultCollectEnabledPair = new Pair<>(\"result.collect.enabled\", false);\n\tprivate volatile boolean argsCollectEnabled = argsCollectEnabledPair.getValue();\n    private volatile boolean resultCollectEnabled = resultCollectEnabledPair.getValue();\n\n\tpublic boolean argsCollectEnabled() {\n\t\treturn argsCollectEnabled;\n\t}\n\n    public boolean resultCollectEnabled() {\n        return resultCollectEnabled;\n    }\n\n    public static final AutoRefreshConfigSupplier<DubboTraceConfig> SUPPLIER = new AutoRefreshConfigSupplier<DubboTraceConfig>() {\n\t\t@Override\n\t\tpublic DubboTraceConfig newInstance() {\n\t\t\treturn new DubboTraceConfig();\n\t\t}\n\t};\n\n\t@Override\n\tpublic void onChange(IPluginConfig oldConfig, IPluginConfig newConfig) {\n\t\tString argsCollectEnabled = newConfig.getString(argsCollectEnabledPair.getKey());\n\t\tthis.argsCollectEnabled = Boolean.parseBoolean(argsCollectEnabled);\n\n        String resultCollectEnabled = newConfig.getString(resultCollectEnabledPair.getKey());\n        this.resultCollectEnabled = Boolean.parseBoolean(resultCollectEnabled);\n    }\n}\n"
  },
  {
    "path": "plugins/dubbo/src/main/java/com/megaease/easeagent/plugin/dubbo/interceptor/DubboBaseInterceptor.java",
    "content": "package com.megaease.easeagent.plugin.dubbo.interceptor;\n\nimport com.megaease.easeagent.plugin.api.config.AutoRefreshPluginConfigRegistry;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.dubbo.config.DubboTraceConfig;\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\n\npublic abstract class DubboBaseInterceptor implements Interceptor {\n\n\tpublic static volatile DubboTraceConfig DUBBO_TRACE_CONFIG;\n\n\t@Override\n\tpublic void init(IPluginConfig config, String className, String methodName, String methodDescriptor) {\n\t\tDUBBO_TRACE_CONFIG = AutoRefreshPluginConfigRegistry.getOrCreate(ConfigConst.OBSERVABILITY, ConfigConst.Namespace.DUBBO, this.getType(), DubboTraceConfig.SUPPLIER);\n\t}\n\n}\n"
  },
  {
    "path": "plugins/dubbo/src/main/java/com/megaease/easeagent/plugin/dubbo/interceptor/metrics/DubboBaseMetricsInterceptor.java",
    "content": "package com.megaease.easeagent.plugin.dubbo.interceptor.metrics;\n\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.metric.ServiceMetricRegistry;\nimport com.megaease.easeagent.plugin.api.metric.name.Tags;\nimport com.megaease.easeagent.plugin.dubbo.DubboMetricTags;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\n\npublic abstract class DubboBaseMetricsInterceptor implements Interceptor {\n    public static volatile DubboMetrics DUBBO_METRICS;\n\n    @Override\n    public String getType() {\n        return Order.METRIC.getName();\n    }\n\n    @Override\n    public int order() {\n        return Order.METRIC.getOrder();\n    }\n\n    @Override\n    public void init(IPluginConfig config, String className, String methodName, String methodDescriptor) {\n        Tags tags = new Tags(DubboMetricTags.CATEGORY.name, DubboMetricTags.TYPE.name, DubboMetricTags.LABEL_NAME.name);\n        DUBBO_METRICS = ServiceMetricRegistry.getOrCreate(config, tags, DubboMetrics.DUBBO_METRICS_SUPPLIER);\n    }\n\n}\n"
  },
  {
    "path": "plugins/dubbo/src/main/java/com/megaease/easeagent/plugin/dubbo/interceptor/metrics/DubboMetrics.java",
    "content": "package com.megaease.easeagent.plugin.dubbo.interceptor.metrics;\n\nimport com.megaease.easeagent.plugin.api.metric.*;\nimport com.megaease.easeagent.plugin.api.metric.name.MetricField;\nimport com.megaease.easeagent.plugin.api.metric.name.MetricSubType;\nimport com.megaease.easeagent.plugin.api.metric.name.MetricValueFetcher;\nimport com.megaease.easeagent.plugin.api.metric.name.NameFactory;\nimport com.megaease.easeagent.plugin.tools.metrics.LastMinutesCounterGauge;\nimport com.megaease.easeagent.plugin.utils.ImmutableMap;\n\nimport java.util.concurrent.TimeUnit;\n\npublic class DubboMetrics extends ServiceMetric {\n\n\tpublic static final ServiceMetricSupplier<DubboMetrics> DUBBO_METRICS_SUPPLIER = new ServiceMetricSupplier<DubboMetrics>() {\n\t\t@Override\n\t\tpublic NameFactory newNameFactory() {\n\t\t\treturn nameFactory();\n\t\t}\n\n\t\t@Override\n\t\tpublic DubboMetrics newInstance(MetricRegistry metricRegistry, NameFactory nameFactory) {\n\t\t\treturn new DubboMetrics(metricRegistry, nameFactory);\n\t\t}\n\t};\n\n\tpublic DubboMetrics(MetricRegistry metricRegistry, NameFactory nameFactory) {\n\t\tsuper(metricRegistry, nameFactory);\n\t}\n\n\tprivate static NameFactory nameFactory() {\n\t\treturn NameFactory.createBuilder()\n\t\t\t\t.timerType(MetricSubType.DEFAULT,\n\t\t\t\t\t\tImmutableMap.<MetricField, MetricValueFetcher>builder()\n\t\t\t\t\t\t\t\t.put(MetricField.MIN_EXECUTION_TIME, MetricValueFetcher.SnapshotMinValue)\n\t\t\t\t\t\t\t\t.put(MetricField.MAX_EXECUTION_TIME, MetricValueFetcher.SnapshotMaxValue)\n\t\t\t\t\t\t\t\t.put(MetricField.MEAN_EXECUTION_TIME, MetricValueFetcher.SnapshotMeanValue)\n\t\t\t\t\t\t\t\t.put(MetricField.P25_EXECUTION_TIME, MetricValueFetcher.Snapshot25Percentile)\n\t\t\t\t\t\t\t\t.put(MetricField.P50_EXECUTION_TIME, MetricValueFetcher.Snapshot50PercentileValue)\n\t\t\t\t\t\t\t\t.put(MetricField.P75_EXECUTION_TIME, MetricValueFetcher.Snapshot75PercentileValue)\n\t\t\t\t\t\t\t\t.put(MetricField.P95_EXECUTION_TIME, MetricValueFetcher.Snapshot95PercentileValue)\n\t\t\t\t\t\t\t\t.put(MetricField.P98_EXECUTION_TIME, MetricValueFetcher.Snapshot98PercentileValue)\n\t\t\t\t\t\t\t\t.put(MetricField.P99_EXECUTION_TIME, MetricValueFetcher.Snapshot99PercentileValue)\n\t\t\t\t\t\t\t\t.put(MetricField.P999_EXECUTION_TIME, MetricValueFetcher.Snapshot999PercentileValue)\n\t\t\t\t\t\t\t\t.build())\n\t\t\t\t.meterType(MetricSubType.DEFAULT,\n\t\t\t\t\t\tImmutableMap.<MetricField, MetricValueFetcher>builder()\n\t\t\t\t\t\t\t\t.put(MetricField.M1_RATE, MetricValueFetcher.MeteredM1Rate)\n\t\t\t\t\t\t\t\t.put(MetricField.M5_RATE, MetricValueFetcher.MeteredM5Rate)\n\t\t\t\t\t\t\t\t.put(MetricField.M15_RATE, MetricValueFetcher.MeteredM15Rate)\n\t\t\t\t\t\t\t\t.put(MetricField.MEAN_RATE, MetricValueFetcher.MeteredMeanRate)\n\t\t\t\t\t\t\t\t.build())\n\t\t\t\t.meterType(MetricSubType.ERROR,\n\t\t\t\t\t\tImmutableMap.<MetricField, MetricValueFetcher>builder()\n\t\t\t\t\t\t\t\t.put(MetricField.M1_ERROR_RATE, MetricValueFetcher.MeteredM1Rate)\n\t\t\t\t\t\t\t\t.put(MetricField.M5_ERROR_RATE, MetricValueFetcher.MeteredM5Rate)\n\t\t\t\t\t\t\t\t.put(MetricField.M15_ERROR_RATE, MetricValueFetcher.MeteredM15Rate)\n\t\t\t\t\t\t\t\t.build())\n\t\t\t\t.counterType(MetricSubType.DEFAULT,\n\t\t\t\t\t\tImmutableMap.<MetricField, MetricValueFetcher>builder()\n\t\t\t\t\t\t\t\t.put(MetricField.EXECUTION_COUNT, MetricValueFetcher.CountingCount)\n\t\t\t\t\t\t\t\t.build())\n\t\t\t\t.counterType(MetricSubType.ERROR,\n\t\t\t\t\t\tImmutableMap.<MetricField, MetricValueFetcher>builder()\n\t\t\t\t\t\t\t\t.put(MetricField.EXECUTION_ERROR_COUNT, MetricValueFetcher.CountingCount)\n\t\t\t\t\t\t\t\t.build())\n\t\t\t\t.gaugeType(MetricSubType.DEFAULT,\n\t\t\t\t\t\tImmutableMap.<MetricField, MetricValueFetcher>builder()\n\t\t\t\t\t\t\t\t.build())\n\t\t\t\t.build();\n\t}\n\n\tpublic void collect(String key, long duration, boolean success) {\n\n\t\tthis.timer(key, MetricSubType.DEFAULT).update(duration, TimeUnit.MILLISECONDS);\n\t\tfinal Meter meter = this.meter(key, MetricSubType.DEFAULT);\n\t\tfinal Counter counter = this.counter(key, MetricSubType.DEFAULT);\n\t\tfinal Meter errorMeter = this.meter(key, MetricSubType.ERROR);\n\t\tfinal Counter errorCounter = this.counter(key, MetricSubType.ERROR);\n\t\tmeter.mark();\n\t\tcounter.inc();\n\n\t\tif (!success) {\n\t\t\terrorMeter.mark();\n\t\t\terrorCounter.inc();\n\t\t}\n\n\t\tthis.gauge(key, MetricSubType.DEFAULT, new MetricSupplier<Gauge>() {\n\t\t\t@Override\n\t\t\tpublic Gauge<LastMinutesCounterGauge> newMetric() {\n\t\t\t\treturn () -> LastMinutesCounterGauge.builder()\n\t\t\t\t\t\t.m1Count((long) (meter.getOneMinuteRate() * 60))\n\t\t\t\t\t\t.m5Count((long) (meter.getFiveMinuteRate() * 60 * 5))\n\t\t\t\t\t\t.m15Count((long) (meter.getFifteenMinuteRate() * 60 * 15))\n\t\t\t\t\t\t.build();\n\t\t\t}\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "plugins/dubbo/src/main/java/com/megaease/easeagent/plugin/dubbo/interceptor/metrics/alibaba/AlibabaDubboAsyncMetricsInterceptor.java",
    "content": "package com.megaease.easeagent.plugin.dubbo.interceptor.metrics.alibaba;\n\nimport com.alibaba.dubbo.remoting.exchange.ResponseCallback;\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.AsyncContext;\nimport com.megaease.easeagent.plugin.dubbo.DubboPlugin;\nimport com.megaease.easeagent.plugin.dubbo.advice.AlibabaDubboResponseFutureAdvice;\nimport com.megaease.easeagent.plugin.dubbo.interceptor.metrics.DubboBaseMetricsInterceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\n\n@AdviceTo(value = AlibabaDubboResponseFutureAdvice.class,plugin = DubboPlugin.class)\npublic class AlibabaDubboAsyncMetricsInterceptor extends DubboBaseMetricsInterceptor {\n\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n        ResponseCallback responseCallback = (ResponseCallback) methodInfo.getArgs()[0];\n        AsyncContext asyncContext = context.exportAsync();\n        AlibabaDubboMetricsCallback alibabaDubboMetricsCallback = new AlibabaDubboMetricsCallback(responseCallback, asyncContext);\n        methodInfo.changeArg(0, alibabaDubboMetricsCallback);\n    }\n}\n"
  },
  {
    "path": "plugins/dubbo/src/main/java/com/megaease/easeagent/plugin/dubbo/interceptor/metrics/alibaba/AlibabaDubboMetricsCallback.java",
    "content": "package com.megaease.easeagent.plugin.dubbo.interceptor.metrics.alibaba;\n\nimport com.alibaba.dubbo.remoting.exchange.ResponseCallback;\nimport com.megaease.easeagent.plugin.api.Cleaner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.AsyncContext;\nimport com.megaease.easeagent.plugin.api.context.ContextUtils;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.dubbo.AlibabaDubboCtxUtils;\n\nimport static com.megaease.easeagent.plugin.dubbo.interceptor.metrics.DubboBaseMetricsInterceptor.DUBBO_METRICS;\n\npublic class AlibabaDubboMetricsCallback implements ResponseCallback {\n    private ResponseCallback responseCallback;\n    private AsyncContext asyncContext;\n\n    public AlibabaDubboMetricsCallback(ResponseCallback responseCallback, AsyncContext asyncContext) {\n        this.responseCallback = responseCallback;\n        this.asyncContext = asyncContext;\n    }\n\n    @Override\n    public void done(Object response) {\n        try {\n            responseCallback.done(response);\n        } finally {\n            process(response, null);\n        }\n    }\n\n    @Override\n    public void caught(Throwable exception) {\n        try{\n            responseCallback.caught(exception);\n        } finally {\n            process(null, exception);\n        }\n    }\n\n    private void process(Object response, Throwable throwable ){\n        try(Cleaner cleaner = asyncContext.importToCurrent()) {\n            Context context = EaseAgent.getContext();\n            boolean callResult = AlibabaDubboCtxUtils.checkCallResult(response, throwable);\n            Long duration = ContextUtils.getDuration(context, AlibabaDubboCtxUtils.BEGIN_TIME);\n            String service = context.get(AlibabaDubboCtxUtils.METRICS_SERVICE_NAME);\n            DUBBO_METRICS.collect(service, duration, callResult);\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/dubbo/src/main/java/com/megaease/easeagent/plugin/dubbo/interceptor/metrics/alibaba/AlibabaDubboMetricsInterceptor.java",
    "content": "package com.megaease.easeagent.plugin.dubbo.interceptor.metrics.alibaba;\n\nimport com.alibaba.dubbo.common.Constants;\nimport com.alibaba.dubbo.rpc.Invocation;\nimport com.alibaba.dubbo.rpc.Invoker;\nimport com.alibaba.dubbo.rpc.Result;\nimport com.alibaba.dubbo.rpc.RpcContext;\nimport com.alibaba.dubbo.rpc.protocol.dubbo.FutureAdapter;\nimport com.alibaba.dubbo.rpc.support.RpcUtils;\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.ContextUtils;\nimport com.megaease.easeagent.plugin.dubbo.AlibabaDubboCtxUtils;\nimport com.megaease.easeagent.plugin.dubbo.DubboPlugin;\nimport com.megaease.easeagent.plugin.dubbo.advice.AlibabaDubboAdvice;\nimport com.megaease.easeagent.plugin.dubbo.interceptor.metrics.DubboBaseMetricsInterceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.utils.SystemClock;\n\nimport java.util.concurrent.Future;\n\n\n@AdviceTo(value = AlibabaDubboAdvice.class, plugin = DubboPlugin.class)\npublic class AlibabaDubboMetricsInterceptor extends DubboBaseMetricsInterceptor {\n\n\t@Override\n\tpublic void before(MethodInfo methodInfo, Context context) {\n\t\tcontext.put(AlibabaDubboCtxUtils.BEGIN_TIME, SystemClock.now());\n        Invoker<?> invoker = (Invoker<?>) methodInfo.getArgs()[0];\n        Invocation invocation = (Invocation) methodInfo.getArgs()[1];\n        boolean isAsync = RpcUtils.isAsync(invoker.getUrl(), invocation);\n        boolean isConsumer = invoker.getUrl().getParameter(Constants.SIDE_KEY, Constants.PROVIDER_SIDE).equals(Constants.CONSUMER_SIDE);\n        if (isConsumer && isAsync) {\n\t\t    String interfaceSignature = AlibabaDubboCtxUtils.interfaceSignature(invocation);\n            context.put(AlibabaDubboCtxUtils.METRICS_SERVICE_NAME, interfaceSignature);\n        }\n\t}\n\n\t@Override\n\tpublic void after(MethodInfo methodInfo, Context context) {\n\t\tInvoker<?> invoker = (Invoker<?>) methodInfo.getArgs()[0];\n\t\tInvocation invocation = (Invocation) methodInfo.getArgs()[1];\n\n\t\tboolean isAsync = RpcUtils.isAsync(invoker.getUrl(), invocation);\n        Future<?> f = RpcContext.getContext().getFuture();\n\t\tif (isAsync && f instanceof FutureAdapter) {\n            return;\n\t\t}\n\n\t\tLong duration = ContextUtils.getDuration(context, AlibabaDubboCtxUtils.BEGIN_TIME);\n\t\tResult retValue = (Result) methodInfo.getRetValue();\n\t\tString interfaceSignature = AlibabaDubboCtxUtils.interfaceSignature(invocation);\n        boolean callResult = AlibabaDubboCtxUtils.checkCallResult(retValue, methodInfo.getThrowable());\n        DUBBO_METRICS.collect(interfaceSignature, duration, callResult);\n\t}\n}\n"
  },
  {
    "path": "plugins/dubbo/src/main/java/com/megaease/easeagent/plugin/dubbo/interceptor/metrics/apache/ApacheDubboMetricsAsyncCallback.java",
    "content": "package com.megaease.easeagent.plugin.dubbo.interceptor.metrics.apache;\n\nimport com.megaease.easeagent.plugin.api.Cleaner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.AsyncContext;\nimport com.megaease.easeagent.plugin.api.context.ContextUtils;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.dubbo.ApacheDubboCtxUtils;\nimport org.apache.dubbo.rpc.Result;\n\nimport java.util.function.BiConsumer;\n\nimport static com.megaease.easeagent.plugin.dubbo.interceptor.metrics.DubboBaseMetricsInterceptor.DUBBO_METRICS;\n\npublic class ApacheDubboMetricsAsyncCallback implements BiConsumer<Result,Throwable> {\n\n    private final AsyncContext asyncContext;\n\n    public ApacheDubboMetricsAsyncCallback(AsyncContext asyncContext) {\n        this.asyncContext = asyncContext;\n    }\n\n    @Override\n    public void accept(Result result, Throwable throwable) {\n        try(Cleaner cleaner = asyncContext.importToCurrent()) {\n            Context context = EaseAgent.getContext();\n            Long duration = ContextUtils.getDuration(context,ApacheDubboCtxUtils.BEGIN_TIME);\n            boolean callResult = ApacheDubboCtxUtils.checkCallResult(result, throwable);\n            String service = context.get(ApacheDubboCtxUtils.METRICS_SERVICE_NAME);\n            DUBBO_METRICS.collect(service, duration, callResult);\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/dubbo/src/main/java/com/megaease/easeagent/plugin/dubbo/interceptor/metrics/apache/ApacheDubboMetricsInterceptor.java",
    "content": "package com.megaease.easeagent.plugin.dubbo.interceptor.metrics.apache;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.ContextUtils;\nimport com.megaease.easeagent.plugin.dubbo.ApacheDubboCtxUtils;\nimport com.megaease.easeagent.plugin.dubbo.DubboPlugin;\nimport com.megaease.easeagent.plugin.dubbo.advice.ApacheDubboAdvice;\nimport com.megaease.easeagent.plugin.dubbo.interceptor.metrics.DubboBaseMetricsInterceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.utils.SystemClock;\nimport org.apache.dubbo.rpc.AsyncRpcResult;\nimport org.apache.dubbo.rpc.Invocation;\nimport org.apache.dubbo.rpc.Invoker;\nimport org.apache.dubbo.rpc.Result;\n\n@AdviceTo(value = ApacheDubboAdvice.class, plugin = DubboPlugin.class)\npublic class ApacheDubboMetricsInterceptor extends DubboBaseMetricsInterceptor {\n\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n        context.put(ApacheDubboCtxUtils.BEGIN_TIME, SystemClock.now());\n    }\n\n    @Override\n    public void after(MethodInfo methodInfo, Context context) {\n        Invoker<?> invoker = (Invoker<?>) methodInfo.getArgs()[0];\n        Invocation invocation = (Invocation) methodInfo.getArgs()[1];\n        if (invoker.getUrl().getPath().equals(ApacheDubboCtxUtils.METADATA_INTERFACE)) {\n            return;\n        }\n\n        String service = ApacheDubboCtxUtils.interfaceSignature(invocation);\n        context.put(ApacheDubboCtxUtils.METRICS_SERVICE_NAME, service);\n        Result retValue = (Result) methodInfo.getRetValue();\n        if (retValue instanceof AsyncRpcResult) {\n            retValue.whenCompleteWithContext(new ApacheDubboMetricsAsyncCallback(context.exportAsync()));\n        } else {\n            Long duration = ContextUtils.getDuration(context, ApacheDubboCtxUtils.BEGIN_TIME);\n            boolean callResult = ApacheDubboCtxUtils.checkCallResult(retValue, methodInfo.getThrowable());\n            DUBBO_METRICS.collect(service, duration, callResult);\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/dubbo/src/main/java/com/megaease/easeagent/plugin/dubbo/interceptor/trace/alibaba/AlibabaDubboAsyncTraceInterceptor.java",
    "content": "package com.megaease.easeagent.plugin.dubbo.interceptor.trace.alibaba;\n\nimport com.alibaba.dubbo.remoting.exchange.ResponseCallback;\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.AsyncContext;\nimport com.megaease.easeagent.plugin.api.context.RequestContext;\nimport com.megaease.easeagent.plugin.api.trace.Scope;\nimport com.megaease.easeagent.plugin.dubbo.AlibabaDubboCtxUtils;\nimport com.megaease.easeagent.plugin.dubbo.DubboPlugin;\nimport com.megaease.easeagent.plugin.dubbo.advice.AlibabaDubboResponseFutureAdvice;\nimport com.megaease.easeagent.plugin.dubbo.interceptor.DubboBaseInterceptor;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\n\n@AdviceTo(value = AlibabaDubboResponseFutureAdvice.class,plugin = DubboPlugin.class)\npublic class AlibabaDubboAsyncTraceInterceptor extends DubboBaseInterceptor {\n    @Override\n    public int order() {\n        return Order.TRACING.getOrder();\n    }\n\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n        ResponseCallback responseCallback = (ResponseCallback) methodInfo.getArgs()[0];\n        AsyncContext asyncContext = context.exportAsync();\n        RequestContext requestContext = context.get(AlibabaDubboCtxUtils.CLIENT_REQUEST_CONTEXT);\n        try(Scope scope = requestContext.scope()) {\n            methodInfo.changeArg(0, new AlibabaDubboTraceCallback(responseCallback, asyncContext));\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/dubbo/src/main/java/com/megaease/easeagent/plugin/dubbo/interceptor/trace/alibaba/AlibabaDubboClientRequest.java",
    "content": "package com.megaease.easeagent.plugin.dubbo.interceptor.trace.alibaba;\n\nimport com.alibaba.dubbo.rpc.Invocation;\nimport com.alibaba.dubbo.rpc.Invoker;\nimport com.megaease.easeagent.plugin.api.trace.Request;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.dubbo.AlibabaDubboCtxUtils;\n\n\npublic class AlibabaDubboClientRequest implements Request {\n\n\tprivate final Invoker<?> invoker;\n\tprivate final Invocation invocation;\n\n\tpublic AlibabaDubboClientRequest(Invoker<?> invoker, Invocation invocation) {\n\t\tthis.invoker = invoker;\n\t\tthis.invocation = invocation;\n\t}\n\n\t@Override\n\tpublic Span.Kind kind() {\n\t\treturn Span.Kind.CLIENT;\n\t}\n\n\t@Override\n\tpublic String header(String name) {\n\t\treturn invocation.getAttachment(name);\n\t}\n\n\t@Override\n\tpublic String name() {\n\t\treturn AlibabaDubboCtxUtils.name(invocation);\n\t}\n\n\t@Override\n\tpublic boolean cacheScope() {\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic void setHeader(String name, String value) {\n\t\tinvocation.getAttachments().put(name, value);\n\t}\n}\n"
  },
  {
    "path": "plugins/dubbo/src/main/java/com/megaease/easeagent/plugin/dubbo/interceptor/trace/alibaba/AlibabaDubboServerRequest.java",
    "content": "package com.megaease.easeagent.plugin.dubbo.interceptor.trace.alibaba;\n\nimport com.alibaba.dubbo.rpc.Invocation;\nimport com.alibaba.dubbo.rpc.Invoker;\nimport com.megaease.easeagent.plugin.api.trace.Request;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.dubbo.AlibabaDubboCtxUtils;\n\npublic class AlibabaDubboServerRequest implements Request {\n\n\tprivate final Invoker<?> invoker;\n\tprivate final Invocation invocation;\n\n\tpublic AlibabaDubboServerRequest(Invoker<?> invoker, Invocation invocation) {\n\t\tthis.invoker = invoker;\n\t\tthis.invocation = invocation;\n\t}\n\n\t@Override\n\tpublic Span.Kind kind() {\n\t\treturn Span.Kind.SERVER;\n\t}\n\n\t@Override\n\tpublic String header(String name) {\n\t\treturn invocation.getAttachments().get(name);\n\t}\n\n\t@Override\n\tpublic String name() {\n\t\treturn AlibabaDubboCtxUtils.name(invocation);\n\t}\n\n\t@Override\n\tpublic boolean cacheScope() {\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic void setHeader(String name, String value) {\n\t\tinvocation.getAttachments().put(name, value);\n\t}\n}\n"
  },
  {
    "path": "plugins/dubbo/src/main/java/com/megaease/easeagent/plugin/dubbo/interceptor/trace/alibaba/AlibabaDubboTraceCallback.java",
    "content": "package com.megaease.easeagent.plugin.dubbo.interceptor.trace.alibaba;\n\nimport com.alibaba.dubbo.remoting.exchange.ResponseCallback;\nimport com.alibaba.dubbo.rpc.Result;\nimport com.megaease.easeagent.plugin.api.Cleaner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.AsyncContext;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.dubbo.AlibabaDubboCtxUtils;\n\npublic class AlibabaDubboTraceCallback implements ResponseCallback {\n    private ResponseCallback responseCallback;\n    private AsyncContext asyncContext;\n\n    public AlibabaDubboTraceCallback(ResponseCallback responseCallback, AsyncContext asyncContext) {\n        this.responseCallback = responseCallback;\n        this.asyncContext = asyncContext;\n    }\n\n    @Override\n    public void done(Object response) {\n        try {\n            responseCallback.done(response);\n        } finally {\n            process(response, null);\n        }\n    }\n\n    @Override\n    public void caught(Throwable exception) {\n        try{\n            responseCallback.caught(exception);\n        } finally {\n            process(null, exception);\n        }\n    }\n\n    private void process(Object response, Throwable throwable ){\n        try(Cleaner cleaner = asyncContext.importToCurrent()) {\n            Context context = EaseAgent.getContext();\n            Result result = (Result) response;\n            AlibabaDubboCtxUtils.finishSpan(context, result, throwable);\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/dubbo/src/main/java/com/megaease/easeagent/plugin/dubbo/interceptor/trace/alibaba/AlibabaDubboTraceInterceptor.java",
    "content": "package com.megaease.easeagent.plugin.dubbo.interceptor.trace.alibaba;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.dubbo.AlibabaDubboCtxUtils;\nimport com.megaease.easeagent.plugin.dubbo.DubboPlugin;\nimport com.megaease.easeagent.plugin.dubbo.advice.AlibabaDubboAdvice;\nimport com.megaease.easeagent.plugin.dubbo.interceptor.DubboBaseInterceptor;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\n\n@AdviceTo(value = AlibabaDubboAdvice.class, plugin = DubboPlugin.class)\npublic class AlibabaDubboTraceInterceptor extends DubboBaseInterceptor {\n\n\n\t@Override\n\tpublic String getType() {\n\t\treturn Order.TRACING.getName();\n\t}\n\n\t@Override\n\tpublic int order() {\n\t\treturn Order.TRACING.getOrder();\n\t}\n\n\t@Override\n\tpublic void before(MethodInfo methodInfo, Context context) {\n\t\tAlibabaDubboCtxUtils.initSpan(methodInfo, context);\n\t}\n\n\t@Override\n\tpublic void after(MethodInfo methodInfo, Context context) {\n\t\tAlibabaDubboCtxUtils.finishSpan(context, methodInfo);\n\t}\n\n}\n"
  },
  {
    "path": "plugins/dubbo/src/main/java/com/megaease/easeagent/plugin/dubbo/interceptor/trace/apache/ApacheDubboClientRequest.java",
    "content": "package com.megaease.easeagent.plugin.dubbo.interceptor.trace.apache;\n\nimport com.megaease.easeagent.plugin.api.trace.Request;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.dubbo.ApacheDubboCtxUtils;\nimport org.apache.dubbo.rpc.Invocation;\nimport org.apache.dubbo.rpc.Invoker;\n\npublic class ApacheDubboClientRequest implements Request {\n\n\tprivate final Invoker<?> invoker;\n\tprivate final Invocation invocation;\n\n\tpublic ApacheDubboClientRequest(Invoker<?> invoker, Invocation invocation) {\n\t\tthis.invoker = invoker;\n\t\tthis.invocation = invocation;\n\t}\n\n\t@Override\n\tpublic Span.Kind kind() {\n\t\treturn Span.Kind.CLIENT;\n\t}\n\n\t@Override\n\tpublic String header(String name) {\n\t\treturn invocation.getAttachment(name.toLowerCase());\n\t}\n\n\t@Override\n\tpublic String name() {\n\t\treturn ApacheDubboCtxUtils.name(invocation);\n\t}\n\n\t@Override\n\tpublic boolean cacheScope() {\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic void setHeader(String name, String value) {\n\t\tinvocation.getAttachments().put(name, value);\n\t}\n}\n"
  },
  {
    "path": "plugins/dubbo/src/main/java/com/megaease/easeagent/plugin/dubbo/interceptor/trace/apache/ApacheDubboServerRequest.java",
    "content": "package com.megaease.easeagent.plugin.dubbo.interceptor.trace.apache;\n\nimport com.megaease.easeagent.plugin.api.trace.Request;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.dubbo.ApacheDubboCtxUtils;\nimport org.apache.dubbo.rpc.Invocation;\nimport org.apache.dubbo.rpc.Invoker;\n\npublic class ApacheDubboServerRequest implements Request {\n\n\tprivate final Invoker<?> invoker;\n\tprivate final Invocation invocation;\n\n\tpublic ApacheDubboServerRequest(Invoker<?> invoker, Invocation invocation) {\n\t\tthis.invoker = invoker;\n\t\tthis.invocation = invocation;\n\t}\n\n\t@Override\n\tpublic Span.Kind kind() {\n\t\treturn Span.Kind.SERVER;\n\t}\n\n\t@Override\n\tpublic String header(String name) {\n\t\treturn invocation.getAttachments().get(name.toLowerCase());\n\t}\n\n\t@Override\n\tpublic String name() {\n\t\treturn ApacheDubboCtxUtils.name(invocation);\n\t}\n\n\t@Override\n\tpublic boolean cacheScope() {\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic void setHeader(String name, String value) {\n\t\tinvocation.getAttachments().put(name, value);\n\t}\n}\n"
  },
  {
    "path": "plugins/dubbo/src/main/java/com/megaease/easeagent/plugin/dubbo/interceptor/trace/apache/ApacheDubboTraceCallback.java",
    "content": "package com.megaease.easeagent.plugin.dubbo.interceptor.trace.apache;\n\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.dubbo.ApacheDubboCtxUtils;\nimport org.apache.dubbo.rpc.Result;\n\nimport java.util.function.BiConsumer;\n\npublic class ApacheDubboTraceCallback implements BiConsumer<Result, Throwable> {\n    private Span span;\n\n    public ApacheDubboTraceCallback(Span span) {\n        this.span = span;\n    }\n\n    @Override\n    public void accept(Result result, Throwable throwable) {\n        ApacheDubboCtxUtils.doFinishSpan(span, result, throwable);\n    }\n}\n"
  },
  {
    "path": "plugins/dubbo/src/main/java/com/megaease/easeagent/plugin/dubbo/interceptor/trace/apache/ApacheDubboTraceInterceptor.java",
    "content": "package com.megaease.easeagent.plugin.dubbo.interceptor.trace.apache;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.dubbo.ApacheDubboCtxUtils;\nimport com.megaease.easeagent.plugin.dubbo.DubboPlugin;\nimport com.megaease.easeagent.plugin.dubbo.advice.ApacheDubboAdvice;\nimport com.megaease.easeagent.plugin.dubbo.interceptor.DubboBaseInterceptor;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport org.apache.dubbo.rpc.Invoker;\nimport org.apache.dubbo.rpc.Result;\n\n@AdviceTo(value = ApacheDubboAdvice.class, plugin = DubboPlugin.class)\npublic class ApacheDubboTraceInterceptor extends DubboBaseInterceptor {\n\n\n\t@Override\n\tpublic String getType() {\n\t\treturn Order.TRACING.getName();\n\t}\n\n\t@Override\n\tpublic int order() {\n\t\treturn Order.TRACING.getOrder();\n\t}\n\n\t@Override\n\tpublic void before(MethodInfo methodInfo, Context context) {\n\t\tApacheDubboCtxUtils.initSpan(methodInfo, context);\n\t}\n\n\t@Override\n\tpublic void after(MethodInfo methodInfo, Context context) {\n\t\tResult result = (Result) methodInfo.getRetValue();\n\t\tInvoker<?> invoker = (Invoker<?>) methodInfo.getArgs()[0];\n\t\tApacheDubboCtxUtils.finishSpan(invoker.getUrl(), context, result, methodInfo.getThrowable());\n\t}\n\n}\n"
  },
  {
    "path": "plugins/dubbo/src/test/java/com/megaease/easeagent/plugin/dubbo/interceptor/AlibabaDubboBaseTest.java",
    "content": "package com.megaease.easeagent.plugin.dubbo.interceptor;\n\nimport com.alibaba.dubbo.common.Constants;\nimport com.alibaba.dubbo.common.URL;\nimport com.alibaba.dubbo.remoting.Channel;\nimport com.alibaba.dubbo.remoting.exchange.Request;\nimport com.alibaba.dubbo.remoting.exchange.ResponseCallback;\nimport com.alibaba.dubbo.remoting.exchange.support.DefaultFuture;\nimport com.alibaba.dubbo.rpc.Invocation;\nimport com.alibaba.dubbo.rpc.Invoker;\nimport com.alibaba.dubbo.rpc.RpcInvocation;\nimport com.alibaba.dubbo.rpc.RpcResult;\nimport com.alibaba.dubbo.rpc.protocol.dubbo.FutureAdapter;\nimport com.megaease.easeagent.mock.config.MockConfig;\nimport com.megaease.easeagent.mock.plugin.api.MockEaseAgent;\nimport com.megaease.easeagent.mock.plugin.api.utils.InterceptorTestUtils;\nimport com.megaease.easeagent.mock.plugin.api.utils.TagVerifier;\nimport com.megaease.easeagent.mock.report.impl.LastJsonReporter;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.api.metric.name.MetricField;\nimport com.megaease.easeagent.plugin.api.metric.name.Tags;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.dubbo.AlibabaDubboCtxUtils;\nimport com.megaease.easeagent.plugin.dubbo.DubboMetricTags;\nimport com.megaease.easeagent.plugin.dubbo.DubboPlugin;\nimport com.megaease.easeagent.plugin.dubbo.DubboTraceTags;\nimport com.megaease.easeagent.plugin.dubbo.config.DubboTraceConfig;\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport com.megaease.easeagent.plugin.utils.common.JsonUtil;\nimport org.junit.Before;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static com.alibaba.dubbo.common.Constants.CONSUMER_SIDE;\nimport static com.alibaba.dubbo.common.Constants.SIDE_KEY;\nimport static org.junit.Assert.*;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\n\npublic abstract class AlibabaDubboBaseTest {\n\n\t@Mock\n\tprotected Invoker<?> consumerInvoker;\n\n\tprotected Invocation consumerInvocation;\n\n\tprotected Invocation asyncConsumerInvocation;\n\n\t@Mock\n\tprotected Invoker<?> providerInvoker;\n\n\tprotected Invocation providerInvocation;\n\n\tprotected RpcResult successResult = new RpcResult(\"hello\");\n\n\tprotected RpcResult failureResult = new RpcResult(new IllegalArgumentException(\"mock exception\"));\n    protected FutureAdapter<Object> futureAdapter;\n\n    protected EmptyResponseCallback responseCallback = new EmptyResponseCallback();\n\n    protected static class EmptyResponseCallback implements ResponseCallback{\n        private Throwable throwable;\n\n        public Throwable throwable() {\n            return throwable;\n        }\n\n        @Override\n        public void done(Object response) {\n        }\n\n        @Override\n        public void caught(Throwable exception) {\n            this.throwable = exception;\n        }\n    }\n\n\tprotected abstract Interceptor createInterceptor();\n\n    @Before\n\tpublic void init() {\n\t\tMockitoAnnotations.openMocks(this);\n\n\t\tURL consumerURL = URL.valueOf(\"dubbo://127.0.0.1:0/com.magaease.easeagent.service.DubboService?side=consumer&application=dubbo-consumer&group=consumer&version=1.0.0\");\n\t\twhen(consumerInvoker.getUrl()).thenReturn(consumerURL);\n\t\tMap<String, String> consumerAttachment = new HashMap<>();\n\t\tconsumerInvocation = new RpcInvocation(\"sayHello\", new Class<?>[]{String.class}, new Object[]{\"hello\"}, consumerAttachment, consumerInvoker);\n\n\t\tMap<String, String> asyncConsumerAttachment = new HashMap<>();\n\t\tasyncConsumerAttachment.put(Constants.ASYNC_KEY, Boolean.TRUE.toString());\n\t\tasyncConsumerInvocation = new RpcInvocation(\"sayHello\", new Class<?>[]{String.class}, new Object[]{\"hello\"}, asyncConsumerAttachment, consumerInvoker);\n\n\t\tMap<String, String> providerAttachment = new HashMap<>();\n\t\tURL providerURL = URL.valueOf(\"dubbo://127.0.0.1:20880/com.magaease.easeagent.service.DubboService?side=provider&application=dubbo-provider&group=provider&version=1.0.0\");\n\t\twhen(providerInvoker.getUrl()).thenReturn(providerURL);\n\t\tproviderInvocation = new RpcInvocation(\"sayHello\", new Class[]{String.class}, new Object[]{\"hello\"}, providerAttachment, providerInvoker);\n\n        Request request = new Request(1);\n        Channel channel = mock(Channel.class);\n        DefaultFuture defaultFuture = new DefaultFuture(channel,request,300);\n        futureAdapter = new FutureAdapter<>(defaultFuture);\n\n        EaseAgent.configFactory = MockConfig.getPluginConfigManager();\n\t    DubboPlugin dubboPlugin = new DubboPlugin();\n\t    InterceptorTestUtils.init(createInterceptor(),dubboPlugin);\n\t}\n\n\tprotected void assertSuccessMetrics() {\n\t\tassertMetrics(true);\n\t}\n\n\tprotected void assertFailureMetrics() {\n\t\tassertMetrics(false);\n\t}\n\n\tprivate void assertMetrics(boolean success) {\n\t\tTagVerifier tagVerifier = new TagVerifier()\n\t\t\t\t.add(Tags.CATEGORY, DubboMetricTags.CATEGORY.name)\n\t\t\t\t.add(Tags.TYPE, DubboMetricTags.TYPE.name)\n\t\t\t\t.add(DubboMetricTags.LABEL_NAME.name, AlibabaDubboCtxUtils.interfaceSignature(asyncConsumerInvocation));\n\t\tLastJsonReporter lastJsonReporter = MockEaseAgent.lastMetricJsonReporter(tagVerifier::verifyAnd);\n\t\tMap<String, Object> metric = lastJsonReporter.flushAndOnlyOne();\n\t\tassertEquals(1, metric.get(MetricField.EXECUTION_COUNT.getField()));\n\t\tif (success) {\n\t\t\tassertEquals(0, metric.get(MetricField.EXECUTION_ERROR_COUNT.getField()));\n\t\t} else {\n\t\t\tassertEquals(1, metric.get(MetricField.EXECUTION_ERROR_COUNT.getField()));\n\t\t}\n\t}\n\n\tprotected void assertConsumerTrace(Object retValue, String errorMessage) {\n\t\tassertTrace(consumerInvocation, retValue, errorMessage);\n\t}\n\n\tprotected void assertProviderTrace(Object retValue, String errorMessage) {\n\t\tassertTrace(providerInvocation, retValue, errorMessage);\n\t}\n\n\n\tprotected void assertTrace(Invocation invocation, Object retValue, String errorMessage) {\n\t\tDubboTraceConfig dubboTraceConfig = DubboBaseInterceptor.DUBBO_TRACE_CONFIG;\n\t\tURL url = invocation.getInvoker().getUrl();\n\t\tboolean isConsumer = url.getParameter(SIDE_KEY).equals(CONSUMER_SIDE);\n\n\t\tReportSpan mockSpan = MockEaseAgent.getLastSpan();\n\t\tassertNotNull(mockSpan);\n\t\tassertEquals(AlibabaDubboCtxUtils.name(invocation).toLowerCase(), mockSpan.name());\n\t\tassertEquals(ConfigConst.Namespace.DUBBO, mockSpan.remoteServiceName());\n\t\tif (isConsumer) {\n\t\t\tassertEquals(Span.Kind.CLIENT.name(), mockSpan.kind());\n\t\t\tassertEquals(url.getParameter(Constants.APPLICATION_KEY), mockSpan.tag(DubboTraceTags.CLIENT_APPLICATION.name));\n\t\t\tassertEquals(url.getParameter(Constants.GROUP_KEY), mockSpan.tag(DubboTraceTags.GROUP.name));\n\t\t\tassertEquals(url.getParameter(Constants.VERSION_KEY), mockSpan.tag(DubboTraceTags.SERVICE_VERSION.name));\n\t\t\tassertEquals(AlibabaDubboCtxUtils.method(invocation), mockSpan.tag(DubboTraceTags.METHOD.name));\n\t\t\tassertEquals(url.getPath(), mockSpan.tag(DubboTraceTags.SERVICE.name));\n\t\t\tString expectedArgs = dubboTraceConfig.argsCollectEnabled() ? JsonUtil.toJson(invocation.getArguments()) : null;\n\t\t\tString expectedResult = dubboTraceConfig.resultCollectEnabled() && retValue != null ? JsonUtil.toJson(retValue) : null;\n\t\t\tassertEquals(expectedArgs, mockSpan.tag(DubboTraceTags.ARGS.name));\n\t\t\tassertEquals(expectedResult, mockSpan.tag(DubboTraceTags.RESULT.name));\n\t\t} else {\n\t\t\tassertEquals(Span.Kind.SERVER.name(), mockSpan.kind());\n\t\t\tassertEquals(url.getParameter(Constants.APPLICATION_KEY), mockSpan.tag(DubboTraceTags.SERVER_APPLICATION.name));\n\t\t}\n\n\t\tif (retValue != null) {\n\t\t\tassertFalse(mockSpan.hasError());\n\t\t\tassertEquals(successResult.getValue(), retValue);\n\t\t} else {\n\t\t\tassertTrue(mockSpan.hasError());\n            if (errorMessage != null) {\n    \t\t\tassertEquals(errorMessage, mockSpan.errorInfo());\n            }\n\t\t}\n\t\tassertNull(mockSpan.parentId());\n\t}\n\n}\n"
  },
  {
    "path": "plugins/dubbo/src/test/java/com/megaease/easeagent/plugin/dubbo/interceptor/ApacheDubboBaseTest.java",
    "content": "package com.megaease.easeagent.plugin.dubbo.interceptor;\n\nimport com.megaease.easeagent.mock.config.MockConfig;\nimport com.megaease.easeagent.mock.plugin.api.MockEaseAgent;\nimport com.megaease.easeagent.mock.plugin.api.utils.InterceptorTestUtils;\nimport com.megaease.easeagent.mock.plugin.api.utils.TagVerifier;\nimport com.megaease.easeagent.mock.report.impl.LastJsonReporter;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.api.metric.name.MetricField;\nimport com.megaease.easeagent.plugin.api.metric.name.Tags;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.dubbo.*;\nimport com.megaease.easeagent.plugin.dubbo.config.DubboTraceConfig;\nimport com.megaease.easeagent.plugin.dubbo.interceptor.trace.apache.ApacheDubboTraceInterceptor;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport com.megaease.easeagent.plugin.utils.common.JsonUtil;\nimport org.apache.dubbo.common.URL;\nimport org.apache.dubbo.common.constants.CommonConstants;\nimport org.apache.dubbo.rpc.*;\nimport org.junit.Before;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static org.apache.dubbo.common.constants.CommonConstants.CONSUMER_SIDE;\nimport static org.apache.dubbo.common.constants.CommonConstants.SIDE_KEY;\nimport static org.junit.Assert.*;\nimport static org.mockito.Mockito.when;\n\n\npublic abstract class ApacheDubboBaseTest {\n\n\n\t@Mock\n\tprotected Invoker<?> consumerInvoker;\n\n\tprotected Invocation consumerInvocation;\n\n\t@Mock\n\tprotected Invoker<?> providerInvoker;\n\n\tprotected Invocation providerInvocation;\n\n\tprotected AppResponse successResponse = new AppResponse(\"test\");\n\n\tprotected AppResponse failureResponse = new AppResponse(new RuntimeException(\"mock exception\"));\n\n    protected AsyncRpcResult asyncRpcResult = new AsyncRpcResult((Invocation) null);\n\n\tprotected abstract Interceptor createInterceptor();\n\n\t@Before\n\tpublic void init() {\n\t\tMockitoAnnotations.openMocks(this);\n\n\t\tURL consumerURL = URL.valueOf(\"dubbo://127.0.0.1:0/com.magaease.easeagent.service.DubboService?side=consumer&application=dubbo-consumer&group=consumer&version=1.0.0\");\n\t\twhen(consumerInvoker.getUrl()).thenReturn(consumerURL);\n\t\tMap<String, String> consumerAttachment = new HashMap<>();\n\t\tconsumerInvocation = new RpcInvocation(\"sayHello\", new Class[]{String.class}, new Object[]{\"hello\"}, consumerAttachment, consumerInvoker);\n\n\t\tMap<String, String> providerAttachment = new HashMap<>();\n\t\tURL providerURL = URL.valueOf(\"dubbo://127.0.0.1:20880/com.magaease.easeagent.service.DubboService?side=provider&application=dubbo-provider&group=provider&version=1.0.0\");\n\t\twhen(providerInvoker.getUrl()).thenReturn(providerURL);\n\t\tproviderInvocation = new RpcInvocation(\"sayHello\", new Class[]{String.class}, new Object[]{\"hello\"}, providerAttachment, providerInvoker);\n\n\t\tEaseAgent.configFactory = MockConfig.getPluginConfigManager();\n\t\tDubboPlugin dubboPlugin = new DubboPlugin();\n\t\tInterceptorTestUtils.init(createInterceptor(), dubboPlugin);\n\t}\n\n\tprotected void assertSuccessMetrics() {\n\t\tassertMetrics(true);\n\t}\n\n\tprotected void assertFailureMetrics() {\n\t\tassertMetrics(false);\n\t}\n\n\tprivate void assertMetrics(boolean success) {\n\t\tTagVerifier tagVerifier = new TagVerifier()\n\t\t\t\t.add(Tags.CATEGORY, DubboMetricTags.CATEGORY.name)\n\t\t\t\t.add(Tags.TYPE, DubboMetricTags.TYPE.name)\n\t\t\t\t.add(DubboMetricTags.LABEL_NAME.name, ApacheDubboCtxUtils.interfaceSignature(consumerInvocation));\n\t\tLastJsonReporter lastJsonReporter = MockEaseAgent.lastMetricJsonReporter(tagVerifier::verifyAnd);\n\t\tMap<String, Object> metric = lastJsonReporter.flushAndOnlyOne();\n\t\tassertEquals(1, metric.get(MetricField.EXECUTION_COUNT.getField()));\n\t\tif (success) {\n\t\t\tassertEquals(0, metric.get(MetricField.EXECUTION_ERROR_COUNT.getField()));\n\t\t} else {\n\t\t\tassertEquals(1, metric.get(MetricField.EXECUTION_ERROR_COUNT.getField()));\n\t\t}\n\t}\n\n\n\tprotected void assertConsumerTrace(Object retValue, String errorMessage) {\n\t\tassertTrace(consumerInvocation, retValue, errorMessage);\n\t}\n\n\tprotected void assertProviderTrace(Object retValue, String errorMessage) {\n\t\tassertTrace(providerInvocation, retValue, errorMessage);\n\t}\n\n\n\tprotected void assertTrace(Invocation invocation, Object retValue, String errorMessage) {\n\t\tDubboTraceConfig dubboTraceConfig = AgentFieldReflectAccessor.getStaticFieldValue(ApacheDubboTraceInterceptor.class, \"DUBBO_TRACE_CONFIG\");\n\t\tURL url = invocation.getInvoker().getUrl();\n\t\tboolean isConsumer = url.getParameter(SIDE_KEY).equals(CONSUMER_SIDE);\n\t\tReportSpan mockSpan = MockEaseAgent.getLastSpan();\n\t\tassertNotNull(mockSpan);\n\t\tif (isConsumer) {\n\t\t\tassertEquals(Span.Kind.CLIENT.name(), mockSpan.kind());\n\t\t\tassertEquals(url.getParameter(CommonConstants.APPLICATION_KEY), mockSpan.tag(DubboTraceTags.CLIENT_APPLICATION.name));\n\t\t\tassertEquals(url.getParameter(CommonConstants.GROUP_KEY), mockSpan.tag(DubboTraceTags.GROUP.name));\n\t\t\tassertEquals(url.getParameter(CommonConstants.VERSION_KEY), mockSpan.tag(DubboTraceTags.SERVICE_VERSION.name));\n\t\t\tassertEquals(ApacheDubboCtxUtils.method(invocation), mockSpan.tag(DubboTraceTags.METHOD.name));\n\t\t\tassertEquals(url.getPath(), mockSpan.tag(DubboTraceTags.SERVICE.name));\n\t\t\tString expectedArgs = dubboTraceConfig.argsCollectEnabled() ? JsonUtil.toJson(invocation.getArguments()) : null;\n\t\t\tString expectedResult = dubboTraceConfig.resultCollectEnabled() && retValue != null ? JsonUtil.toJson(retValue) : null;\n\t\t\tassertEquals(expectedArgs, mockSpan.tag(DubboTraceTags.ARGS.name));\n\t\t\tassertEquals(expectedResult, mockSpan.tag(DubboTraceTags.RESULT.name));\n\t\t} else {\n\t\t\tassertEquals(Span.Kind.SERVER.name(), mockSpan.kind());\n\t\t\tassertEquals(url.getParameter(CommonConstants.APPLICATION_KEY), mockSpan.tag(DubboTraceTags.SERVER_APPLICATION.name));\n\t\t}\n\t\tassertEquals(ApacheDubboCtxUtils.name(invocation).toLowerCase(), mockSpan.name());\n\t\tassertEquals(ConfigConst.Namespace.DUBBO, mockSpan.remoteServiceName());\n\t\tif (retValue != null) {\n\t\t\tassertFalse(mockSpan.hasError());\n\t\t\tassertEquals(successResponse.getValue(), retValue);\n\t\t} else {\n\t\t\tassertTrue(mockSpan.hasError());\n\t\t\tassertEquals(errorMessage, mockSpan.errorInfo());\n\t\t}\n\t\tassertNull(mockSpan.parentId());\n\t}\n\n}\n"
  },
  {
    "path": "plugins/dubbo/src/test/java/com/megaease/easeagent/plugin/dubbo/interceptor/metrics/alibaba/AlibabaDubboAsyncMetricsInterceptorTest.java",
    "content": "package com.megaease.easeagent.plugin.dubbo.interceptor.metrics.alibaba;\n\nimport com.alibaba.dubbo.rpc.RpcContext;\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.dubbo.interceptor.AlibabaDubboBaseTest;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport java.util.concurrent.TimeUnit;\n\nimport static org.junit.Assert.assertEquals;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class AlibabaDubboAsyncMetricsInterceptorTest extends AlibabaDubboBaseTest {\n\n\tprivate static final AlibabaDubboMetricsInterceptor alibabaDubboMetricsInterceptor = new AlibabaDubboMetricsInterceptor();\n\tprivate static final AlibabaDubboAsyncMetricsInterceptor alibabaDubboAsyncMetricsInterceptor = new AlibabaDubboAsyncMetricsInterceptor();\n\n\n\t@Test\n\tpublic void order() {\n\t\tassertEquals(Order.METRIC.getOrder(), alibabaDubboAsyncMetricsInterceptor.order());\n\t}\n\n\t@Test\n\tpublic void getType() {\n\t\tassertEquals(Order.METRIC.getName(), alibabaDubboAsyncMetricsInterceptor.getType());\n\t}\n\n\t@Override\n\tprotected Interceptor createInterceptor() {\n\t\treturn alibabaDubboMetricsInterceptor;\n\t}\n\n\t@Test\n\tpublic void rpcConsumerAsyncCallSuccess() throws InterruptedException {\n\t\tMethodInfo methodInfo = MethodInfo.builder()\n\t\t\t\t.invoker(asyncConsumerInvocation.getInvoker())\n\t\t\t\t.method(asyncConsumerInvocation.getMethodName())\n\t\t\t\t.args(new Object[]{asyncConsumerInvocation.getInvoker(), asyncConsumerInvocation})\n\t\t\t\t.build();\n\n\t\tContext context = EaseAgent.getContext();\n\t\tRpcContext.getContext().setFuture(futureAdapter);\n\t\talibabaDubboMetricsInterceptor.before(methodInfo, context);\n        methodInfo.setArgs(new Object[]{responseCallback});\n        alibabaDubboAsyncMetricsInterceptor.before(methodInfo,context);\n\n        AlibabaDubboMetricsCallback alibabaDubboMetricsCallback = (AlibabaDubboMetricsCallback) methodInfo.getArgs()[0];\n        Thread thread = new Thread(new Runnable() {\n            @Override\n            public void run() {\n                alibabaDubboMetricsCallback.done(successResult);\n            }\n        });\n        thread.start();\n        thread.join();\n\n\t\tassertSuccessMetrics();\n\t}\n\n\t@Test\n\tpublic void rpcConsumerAsyncCallFailure() throws InterruptedException {\n        MethodInfo methodInfo = MethodInfo.builder()\n            .invoker(asyncConsumerInvocation.getInvoker())\n            .method(asyncConsumerInvocation.getMethodName())\n            .args(new Object[]{asyncConsumerInvocation.getInvoker(), asyncConsumerInvocation})\n            .build();\n\n        Context context = EaseAgent.getContext();\n        RpcContext.getContext().setFuture(futureAdapter);\n        alibabaDubboMetricsInterceptor.before(methodInfo, context);\n        methodInfo.setArgs(new Object[]{responseCallback});\n        alibabaDubboAsyncMetricsInterceptor.before(methodInfo,context);\n\n        AlibabaDubboMetricsCallback alibabaDubboMetricsCallback = (AlibabaDubboMetricsCallback) methodInfo.getArgs()[0];\n        Thread thread = new Thread(new Runnable() {\n            @Override\n            public void run() {\n                alibabaDubboMetricsCallback.caught(failureResult.getException());\n            }\n        });\n        thread.start();\n        thread.join();\n\n        assertFailureMetrics();\n\t}\n\n\t@Test\n\tpublic void rpcConsumerAsyncCallTimeout() throws InterruptedException {\n        MethodInfo methodInfo = MethodInfo.builder()\n            .invoker(asyncConsumerInvocation.getInvoker())\n            .method(asyncConsumerInvocation.getMethodName())\n            .args(new Object[]{asyncConsumerInvocation.getInvoker(), asyncConsumerInvocation})\n            .build();\n\n        Context context = EaseAgent.getContext();\n        RpcContext.getContext().setFuture(futureAdapter);\n        alibabaDubboMetricsInterceptor.before(methodInfo, context);\n        methodInfo.setArgs(new Object[]{responseCallback});\n        alibabaDubboAsyncMetricsInterceptor.before(methodInfo,context);\n\n        AlibabaDubboMetricsCallback alibabaDubboMetricsCallback = (AlibabaDubboMetricsCallback)methodInfo.getArgs()[0];\n        futureAdapter.getFuture().setCallback(alibabaDubboMetricsCallback);\n        TimeUnit.SECONDS.sleep(1);\n\n\t\tassertFailureMetrics();\n\t}\n}\n"
  },
  {
    "path": "plugins/dubbo/src/test/java/com/megaease/easeagent/plugin/dubbo/interceptor/metrics/alibaba/AlibabaDubboMetricsInterceptorTest.java",
    "content": "package com.megaease.easeagent.plugin.dubbo.interceptor.metrics.alibaba;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.ContextUtils;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.dubbo.interceptor.AlibabaDubboBaseTest;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotNull;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class AlibabaDubboMetricsInterceptorTest extends AlibabaDubboBaseTest {\n\n\tprivate final AlibabaDubboMetricsInterceptor alibabaDubboMetricsInterceptor = new AlibabaDubboMetricsInterceptor();\n\n\t@Test\n\tpublic void order() {\n\t\tassertEquals(Order.METRIC.getOrder(), alibabaDubboMetricsInterceptor.order());\n\t}\n\n\t@Test\n\tpublic void getType() {\n\t\tassertEquals(Order.METRIC.getName(), alibabaDubboMetricsInterceptor.getType());\n\t}\n\n\n\t@Test\n\tpublic void rpcConsumerCallSuccess() {\n\t\tMethodInfo methodInfo = MethodInfo.builder()\n\t\t\t\t.invoker(consumerInvoker)\n\t\t\t\t.method(consumerInvocation.getMethodName())\n\t\t\t\t.args(new Object[]{consumerInvoker, consumerInvocation})\n\t\t\t\t.retValue(successResult)\n\t\t\t\t.build();\n\n\t\tContext context = EaseAgent.getContext();\n\t\talibabaDubboMetricsInterceptor.before(methodInfo, context);\n\t\talibabaDubboMetricsInterceptor.after(methodInfo, context);\n\n\t\tassertSuccessMetrics();\n\t}\n\n\t@Test\n\tpublic void rpcConsumerCallFailure() {\n\t\tMethodInfo methodInfo = MethodInfo.builder()\n\t\t\t\t.invoker(consumerInvoker)\n\t\t\t\t.method(consumerInvocation.getMethodName())\n\t\t\t\t.args(new Object[]{consumerInvoker, consumerInvocation})\n\t\t\t\t.retValue(failureResult)\n\t\t\t\t.build();\n\n\t\tContext context = EaseAgent.getContext();\n\t\talibabaDubboMetricsInterceptor.before(methodInfo, context);\n\t\talibabaDubboMetricsInterceptor.after(methodInfo, context);\n\n\t\tassertFailureMetrics();\n\t}\n\n\n    @Test\n    public void rpcConsumerCallException() {\n        MethodInfo methodInfo = MethodInfo.builder()\n            .invoker(consumerInvoker)\n            .method(consumerInvocation.getMethodName())\n            .args(new Object[]{consumerInvoker, consumerInvocation})\n            .throwable(new RuntimeException(\"mock exception\"))\n            .build();\n\n        Context context = EaseAgent.getContext();\n        alibabaDubboMetricsInterceptor.before(methodInfo, context);\n        alibabaDubboMetricsInterceptor.after(methodInfo, context);\n\n\t\tassertFailureMetrics();\n    }\n\n\t@Override\n\tprotected Interceptor createInterceptor() {\n\t\treturn alibabaDubboMetricsInterceptor;\n\t}\n}\n"
  },
  {
    "path": "plugins/dubbo/src/test/java/com/megaease/easeagent/plugin/dubbo/interceptor/metrics/apache/ApacheDubboMetricsInterceptorTest.java",
    "content": "package com.megaease.easeagent.plugin.dubbo.interceptor.metrics.apache;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.ContextUtils;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.dubbo.interceptor.ApacheDubboBaseTest;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotNull;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class ApacheDubboMetricsInterceptorTest extends ApacheDubboBaseTest {\n\n\tprivate static final ApacheDubboMetricsInterceptor apacheDubboMetricsInterceptor = new ApacheDubboMetricsInterceptor();\n\n\t@Override\n\tprotected Interceptor createInterceptor() {\n\t\treturn apacheDubboMetricsInterceptor;\n\t}\n\n\t@Test\n\tpublic void order() {\n\t\tassertEquals(Order.METRIC.getOrder(), apacheDubboMetricsInterceptor.order());\n\t}\n\n\t@Test\n\tpublic void getType() {\n\t\tassertEquals(Order.METRIC.getName(), apacheDubboMetricsInterceptor.getType());\n\t}\n\n\t@Test\n\tpublic void rpcConsumerCallSuccess() {\n\t\tMethodInfo methodInfo = MethodInfo.builder()\n\t\t\t\t.invoker(consumerInvoker)\n\t\t\t\t.method(consumerInvocation.getMethodName())\n\t\t\t\t.args(new Object[]{consumerInvoker, consumerInvocation})\n\t\t\t\t.retValue(successResponse)\n\t\t\t\t.build();\n\n\t\tContext context = EaseAgent.getContext();\n\t\tapacheDubboMetricsInterceptor.before(methodInfo, context);\n\t\tapacheDubboMetricsInterceptor.after(methodInfo, context);\n\n\t\tassertSuccessMetrics();\n\t}\n\n\t@Test\n\tpublic void rpcConsumerCallFailure() {\n\t\tMethodInfo methodInfo = MethodInfo.builder()\n\t\t\t\t.invoker(consumerInvoker)\n\t\t\t\t.method(consumerInvocation.getMethodName())\n\t\t\t\t.args(new Object[]{consumerInvoker, consumerInvocation})\n\t\t\t\t.retValue(failureResponse)\n\t\t\t\t.build();\n\n\t\tContext context = EaseAgent.getContext();\n\t\tapacheDubboMetricsInterceptor.before(methodInfo, context);\n\t\tapacheDubboMetricsInterceptor.after(methodInfo, context);\n\n\t\tassertFailureMetrics();\n\t}\n\n\t@Test\n\tpublic void rpcConsumerCallException() {\n\t\tMethodInfo methodInfo = MethodInfo.builder()\n\t\t\t\t.invoker(consumerInvoker)\n\t\t\t\t.method(consumerInvocation.getMethodName())\n\t\t\t\t.args(new Object[]{consumerInvoker, consumerInvocation})\n\t\t\t\t.throwable(failureResponse.getException())\n\t\t\t\t.build();\n\n\t\tContext context = EaseAgent.getContext();\n\t\tapacheDubboMetricsInterceptor.before(methodInfo, context);\n\t\tapacheDubboMetricsInterceptor.after(methodInfo, context);\n\n\t\tassertFailureMetrics();\n\t}\n\n\n\t@Test\n\tpublic void rpcConsumerAsyncCallSuccess() throws InterruptedException {\n        MethodInfo methodInfo = MethodInfo.builder()\n\t\t\t\t.invoker(consumerInvoker)\n\t\t\t\t.method(consumerInvocation.getMethodName())\n\t\t\t\t.args(new Object[]{consumerInvoker, consumerInvocation})\n\t\t\t\t.retValue(asyncRpcResult)\n\t\t\t\t.build();\n\n\t\tContext context = EaseAgent.getContext();\n\t\tapacheDubboMetricsInterceptor.before(methodInfo, context);\n\t\tapacheDubboMetricsInterceptor.after(methodInfo, context);\n\n        Thread thread = new Thread(new Runnable() {\n            @Override\n            public void run() {\n                asyncRpcResult.complete(successResponse);\n            }\n        });\n        thread.start();\n        thread.join();\n\n        assertSuccessMetrics();\n\t}\n\n\t@Test\n\tpublic void rpcConsumerAsyncCallFailure() throws InterruptedException {\n\t\tMethodInfo methodInfo = MethodInfo.builder()\n\t\t\t\t.invoker(consumerInvoker)\n\t\t\t\t.method(consumerInvocation.getMethodName())\n\t\t\t\t.args(new Object[]{consumerInvoker, consumerInvocation})\n\t\t\t\t.retValue(asyncRpcResult)\n\t\t\t\t.build();\n\n\t\tContext context = EaseAgent.getContext();\n\t\tapacheDubboMetricsInterceptor.before(methodInfo, context);\n\t\tapacheDubboMetricsInterceptor.after(methodInfo, context);\n\n        Thread thread = new Thread(new Runnable() {\n            @Override\n            public void run() {\n                asyncRpcResult.complete(failureResponse);\n            }\n        });\n        thread.start();\n        thread.join();\n\n\t\tassertFailureMetrics();\n\t}\n}\n"
  },
  {
    "path": "plugins/dubbo/src/test/java/com/megaease/easeagent/plugin/dubbo/interceptor/trace/alibaba/AlibabaDubboAsyncTraceInterceptorTest.java",
    "content": "package com.megaease.easeagent.plugin.dubbo.interceptor.trace.alibaba;\n\nimport com.alibaba.dubbo.rpc.RpcContext;\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.mock.plugin.api.utils.InterceptorTestUtils;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.dubbo.DubboPlugin;\nimport com.megaease.easeagent.plugin.dubbo.interceptor.AlibabaDubboBaseTest;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport java.util.concurrent.TimeUnit;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class AlibabaDubboAsyncTraceInterceptorTest extends AlibabaDubboBaseTest {\n\n    private static final AlibabaDubboAsyncTraceInterceptor alibabaDubboAsyncTraceInterceptor = new AlibabaDubboAsyncTraceInterceptor();\n    private static final AlibabaDubboTraceInterceptor alibabaDubboTraceInterceptor = new AlibabaDubboTraceInterceptor();\n\n\n\t@Override\n\tprotected Interceptor createInterceptor() {\n\t\treturn alibabaDubboAsyncTraceInterceptor;\n\t}\n\n\t@Test\n\tpublic void testExternalConfig() {\n        assertNotNull(AlibabaDubboTraceInterceptor.DUBBO_TRACE_CONFIG);\n        assertTrue(AlibabaDubboTraceInterceptor.DUBBO_TRACE_CONFIG.resultCollectEnabled());\n        assertTrue(AlibabaDubboTraceInterceptor.DUBBO_TRACE_CONFIG.argsCollectEnabled());\n\t}\n\n\t@Test\n\tpublic void rpcConsumerAsyncCallSuccess() throws InterruptedException {\n\t\tMethodInfo methodInfo = MethodInfo.builder()\n\t\t\t\t.args(new Object[]{asyncConsumerInvocation.getInvoker(), asyncConsumerInvocation})\n\t\t\t\t.build();\n\n\t\tContext context = EaseAgent.getContext();\n        RpcContext.getContext().setFuture(futureAdapter);\n        alibabaDubboTraceInterceptor.before(methodInfo,context);\n        methodInfo.setArgs(new Object[]{responseCallback});\n\t\talibabaDubboAsyncTraceInterceptor.before(methodInfo, context);\n        AlibabaDubboTraceCallback alibabaDubboTraceCallback = (AlibabaDubboTraceCallback) methodInfo.getArgs()[0];\n\n        Thread thread = new Thread(new Runnable() {\n            @Override\n            public void run() {\n                alibabaDubboTraceCallback.done(successResult);\n            }\n        });\n        thread.start();\n        thread.join();\n        this.assertConsumerTrace(successResult.getValue(), null);\n\t}\n\n\t@Test\n\tpublic void rpcConsumerAsyncCallFail() throws InterruptedException {\n        MethodInfo methodInfo = MethodInfo.builder()\n            .args(new Object[]{asyncConsumerInvocation.getInvoker(), asyncConsumerInvocation})\n            .build();\n\n        Context context = EaseAgent.getContext();\n        RpcContext.getContext().setFuture(futureAdapter);\n        alibabaDubboTraceInterceptor.before(methodInfo,context);\n        methodInfo.setArgs(new Object[]{responseCallback});\n        alibabaDubboAsyncTraceInterceptor.before(methodInfo, context);\n        AlibabaDubboTraceCallback alibabaDubboTraceCallback = (AlibabaDubboTraceCallback) methodInfo.getArgs()[0];\n\n        Thread thread = new Thread(new Runnable() {\n            @Override\n            public void run() {\n                alibabaDubboTraceCallback.caught(failureResult.getException());\n            }\n        });\n        thread.start();\n        thread.join();\n        this.assertConsumerTrace(null,failureResult.getException().getMessage());\n\t}\n\n\n    @Test\n    public void rpcConsumerAsyncCallTimeout() throws InterruptedException {\n        MethodInfo methodInfo = MethodInfo.builder()\n            .args(new Object[]{asyncConsumerInvocation.getInvoker(), asyncConsumerInvocation})\n            .build();\n\n        Context context = EaseAgent.getContext();\n        RpcContext.getContext().setFuture(futureAdapter);\n        alibabaDubboTraceInterceptor.before(methodInfo,context);\n        methodInfo.setArgs(new Object[]{responseCallback});\n        alibabaDubboAsyncTraceInterceptor.before(methodInfo, context);\n        AlibabaDubboTraceCallback alibabaDubboTraceCallback = (AlibabaDubboTraceCallback)methodInfo.getArgs()[0];\n        futureAdapter.getFuture().setCallback(alibabaDubboTraceCallback);\n        TimeUnit.SECONDS.sleep(1);\n        this.assertConsumerTrace(null,responseCallback.throwable().getMessage());\n    }\n\n\n\t@Test\n\tpublic void order() {\n\t\tassertEquals(Order.TRACING.getOrder(), alibabaDubboAsyncTraceInterceptor.order());\n\t}\n\n\t@Test\n\tpublic void getType() {\n\t\tassertEquals(Order.TRACING.getName(), alibabaDubboAsyncTraceInterceptor.getType());\n\t}\n}\n"
  },
  {
    "path": "plugins/dubbo/src/test/java/com/megaease/easeagent/plugin/dubbo/interceptor/trace/alibaba/AlibabaDubboTraceInterceptorTest.java",
    "content": "package com.megaease.easeagent.plugin.dubbo.interceptor.trace.alibaba;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.dubbo.interceptor.AlibabaDubboBaseTest;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class AlibabaDubboTraceInterceptorTest extends AlibabaDubboBaseTest {\n\n\tprivate static final AlibabaDubboTraceInterceptor alibabaDubboTraceInterceptor = new AlibabaDubboTraceInterceptor();\n\n\t@Override\n\tprotected Interceptor createInterceptor() {\n\t\treturn alibabaDubboTraceInterceptor;\n\t}\n\n\t@Test\n\tpublic void testExternalConfig() {\n        assertNotNull(AlibabaDubboTraceInterceptor.DUBBO_TRACE_CONFIG);\n        assertTrue(AlibabaDubboTraceInterceptor.DUBBO_TRACE_CONFIG.resultCollectEnabled());\n        assertTrue(AlibabaDubboTraceInterceptor.DUBBO_TRACE_CONFIG.argsCollectEnabled());\n\t\tassertNotNull(AgentFieldReflectAccessor.getStaticFieldValue(AlibabaDubboTraceInterceptor.class, \"DUBBO_TRACE_CONFIG\"));\n\t}\n\n\t@Test\n\tpublic void rpcConsumerCallSuccess() {\n\t\tMethodInfo methodInfo = MethodInfo.builder()\n\t\t\t\t.args(new Object[]{consumerInvoker, consumerInvocation})\n\t\t\t\t.retValue(this.successResult)\n\t\t\t\t.build();\n\n\t\tContext context = EaseAgent.getContext();\n\t\talibabaDubboTraceInterceptor.before(methodInfo, context);\n\t\talibabaDubboTraceInterceptor.after(methodInfo, context);\n\t\tthis.assertConsumerTrace(successResult.getValue(), null);\n\t}\n\n    @Test\n    public void rpcConsumerCallFailure() {\n        MethodInfo methodInfo = MethodInfo.builder()\n            .args(new Object[]{consumerInvoker, consumerInvocation})\n            .retValue(this.failureResult)\n            .build();\n\n        Context context = EaseAgent.getContext();\n        alibabaDubboTraceInterceptor.before(methodInfo, context);\n        alibabaDubboTraceInterceptor.after(methodInfo, context);\n        this.assertConsumerTrace(null, failureResult.getException().getMessage());\n    }\n\n    @Test\n    public void rpcConsumerCallException() {\n        MethodInfo methodInfo = MethodInfo.builder()\n            .args(new Object[]{consumerInvoker, consumerInvocation})\n            .throwable(new RuntimeException(\"mock exception\"))\n            .build();\n\n        Context context = EaseAgent.getContext();\n        alibabaDubboTraceInterceptor.before(methodInfo, context);\n        alibabaDubboTraceInterceptor.after(methodInfo, context);\n        this.assertConsumerTrace(null, methodInfo.getThrowable().getMessage());\n    }\n\n\t@Test\n\tpublic void rpcProviderCallSuccess() {\n\t\tMethodInfo methodInfo = MethodInfo.builder()\n\t\t\t\t.args(new Object[]{providerInvoker, providerInvocation})\n\t\t\t\t.retValue(this.successResult)\n\t\t\t\t.build();\n\n\t\tContext context = EaseAgent.getContext();\n\t\talibabaDubboTraceInterceptor.before(methodInfo, context);\n\t\talibabaDubboTraceInterceptor.after(methodInfo, context);\n\t\tthis.assertProviderTrace(successResult.getValue(), null);\n\t}\n\n    @Test\n    public void rpcProviderCallFailure() {\n        MethodInfo methodInfo = MethodInfo.builder()\n            .args(new Object[]{providerInvoker, providerInvocation})\n            .retValue(this.failureResult)\n            .build();\n\n        Context context = EaseAgent.getContext();\n        alibabaDubboTraceInterceptor.before(methodInfo, context);\n        alibabaDubboTraceInterceptor.after(methodInfo, context);\n        this.assertProviderTrace(null, failureResult.getException().getMessage());\n    }\n\n    @Test\n    public void rpcProviderCallException() {\n        MethodInfo methodInfo = MethodInfo.builder()\n            .args(new Object[]{providerInvoker, providerInvocation})\n            .throwable(new RuntimeException(\"mock exception\"))\n            .build();\n\n\n        Context context = EaseAgent.getContext();\n        alibabaDubboTraceInterceptor.before(methodInfo, context);\n        alibabaDubboTraceInterceptor.after(methodInfo, context);\n        this.assertProviderTrace(null, methodInfo.getThrowable().getMessage());\n    }\n\n\t@Test\n\tpublic void order() {\n\t\tassertEquals(Order.TRACING.getOrder(), alibabaDubboTraceInterceptor.order());\n\t}\n\n\t@Test\n\tpublic void getType() {\n\t\tassertEquals(Order.TRACING.getName(), alibabaDubboTraceInterceptor.getType());\n\t}\n}\n"
  },
  {
    "path": "plugins/dubbo/src/test/java/com/megaease/easeagent/plugin/dubbo/interceptor/trace/apache/ApacheDubboTraceInterceptorTest.java",
    "content": "package com.megaease.easeagent.plugin.dubbo.interceptor.trace.apache;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.dubbo.interceptor.ApacheDubboBaseTest;\nimport com.megaease.easeagent.plugin.dubbo.interceptor.trace.alibaba.AlibabaDubboTraceInterceptor;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport org.apache.dubbo.rpc.Result;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class ApacheDubboTraceInterceptorTest extends ApacheDubboBaseTest {\n\n\tprivate static final ApacheDubboTraceInterceptor apacheDubboTraceInterceptor = new ApacheDubboTraceInterceptor();\n\n\t@Override\n\tpublic Interceptor createInterceptor() {\n\t\treturn apacheDubboTraceInterceptor;\n\t}\n\n\t@Test\n\tpublic void testExternalConfig() {\n        assertNotNull(AlibabaDubboTraceInterceptor.DUBBO_TRACE_CONFIG);\n        assertTrue(AlibabaDubboTraceInterceptor.DUBBO_TRACE_CONFIG.resultCollectEnabled());\n        assertTrue(AlibabaDubboTraceInterceptor.DUBBO_TRACE_CONFIG.argsCollectEnabled());\n\t}\n\n\t@Test\n\tpublic void rpcConsumerCallSuccess() {\n\t\tMethodInfo methodInfo = MethodInfo.builder()\n\t\t\t\t.args(new Object[]{consumerInvoker, consumerInvocation})\n\t\t\t\t.retValue(this.successResponse)\n\t\t\t\t.build();\n\n\t\tContext context = EaseAgent.getContext();\n\t\tapacheDubboTraceInterceptor.before(methodInfo, context);\n\t\tapacheDubboTraceInterceptor.after(methodInfo, context);\n\t\tResult retValue = (Result) methodInfo.getRetValue();\n\t\tthis.assertConsumerTrace(retValue.getValue(), null);\n\t}\n\n    @Test\n    public void rpcConsumerCallFailure() {\n        MethodInfo methodInfo = MethodInfo.builder()\n            .args(new Object[]{consumerInvoker, consumerInvocation})\n            .retValue(this.failureResponse)\n            .build();\n\n        Context context = EaseAgent.getContext();\n        apacheDubboTraceInterceptor.before(methodInfo, context);\n        apacheDubboTraceInterceptor.after(methodInfo, context);\n        Result retValue = (Result) methodInfo.getRetValue();\n        this.assertConsumerTrace(null, failureResponse.getException().getMessage());\n    }\n\n    @Test\n    public void rpcConsumerCallException() {\n        MethodInfo methodInfo = MethodInfo.builder()\n            .args(new Object[]{consumerInvoker, consumerInvocation})\n            .throwable(new RuntimeException(\"mock exception\"))\n            .build();\n\n        Context context = EaseAgent.getContext();\n        apacheDubboTraceInterceptor.before(methodInfo, context);\n        apacheDubboTraceInterceptor.after(methodInfo, context);\n        this.assertConsumerTrace(null, methodInfo.getThrowable().getMessage());\n    }\n\n\t@Test\n\tpublic void rpcProviderCallSuccess() {\n\t\tMethodInfo methodInfo = MethodInfo.builder()\n\t\t\t\t.args(new Object[]{providerInvoker, providerInvocation})\n\t\t\t\t.retValue(this.successResponse)\n\t\t\t\t.build();\n\n\t\tContext context = EaseAgent.getContext();\n\t\tapacheDubboTraceInterceptor.before(methodInfo, context);\n\t\tapacheDubboTraceInterceptor.after(methodInfo, context);\n\t\tResult retValue = (Result) methodInfo.getRetValue();\n\t\tthis.assertProviderTrace(retValue.getValue(), null);\n\t}\n\n    @Test\n    public void rpcProviderCallFailure() {\n        MethodInfo methodInfo = MethodInfo.builder()\n            .args(new Object[]{providerInvoker, providerInvocation})\n            .retValue(this.failureResponse)\n            .build();\n\n        Context context = EaseAgent.getContext();\n        apacheDubboTraceInterceptor.before(methodInfo, context);\n        apacheDubboTraceInterceptor.after(methodInfo, context);\n        this.assertProviderTrace(null, failureResponse.getException().getMessage());\n    }\n\n    @Test\n    public void rpcProviderCallException() {\n        MethodInfo methodInfo = MethodInfo.builder()\n            .args(new Object[]{providerInvoker, providerInvocation})\n            .throwable(new RuntimeException(\"mock exception\"))\n            .build();\n\n        Context context = EaseAgent.getContext();\n        apacheDubboTraceInterceptor.before(methodInfo, context);\n        apacheDubboTraceInterceptor.after(methodInfo, context);\n        this.assertProviderTrace(null, methodInfo.getThrowable().getMessage());\n    }\n\n\t@Test\n\tpublic void rpcConsumerAsyncCallSuccess() throws InterruptedException {\n\t\tMethodInfo methodInfo = MethodInfo.builder()\n\t\t\t\t.args(new Object[]{consumerInvoker, consumerInvocation})\n\t\t\t\t.retValue(this.asyncRpcResult)\n\t\t\t\t.build();\n\n\t\tContext context = EaseAgent.getContext();\n\t\tapacheDubboTraceInterceptor.before(methodInfo, context);\n\t\tapacheDubboTraceInterceptor.after(methodInfo, context);\n\n        Thread thread = new Thread(new Runnable() {\n            @Override\n            public void run() {\n                asyncRpcResult.complete(successResponse);\n            }\n        });\n        thread.start();\n        thread.join();\n\n\t\tResult retValue = (Result) methodInfo.getRetValue();\n\t\tthis.assertConsumerTrace(retValue.getValue(), null);\n\t}\n    @Test\n\tpublic void rpcConsumerAsyncCallFail() throws InterruptedException {\n\t\tMethodInfo methodInfo = MethodInfo.builder()\n\t\t\t\t.args(new Object[]{consumerInvoker, consumerInvocation})\n\t\t\t\t.retValue(this.asyncRpcResult)\n\t\t\t\t.build();\n\n\t\tContext context = EaseAgent.getContext();\n\t\tapacheDubboTraceInterceptor.before(methodInfo, context);\n\t\tapacheDubboTraceInterceptor.after(methodInfo, context);\n\n        Thread thread = new Thread(new Runnable() {\n            @Override\n            public void run() {\n                asyncRpcResult.complete(failureResponse);\n            }\n        });\n        thread.start();\n        thread.join();\n\n\t\tResult retValue = (Result) methodInfo.getRetValue();\n\t\tthis.assertConsumerTrace(null, retValue.getException().getMessage());\n\t}\n\n\t@Test\n\tpublic void order() {\n\t\tassertEquals(Order.TRACING.getOrder(), apacheDubboTraceInterceptor.order());\n\t}\n\n\t@Test\n\tpublic void getType() {\n\t\tassertEquals(Order.TRACING.getName(), apacheDubboTraceInterceptor.getType());\n\t}\n}\n"
  },
  {
    "path": "plugins/dubbo/src/test/resources/mock_agent.properties",
    "content": "## dubbo arguments collect switch\nplugin.observability.dubbo.tracing.args.collect.enabled=true\nplugin.observability.dubbo.tracing.result.collect.enabled=true\n"
  },
  {
    "path": "plugins/elasticsearch/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright (c) 2021, MegaEase\n  ~ All rights reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~   http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>plugins</artifactId>\n        <groupId>com.megaease.easeagent</groupId>\n        <version>2.3.0</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>elasticsearch</artifactId>\n\n\n    <dependencies>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>plugin-api</artifactId>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.elasticsearch.client</groupId>\n            <artifactId>elasticsearch-rest-client</artifactId>\n            <version>7.6.2</version>\n            <scope>provided</scope>\n        </dependency>\n\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>plugin-api-mock</artifactId>\n            <version>${project.version}</version>\n            <scope>test</scope>\n        </dependency>\n    </dependencies>\n</project>\n"
  },
  {
    "path": "plugins/elasticsearch/src/main/java/com/megaease/easeagent/plugin/elasticsearch/ElasticsearchPlugin.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.elasticsearch;\n\nimport com.megaease.easeagent.plugin.AgentPlugin;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\n\npublic class ElasticsearchPlugin implements AgentPlugin {\n    @Override\n    public String getNamespace() {\n        return ConfigConst.Namespace.ELASTICSEARCH;\n    }\n\n    @Override\n    public String getDomain() {\n        return ConfigConst.OBSERVABILITY;\n    }\n}\n"
  },
  {
    "path": "plugins/elasticsearch/src/main/java/com/megaease/easeagent/plugin/elasticsearch/ElasticsearchRedirectPlugin.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.elasticsearch;\n\nimport com.megaease.easeagent.plugin.AgentPlugin;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.enums.Order;\n\npublic class ElasticsearchRedirectPlugin implements AgentPlugin {\n    @Override\n    public String getNamespace() {\n        return ConfigConst.Namespace.ELASTICSEARCH;\n    }\n\n    @Override\n    public String getDomain() {\n        return ConfigConst.INTEGRABILITY;\n    }\n\n    @Override\n    public int order() {\n        return Order.REDIRECT.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/elasticsearch/src/main/java/com/megaease/easeagent/plugin/elasticsearch/advice/SpringElasticsearchAdvice.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.elasticsearch.advice;\n\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.Set;\n\nimport static com.megaease.easeagent.plugin.tools.matcher.ClassMatcherUtils.hasSuperType;\nimport static com.megaease.easeagent.plugin.tools.matcher.ClassMatcherUtils.name;\n\npublic class SpringElasticsearchAdvice implements Points {\n    //return def.type(\n    //                hasSuperType(named(\"org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientProperties\"))\n    //                    .or(hasSuperType(named(\"org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchRestClientProperties\")))\n    //                    .or(named(\"org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientProperties\"))\n    //                    .or(named(\"org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchRestClientProperties\"))\n    //            )\n    //            .transform(setProperty(nameStartsWith(\"set\")))\n    //            .end();\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return name(\"org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientProperties\")\n            .or(hasSuperType(\"org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchRestClientProperties\"))\n            .or(name(\"org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientProperties\"))\n            .or(name(\"org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchRestClientProperties\"));\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.multiBuilder()\n            .match(\n                MethodMatcher\n                    .builder()\n                    .nameStartWith(\"set\")\n                    .build()\n            )\n            .build();\n    }\n}\n"
  },
  {
    "path": "plugins/elasticsearch/src/main/java/com/megaease/easeagent/plugin/elasticsearch/interceptor/AsyncResponse4MetricsListener.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.elasticsearch.interceptor;\n\nimport com.megaease.easeagent.plugin.api.Cleaner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.AsyncContext;\nimport com.megaease.easeagent.plugin.api.context.ContextUtils;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport org.elasticsearch.client.Request;\nimport org.elasticsearch.client.Response;\nimport org.elasticsearch.client.ResponseListener;\n\nimport static com.megaease.easeagent.plugin.elasticsearch.interceptor.ElasticsearchCtxUtils.REQUEST;\n\npublic class AsyncResponse4MetricsListener implements ResponseListener {\n\n    private final ResponseListener delegate;\n    private final AsyncContext asyncContext;\n    private final ElasticsearchMetric elasticsearchMetric;\n\n\n    public AsyncResponse4MetricsListener(ResponseListener delegate, AsyncContext asyncContext, ElasticsearchMetric elasticsearchMetric) {\n        this.delegate = delegate;\n        this.asyncContext = asyncContext;\n        this.elasticsearchMetric = elasticsearchMetric;\n    }\n\n    @Override\n    public void onSuccess(Response response) {\n        try {\n            this.delegate.onSuccess(response);\n        } finally {\n            this.process(response, null);\n        }\n    }\n\n    @Override\n    public void onFailure(Exception exception) {\n        try {\n            this.delegate.onFailure(exception);\n        } finally {\n            this.process(null, exception);\n        }\n    }\n\n    private void process(Response response, Exception exception) {\n        try (Cleaner ignored = asyncContext.importToCurrent()) {\n            Context context = EaseAgent.getContext();\n            Request request = context.get(REQUEST);\n            long duration = ContextUtils.getDuration(context);\n            boolean success = ElasticsearchCtxUtils.checkSuccess(response, exception);\n            this.elasticsearchMetric.collectMetric(ElasticsearchCtxUtils\n                .getIndex(request.getEndpoint()), duration, success);\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/elasticsearch/src/main/java/com/megaease/easeagent/plugin/elasticsearch/interceptor/AsyncResponse4TraceListener.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.elasticsearch.interceptor;\n\nimport com.megaease.easeagent.plugin.api.Cleaner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.AsyncContext;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport org.elasticsearch.client.Response;\nimport org.elasticsearch.client.ResponseListener;\n\npublic class AsyncResponse4TraceListener implements ResponseListener {\n\n    private final ResponseListener delegate;\n    private final AsyncContext asyncContext;\n\n\n    public AsyncResponse4TraceListener(ResponseListener delegate, AsyncContext asyncContext) {\n        this.delegate = delegate;\n        this.asyncContext = asyncContext;\n    }\n\n    @Override\n    public void onSuccess(Response response) {\n        try {\n            this.delegate.onSuccess(response);\n        } finally {\n            this.process(response, null);\n        }\n    }\n\n    @Override\n    public void onFailure(Exception exception) {\n        try {\n            this.delegate.onFailure(exception);\n        } finally {\n            this.process(null, exception);\n        }\n    }\n\n    private void process(Response response, Exception exception) {\n        try (Cleaner ignored = asyncContext.importToCurrent()) {\n            Context context = EaseAgent.getContext();\n            ElasticsearchCtxUtils.finishSpan(response, exception, context);\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/elasticsearch/src/main/java/com/megaease/easeagent/plugin/elasticsearch/interceptor/ElasticsearchBaseInterceptor.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.elasticsearch.interceptor;\n\nimport com.megaease.easeagent.plugin.api.config.AutoRefreshPluginConfigImpl;\nimport com.megaease.easeagent.plugin.api.config.AutoRefreshPluginConfigRegistry;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\n\npublic abstract class ElasticsearchBaseInterceptor implements Interceptor {\n\n    protected AutoRefreshPluginConfigImpl config;\n\n    @Override\n    public void init(IPluginConfig config, String className, String methodName, String methodDescriptor) {\n        this.config = AutoRefreshPluginConfigRegistry.getOrCreate(ConfigConst.OBSERVABILITY, ConfigConst.Namespace.ELASTICSEARCH, this.getType());\n    }\n\n}\n"
  },
  {
    "path": "plugins/elasticsearch/src/main/java/com/megaease/easeagent/plugin/elasticsearch/interceptor/ElasticsearchBaseMetricsInterceptor.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.elasticsearch.interceptor;\n\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.metric.MetricRegistry;\nimport com.megaease.easeagent.plugin.api.metric.ServiceMetricRegistry;\nimport com.megaease.easeagent.plugin.api.metric.ServiceMetricSupplier;\nimport com.megaease.easeagent.plugin.api.metric.name.NameFactory;\nimport com.megaease.easeagent.plugin.api.metric.name.Tags;\nimport com.megaease.easeagent.plugin.api.middleware.Redirect;\nimport com.megaease.easeagent.plugin.api.middleware.RedirectProcessor;\nimport com.megaease.easeagent.plugin.enums.Order;\n\npublic abstract class ElasticsearchBaseMetricsInterceptor extends ElasticsearchBaseInterceptor {\n\n    protected ElasticsearchMetric elasticsearchMetric;\n\n    @Override\n    public void init(IPluginConfig config, String className, String methodName, String methodDescriptor) {\n        super.init(config, className, methodName, methodDescriptor);\n        Tags tags = new Tags(\"application\", \"elasticsearch\", \"index\");\n        RedirectProcessor.setTagsIfRedirected(Redirect.ELASTICSEARCH, tags);\n        this.elasticsearchMetric = ServiceMetricRegistry.getOrCreate(config, tags, new ServiceMetricSupplier<ElasticsearchMetric>() {\n            @Override\n            public NameFactory newNameFactory() {\n                return ElasticsearchMetric.nameFactory();\n            }\n\n            @Override\n            public ElasticsearchMetric newInstance(MetricRegistry metricRegistry, NameFactory nameFactory) {\n                return new ElasticsearchMetric(metricRegistry, nameFactory);\n            }\n        });\n    }\n\n    @Override\n    public String getType() {\n        return Order.METRIC.getName();\n    }\n\n    @Override\n    public int order() {\n        return Order.METRIC.getOrder();\n    }\n\n    public ElasticsearchMetric getElasticsearchMetric() {\n        return elasticsearchMetric;\n    }\n}\n"
  },
  {
    "path": "plugins/elasticsearch/src/main/java/com/megaease/easeagent/plugin/elasticsearch/interceptor/ElasticsearchBaseTraceInterceptor.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.elasticsearch.interceptor;\n\nimport com.megaease.easeagent.plugin.enums.Order;\n\npublic abstract class ElasticsearchBaseTraceInterceptor extends ElasticsearchBaseInterceptor {\n\n    @Override\n    public String getType() {\n        return Order.TRACING.getName();\n    }\n\n    @Override\n    public int order() {\n        return Order.TRACING.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/elasticsearch/src/main/java/com/megaease/easeagent/plugin/elasticsearch/interceptor/ElasticsearchCtxUtils.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.elasticsearch.interceptor;\n\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.middleware.MiddlewareConstants;\nimport com.megaease.easeagent.plugin.api.middleware.Type;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.utils.common.StringUtils;\nimport lombok.SneakyThrows;\nimport org.apache.http.HttpEntity;\nimport org.apache.http.util.EntityUtils;\nimport org.elasticsearch.client.Request;\nimport org.elasticsearch.client.Response;\n\nimport java.nio.charset.StandardCharsets;\n\npublic class ElasticsearchCtxUtils {\n    private static final String SPAN = ElasticsearchCtxUtils.class.getName() + \"-Span\";\n    public static final String REQUEST = ElasticsearchCtxUtils.class.getName() + \"-Request\";\n\n    @SneakyThrows\n    public static void initSpan(MethodInfo methodInfo, Context context) {\n        Request request = (Request) methodInfo.getArgs()[0];\n        HttpEntity entity = request.getEntity();\n        Span span = context.nextSpan();\n        span.kind(Span.Kind.CLIENT);\n        span.remoteServiceName(\"elasticsearch\");\n        span.tag(MiddlewareConstants.TYPE_TAG_NAME, Type.ELASTICSEARCH.getRemoteType());\n        span.tag(\"es.index\", getIndex(request.getEndpoint()));\n        span.tag(\"es.operation\", request.getMethod() + \" \" + request.getEndpoint());\n        if (entity != null) {\n            String body = EntityUtils.toString(entity, StandardCharsets.UTF_8);\n            span.tag(\"es.body\", body);\n        }\n        span.start();\n        context.put(SPAN, span);\n        context.put(REQUEST, request);\n    }\n\n    public static String getIndex(String endpoint) {\n        if (StringUtils.isEmpty(endpoint)) {\n            return \"\";\n        }\n        String tmp = endpoint;\n        if (!tmp.startsWith(\"/\")) {\n            tmp = \"/\" + tmp;\n        }\n        int end = tmp.indexOf(\"/\", 1);\n        String index;\n        if (end < 0) {\n            index = tmp.substring(1);\n        } else if (end > 0) {\n            index = tmp.substring(1, end);\n        } else {\n            index = tmp.substring(1);\n        }\n        if (index.startsWith(\"_\") || index.startsWith(\"-\") || index.startsWith(\"+\")) {\n            return \"\";\n        }\n        return index;\n    }\n\n    public static boolean checkSuccess(Response response, Throwable throwable) {\n        if (throwable != null) {\n            return false;\n        }\n        if (response == null) {\n            return false;\n        }\n        return response.getStatusLine().getStatusCode() == 200\n            || response.getStatusLine().getStatusCode() == 201;\n    }\n\n    public static void finishSpan(Response response, Throwable throwable, Context context) {\n        Span span = context.get(SPAN);\n        if (span == null) {\n            return;\n        }\n        if (throwable != null) {\n            span.error(throwable);\n            span.tag(\"error\", throwable.getMessage());\n        } else {\n            if (!checkSuccess(response, null)) {\n                if (response != null) {\n                    int statusCode = response.getStatusLine().getStatusCode();\n                    span.tag(\"error\", String.valueOf(statusCode));\n                } else {\n                    span.tag(\"error\", \"unknown\");\n                }\n\n            }\n        }\n        span.finish();\n        context.remove(SPAN);\n    }\n}\n"
  },
  {
    "path": "plugins/elasticsearch/src/main/java/com/megaease/easeagent/plugin/elasticsearch/interceptor/ElasticsearchMetric.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.elasticsearch.interceptor;\n\nimport com.megaease.easeagent.plugin.api.metric.Counter;\nimport com.megaease.easeagent.plugin.api.metric.Meter;\nimport com.megaease.easeagent.plugin.api.metric.MetricRegistry;\nimport com.megaease.easeagent.plugin.api.metric.ServiceMetric;\nimport com.megaease.easeagent.plugin.api.metric.name.*;\nimport com.megaease.easeagent.plugin.tools.metrics.LastMinutesCounterGauge;\nimport com.megaease.easeagent.plugin.utils.ImmutableMap;\n\nimport javax.annotation.Nonnull;\nimport java.util.HashMap;\nimport java.util.concurrent.TimeUnit;\n\npublic class ElasticsearchMetric extends ServiceMetric {\n    public ElasticsearchMetric(@Nonnull MetricRegistry metricRegistry, @Nonnull NameFactory nameFactory) {\n        super(metricRegistry, nameFactory);\n    }\n\n    public static NameFactory nameFactory() {\n        return NameFactory.createBuilder()\n            .timerType(MetricSubType.DEFAULT,\n                ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                    .put(MetricField.MIN_EXECUTION_TIME, MetricValueFetcher.SnapshotMinValue)\n                    .put(MetricField.MAX_EXECUTION_TIME, MetricValueFetcher.SnapshotMaxValue)\n                    .put(MetricField.MEAN_EXECUTION_TIME, MetricValueFetcher.SnapshotMeanValue)\n                    .put(MetricField.P25_EXECUTION_TIME, MetricValueFetcher.Snapshot25Percentile)\n                    .put(MetricField.P50_EXECUTION_TIME, MetricValueFetcher.Snapshot50PercentileValue)\n                    .put(MetricField.P75_EXECUTION_TIME, MetricValueFetcher.Snapshot75PercentileValue)\n                    .put(MetricField.P95_EXECUTION_TIME, MetricValueFetcher.Snapshot95PercentileValue)\n                    .put(MetricField.P98_EXECUTION_TIME, MetricValueFetcher.Snapshot98PercentileValue)\n                    .put(MetricField.P99_EXECUTION_TIME, MetricValueFetcher.Snapshot99PercentileValue)\n                    .put(MetricField.P999_EXECUTION_TIME, MetricValueFetcher.Snapshot999PercentileValue)\n                    .build())\n            .gaugeType(MetricSubType.DEFAULT, new HashMap<>())\n            .meterType(MetricSubType.DEFAULT,\n                ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                    .put(MetricField.M1_RATE, MetricValueFetcher.MeteredM1Rate)\n                    .put(MetricField.M5_RATE, MetricValueFetcher.MeteredM5Rate)\n                    .put(MetricField.M15_RATE, MetricValueFetcher.MeteredM15Rate)\n                    .put(MetricField.MEAN_RATE, MetricValueFetcher.MeteredMeanRate)\n                    .build())\n            .meterType(MetricSubType.ERROR,\n                ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                    .put(MetricField.M1_ERROR_RATE, MetricValueFetcher.MeteredM1Rate)\n                    .put(MetricField.M5_ERROR_RATE, MetricValueFetcher.MeteredM5Rate)\n                    .put(MetricField.M15_ERROR_RATE, MetricValueFetcher.MeteredM15Rate)\n                    .build())\n            .counterType(MetricSubType.ERROR, ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                .put(MetricField.EXECUTION_ERROR_COUNT, MetricValueFetcher.CountingCount)\n                .build())\n            .counterType(MetricSubType.DEFAULT, ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                .put(MetricField.EXECUTION_COUNT, MetricValueFetcher.CountingCount)\n                .build())\n            .build();\n    }\n\n    public void collectMetric(String key, long duration, boolean success) {\n        metricRegistry.timer(this.nameFactory.timerName(key, MetricSubType.DEFAULT)).update(duration, TimeUnit.MILLISECONDS);\n        final Meter defaultMeter = metricRegistry.meter(nameFactory.meterName(key, MetricSubType.DEFAULT));\n        final Counter defaultCounter = metricRegistry.counter(nameFactory.counterName(key, MetricSubType.DEFAULT));\n\n        if (!success) {\n            final Meter errorMeter = metricRegistry.meter(nameFactory.meterName(key, MetricSubType.ERROR));\n            final Counter errorCounter = metricRegistry.counter(nameFactory.counterName(key, MetricSubType.ERROR));\n            errorMeter.mark();\n            errorCounter.inc();\n        }\n        defaultMeter.mark();\n        defaultCounter.inc();\n\n        MetricName gaugeName = nameFactory.gaugeNames(key).get(MetricSubType.DEFAULT);\n        metricRegistry.gauge(gaugeName.name(), () -> () ->\n            LastMinutesCounterGauge.builder()\n                .m1Count((long) (defaultMeter.getOneMinuteRate() * 60))\n                .m5Count((long) (defaultMeter.getFiveMinuteRate() * 60 * 5))\n                .m15Count((long) (defaultMeter.getFifteenMinuteRate() * 60 * 15))\n                .build());\n    }\n}\n"
  },
  {
    "path": "plugins/elasticsearch/src/main/java/com/megaease/easeagent/plugin/elasticsearch/interceptor/ElasticsearchPerformRequestAsync4MetricsInterceptor.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.elasticsearch.interceptor;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.AsyncContext;\nimport com.megaease.easeagent.plugin.elasticsearch.ElasticsearchPlugin;\nimport com.megaease.easeagent.plugin.elasticsearch.points.ElasticsearchPerformRequestAsyncPoints;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport org.elasticsearch.client.Request;\nimport org.elasticsearch.client.ResponseListener;\n\n@AdviceTo(value = ElasticsearchPerformRequestAsyncPoints.class, plugin = ElasticsearchPlugin.class)\npublic class ElasticsearchPerformRequestAsync4MetricsInterceptor extends ElasticsearchBaseMetricsInterceptor {\n\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n        Request request = (Request) methodInfo.getArgs()[0];\n        context.put(ElasticsearchCtxUtils.REQUEST, request);\n        AsyncContext asyncContext = context.exportAsync();\n        ResponseListener listener = (ResponseListener) methodInfo.getArgs()[1];\n        ResponseListener asyncResponseListener = new AsyncResponse4MetricsListener(listener, asyncContext, elasticsearchMetric);\n        methodInfo.changeArg(1, asyncResponseListener);\n    }\n}\n"
  },
  {
    "path": "plugins/elasticsearch/src/main/java/com/megaease/easeagent/plugin/elasticsearch/interceptor/ElasticsearchPerformRequestAsync4TraceInterceptor.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.elasticsearch.interceptor;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.AsyncContext;\nimport com.megaease.easeagent.plugin.elasticsearch.ElasticsearchPlugin;\nimport com.megaease.easeagent.plugin.elasticsearch.points.ElasticsearchPerformRequestAsyncPoints;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport org.elasticsearch.client.ResponseListener;\n\n@AdviceTo(value = ElasticsearchPerformRequestAsyncPoints.class, plugin = ElasticsearchPlugin.class)\npublic class ElasticsearchPerformRequestAsync4TraceInterceptor extends ElasticsearchBaseTraceInterceptor {\n\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n        ElasticsearchCtxUtils.initSpan(methodInfo, context);\n        AsyncContext asyncContext = context.exportAsync();\n        ResponseListener listener = (ResponseListener) methodInfo.getArgs()[1];\n        ResponseListener asyncResponseListener = new AsyncResponse4TraceListener(listener, asyncContext);\n        methodInfo.changeArg(1, asyncResponseListener);\n    }\n\n}\n"
  },
  {
    "path": "plugins/elasticsearch/src/main/java/com/megaease/easeagent/plugin/elasticsearch/interceptor/ElasticsearchPerformRequestMetricsInterceptor.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.elasticsearch.interceptor;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.ContextUtils;\nimport com.megaease.easeagent.plugin.elasticsearch.ElasticsearchPlugin;\nimport com.megaease.easeagent.plugin.elasticsearch.points.ElasticsearchPerformRequestPoints;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport org.elasticsearch.client.Request;\nimport org.elasticsearch.client.Response;\n\nimport static com.megaease.easeagent.plugin.elasticsearch.interceptor.ElasticsearchCtxUtils.REQUEST;\n\n@AdviceTo(value = ElasticsearchPerformRequestPoints.class, plugin = ElasticsearchPlugin.class)\npublic class ElasticsearchPerformRequestMetricsInterceptor extends ElasticsearchBaseMetricsInterceptor {\n\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n        Request request = (Request) methodInfo.getArgs()[0];\n        context.put(REQUEST, request);\n    }\n\n    @Override\n    public void after(MethodInfo methodInfo, Context context) {\n        Response response = (Response) methodInfo.getRetValue();\n        Request request = (Request) methodInfo.getArgs()[0];\n        boolean success = ElasticsearchCtxUtils.checkSuccess(response, methodInfo.getThrowable());\n        this.elasticsearchMetric.collectMetric(ElasticsearchCtxUtils.getIndex(request.getEndpoint()),\n            ContextUtils.getDuration(context), success);\n    }\n}\n"
  },
  {
    "path": "plugins/elasticsearch/src/main/java/com/megaease/easeagent/plugin/elasticsearch/interceptor/ElasticsearchPerformRequestTraceInterceptor.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.elasticsearch.interceptor;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.elasticsearch.ElasticsearchPlugin;\nimport com.megaease.easeagent.plugin.elasticsearch.points.ElasticsearchPerformRequestPoints;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport org.elasticsearch.client.Response;\n\n@AdviceTo(value = ElasticsearchPerformRequestPoints.class, plugin = ElasticsearchPlugin.class)\npublic class ElasticsearchPerformRequestTraceInterceptor extends ElasticsearchBaseTraceInterceptor {\n\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n        ElasticsearchCtxUtils.initSpan(methodInfo, context);\n    }\n\n    @Override\n    public void after(MethodInfo methodInfo, Context context) {\n        Response response = (Response) methodInfo.getRetValue();\n        ElasticsearchCtxUtils.finishSpan(response, methodInfo.getThrowable(), context);\n    }\n\n}\n"
  },
  {
    "path": "plugins/elasticsearch/src/main/java/com/megaease/easeagent/plugin/elasticsearch/interceptor/redirect/SpringElasticsearchInterceptor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.elasticsearch.interceptor.redirect;\n\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.logging.Logger;\nimport com.megaease.easeagent.plugin.api.middleware.Redirect;\nimport com.megaease.easeagent.plugin.api.middleware.RedirectProcessor;\nimport com.megaease.easeagent.plugin.api.middleware.ResourceConfig;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.elasticsearch.ElasticsearchRedirectPlugin;\nimport com.megaease.easeagent.plugin.elasticsearch.advice.SpringElasticsearchAdvice;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.NonReentrantInterceptor;\nimport com.megaease.easeagent.plugin.utils.common.StringUtils;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n@AdviceTo(value = SpringElasticsearchAdvice.class, plugin = ElasticsearchRedirectPlugin.class)\npublic class SpringElasticsearchInterceptor implements NonReentrantInterceptor {\n    private static final Logger LOGGER = EaseAgent.getLogger(SpringElasticsearchInterceptor.class);\n\n    @Override\n    public void doBefore(MethodInfo methodInfo, Context context) {\n        ResourceConfig cnf = Redirect.ELASTICSEARCH.getConfig();\n        if (cnf == null) {\n            return;\n        }\n        String method = methodInfo.getMethod();\n        List<String> uris = this.formatUris(cnf.getUriList());\n        if (method.equals(\"setUsername\") && StringUtils.isNotEmpty(cnf.getUserName())) {\n            LOGGER.info(\"Redirect Elasticsearch Username: {} to {}\", methodInfo.getArgs()[0], cnf.getUserName());\n            methodInfo.changeArg(0, cnf.getUserName());\n        } else if (method.equals(\"setPassword\") && StringUtils.isNotEmpty(cnf.getPassword())) {\n            LOGGER.info(\"Redirect Elasticsearch Password: *** to ***\");\n            methodInfo.changeArg(0, cnf.getPassword());\n        } else if (method.equals(\"setEndpoints\") || method.equals(\"setUris\")) {\n            LOGGER.info(\"Redirect Elasticsearch uris: {} to {}\", methodInfo.getArgs()[0], cnf.getUris());\n            methodInfo.changeArg(0, uris);\n            RedirectProcessor.redirected(Redirect.ELASTICSEARCH, cnf.getUris());\n        }\n\n    }\n\n    private List<String> formatUris(List<String> uriList) {\n        List<String> list = new ArrayList<>();\n        for (String uri : uriList) {\n            if (uri.startsWith(\"http://\") || uri.startsWith(\"https://\")) {\n                list.add(uri);\n            } else {\n                list.add(\"http://\" + uri);\n            }\n        }\n        return list;\n    }\n\n    @Override\n    public String getType() {\n        return Order.REDIRECT.getName();\n    }\n\n    @Override\n    public int order() {\n        return Order.REDIRECT.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/elasticsearch/src/main/java/com/megaease/easeagent/plugin/elasticsearch/points/ElasticsearchPerformRequestAsyncPoints.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.elasticsearch.points;\n\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.Set;\n\npublic class ElasticsearchPerformRequestAsyncPoints implements Points {\n\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return ClassMatcher.builder()\n            .hasClassName(\"org.elasticsearch.client.RestClient\")\n            .build();\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.builder()\n            .named(\"performRequestAsync\")\n            .isPublic()\n            .arg(0, \"org.elasticsearch.client.Request\")\n            .arg(1, \"org.elasticsearch.client.ResponseListener\")\n            .build().toSet();\n    }\n}\n"
  },
  {
    "path": "plugins/elasticsearch/src/main/java/com/megaease/easeagent/plugin/elasticsearch/points/ElasticsearchPerformRequestPoints.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.elasticsearch.points;\n\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.Set;\n\npublic class ElasticsearchPerformRequestPoints implements Points {\n\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return ClassMatcher.builder()\n            .hasClassName(\"org.elasticsearch.client.RestClient\")\n            .build();\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.builder()\n            .named(\"performRequest\")\n            .isPublic()\n            .arg(0, \"org.elasticsearch.client.Request\")\n            .build().toSet();\n    }\n}\n"
  },
  {
    "path": "plugins/elasticsearch/src/test/java/com/megaease/easeagent/plugin/elasticsearch/interceptor/ElasticsearchBaseTest.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.elasticsearch.interceptor;\n\nimport com.megaease.easeagent.mock.plugin.api.MockEaseAgent;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.context.ContextUtils;\nimport com.megaease.easeagent.plugin.api.metric.Metric;\nimport com.megaease.easeagent.plugin.api.metric.MetricRegistry;\nimport com.megaease.easeagent.plugin.api.metric.name.MetricSubType;\nimport com.megaease.easeagent.plugin.api.metric.name.NameFactory;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport org.apache.http.HttpEntity;\nimport org.apache.http.StatusLine;\nimport org.apache.http.entity.ByteArrayEntity;\nimport org.apache.http.entity.ContentType;\nimport org.elasticsearch.client.Request;\nimport org.elasticsearch.client.Response;\nimport org.elasticsearch.client.ResponseListener;\nimport org.junit.Assert;\nimport org.junit.Before;\n\nimport java.nio.charset.StandardCharsets;\nimport java.util.Map;\n\nimport static org.junit.Assert.*;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\npublic abstract class ElasticsearchBaseTest {\n\n    protected Request request;\n    protected String body;\n    protected Response successResponse;\n    protected Response failResponse;\n    protected String errMsg;\n    protected String index = \"index-1\";\n    protected IPluginConfig config;\n    protected ResponseListener responseListener;\n\n    @Before\n    public void before() {\n        Context context = EaseAgent.getContext();\n        ContextUtils.setBeginTime(context);\n\n        request = new Request(\"GET\", \"/\" + index + \"/_search\");\n        body = \"mock body\";\n        HttpEntity httpEntity = new ByteArrayEntity(body.getBytes(StandardCharsets.UTF_8), ContentType.APPLICATION_JSON);\n        request.setEntity(httpEntity);\n\n        errMsg = \"mock exception\";\n\n        {\n            successResponse = mock(Response.class);\n            StatusLine statusLine = mock(StatusLine.class);\n            when(statusLine.getStatusCode()).thenReturn(200);\n            when(successResponse.getStatusLine()).thenReturn(statusLine);\n        }\n\n        {\n            failResponse = mock(Response.class);\n            StatusLine statusLine = mock(StatusLine.class);\n            when(statusLine.getStatusCode()).thenReturn(500);\n            when(failResponse.getStatusLine()).thenReturn(statusLine);\n        }\n\n        config = mock(IPluginConfig.class);\n        when(config.namespace()).thenReturn(\"es\");\n\n        responseListener = mock(ResponseListener.class);\n\n    }\n\n    protected void assertTrace(boolean success, String error) {\n        ReportSpan mockSpan = MockEaseAgent.getLastSpan();\n        assertNotNull(mockSpan);\n        assertEquals(Span.Kind.CLIENT.name(), mockSpan.kind());\n        assertEquals(\"index-1\", mockSpan.tag(\"es.index\"));\n        assertEquals(\"GET /index-1/_search\", mockSpan.tag(\"es.operation\"));\n        assertEquals(body, mockSpan.tag(\"es.body\"));\n        if (success) {\n            assertNull(mockSpan.tag(\"error\"));\n        }\n        if (!success) {\n            assertNotNull(mockSpan.tag(\"error\"));\n            assertEquals(error, mockSpan.tag(\"error\"));\n        }\n        assertNull(mockSpan.parentId());\n    }\n\n    protected void assertMetric(NameFactory nameFactory, MetricRegistry metricRegistry, boolean success) {\n        Map<String, Metric> metrics = metricRegistry.getMetrics();\n        Assert.assertFalse(metrics.isEmpty());\n        Assert.assertNotNull(metrics.get(nameFactory.timerName(this.index, MetricSubType.DEFAULT)));\n        Assert.assertNotNull(metrics.get(nameFactory.meterName(this.index, MetricSubType.DEFAULT)));\n        Assert.assertNotNull(metrics.get(nameFactory.counterName(this.index, MetricSubType.DEFAULT)));\n        Assert.assertNotNull(metrics.get(nameFactory.gaugeName(this.index, MetricSubType.DEFAULT)));\n        if (success) {\n            Assert.assertNull(metrics.get(nameFactory.meterName(this.index, MetricSubType.ERROR)));\n            Assert.assertNull(metrics.get(nameFactory.counterName(this.index, MetricSubType.ERROR)));\n        } else {\n            Assert.assertNotNull(metrics.get(nameFactory.meterName(this.index, MetricSubType.ERROR)));\n            Assert.assertNotNull(metrics.get(nameFactory.counterName(this.index, MetricSubType.ERROR)));\n        }\n        metricRegistry.remove(nameFactory.timerName(this.index, MetricSubType.DEFAULT));\n        metricRegistry.remove(nameFactory.meterName(this.index, MetricSubType.DEFAULT));\n        metricRegistry.remove(nameFactory.counterName(this.index, MetricSubType.DEFAULT));\n        metricRegistry.remove(nameFactory.gaugeName(this.index, MetricSubType.DEFAULT));\n\n        metricRegistry.remove(nameFactory.meterName(this.index, MetricSubType.ERROR));\n        metricRegistry.remove(nameFactory.counterName(this.index, MetricSubType.ERROR));\n    }\n}\n"
  },
  {
    "path": "plugins/elasticsearch/src/test/java/com/megaease/easeagent/plugin/elasticsearch/interceptor/ElasticsearchCtxUtilsTest.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.elasticsearch.interceptor;\n\nimport org.junit.Assert;\nimport org.junit.Test;\n\npublic class ElasticsearchCtxUtilsTest {\n\n    @Test\n    public void endpoint() {\n        Assert.assertEquals(\"idx-1\", ElasticsearchCtxUtils.getIndex(\"/idx-1/_search\"));\n        Assert.assertEquals(\"idx-1\", ElasticsearchCtxUtils.getIndex(\"/idx-1/_doc/122\"));\n        Assert.assertEquals(\"\", ElasticsearchCtxUtils.getIndex(\"/_xpack\"));\n        Assert.assertEquals(\"\", ElasticsearchCtxUtils.getIndex(\"\"));\n        Assert.assertEquals(\"\", ElasticsearchCtxUtils.getIndex(\"/\"));\n    }\n}\n"
  },
  {
    "path": "plugins/elasticsearch/src/test/java/com/megaease/easeagent/plugin/elasticsearch/interceptor/ElasticsearchPerformRequestAsyncMetricsInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.elasticsearch.interceptor;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class ElasticsearchPerformRequestAsyncMetricsInterceptorTest extends ElasticsearchBaseTest {\n    ElasticsearchPerformRequestAsync4MetricsInterceptor interceptor;\n\n    @Before\n    public void before() {\n        super.before();\n        interceptor = new ElasticsearchPerformRequestAsync4MetricsInterceptor();\n    }\n\n    @Test\n    public void performSuccess() {\n        MethodInfo methodInfo = MethodInfo.builder()\n            .invoker(this)\n            .method(\"perform\")\n            .args(new Object[]{request, responseListener})\n            .build();\n\n        Context context = EaseAgent.getContext();\n        interceptor.init(config, \"\", \"\", \"\");\n        interceptor.before(methodInfo, context);\n        AsyncResponse4MetricsListener traceListener = (AsyncResponse4MetricsListener) methodInfo.getArgs()[1];\n        traceListener.onSuccess(this.successResponse);\n        this.assertMetric(interceptor.getElasticsearchMetric().getNameFactory(),\n            interceptor.getElasticsearchMetric().getMetricRegistry(), true);\n\n    }\n\n    @Test\n    public void performFail() {\n        MethodInfo methodInfo = MethodInfo.builder()\n            .invoker(this)\n            .method(\"perform\")\n            .args(new Object[]{request, responseListener})\n            .build();\n\n        Context context = EaseAgent.getContext();\n        interceptor.init(config, \"\", \"\", \"\");\n        interceptor.before(methodInfo, context);\n        AsyncResponse4MetricsListener traceListener = (AsyncResponse4MetricsListener) methodInfo.getArgs()[1];\n        traceListener.onSuccess(this.failResponse);\n        this.assertMetric(interceptor.getElasticsearchMetric().getNameFactory(),\n            interceptor.getElasticsearchMetric().getMetricRegistry(), false);\n    }\n\n    @Test\n    public void performFailThrowable() {\n        MethodInfo methodInfo = MethodInfo.builder()\n            .invoker(this)\n            .method(\"perform\")\n            .args(new Object[]{request, responseListener})\n            .build();\n\n        Context context = EaseAgent.getContext();\n        interceptor.init(config, \"\", \"\", \"\");\n        interceptor.before(methodInfo, context);\n        AsyncResponse4MetricsListener traceListener = (AsyncResponse4MetricsListener) methodInfo.getArgs()[1];\n        traceListener.onFailure(new RuntimeException(this.errMsg));\n        this.assertMetric(interceptor.getElasticsearchMetric().getNameFactory(),\n            interceptor.getElasticsearchMetric().getMetricRegistry(), false);\n    }\n}\n"
  },
  {
    "path": "plugins/elasticsearch/src/test/java/com/megaease/easeagent/plugin/elasticsearch/interceptor/ElasticsearchPerformRequestAsyncTraceInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.elasticsearch.interceptor;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class ElasticsearchPerformRequestAsyncTraceInterceptorTest extends ElasticsearchBaseTest {\n    ElasticsearchPerformRequestAsync4TraceInterceptor interceptor;\n\n    @Before\n    public void before() {\n        super.before();\n        interceptor = new ElasticsearchPerformRequestAsync4TraceInterceptor();\n    }\n\n    @Test\n    public void performSuccess() {\n        Context context = EaseAgent.getContext();\n        MethodInfo methodInfo = MethodInfo.builder()\n            .invoker(this)\n            .method(\"perform\")\n            .args(new Object[]{request, responseListener})\n            .build();\n        interceptor.before(methodInfo, context);\n        AsyncResponse4TraceListener traceListener = (AsyncResponse4TraceListener) methodInfo.getArgs()[1];\n        traceListener.onSuccess(this.successResponse);\n        this.assertTrace(true, null);\n    }\n\n    @Test\n    public void performFail() {\n        Context context = EaseAgent.getContext();\n        MethodInfo methodInfo = MethodInfo.builder()\n            .invoker(this)\n            .method(\"perform\")\n            .args(new Object[]{request, responseListener})\n            .build();\n        interceptor.before(methodInfo, context);\n        AsyncResponse4TraceListener traceListener = (AsyncResponse4TraceListener) methodInfo.getArgs()[1];\n        traceListener.onSuccess(this.failResponse);\n        this.assertTrace(false, \"500\");\n    }\n\n    @Test\n    public void performFailThrowable() {\n        Context context = EaseAgent.getContext();\n        MethodInfo methodInfo = MethodInfo.builder()\n            .invoker(this)\n            .method(\"perform\")\n            .args(new Object[]{request, responseListener})\n            .build();\n        interceptor.before(methodInfo, context);\n        AsyncResponse4TraceListener traceListener = (AsyncResponse4TraceListener) methodInfo.getArgs()[1];\n        traceListener.onFailure(new RuntimeException(this.errMsg));\n        this.assertTrace(false, this.errMsg);\n    }\n}\n"
  },
  {
    "path": "plugins/elasticsearch/src/test/java/com/megaease/easeagent/plugin/elasticsearch/interceptor/ElasticsearchPerformRequestMetricsInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.elasticsearch.interceptor;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class ElasticsearchPerformRequestMetricsInterceptorTest extends ElasticsearchBaseTest {\n    ElasticsearchPerformRequestMetricsInterceptor interceptor;\n\n    @Before\n    public void before() {\n        super.before();\n        interceptor = new ElasticsearchPerformRequestMetricsInterceptor();\n    }\n\n    @Test\n    public void performSuccess() {\n        MethodInfo methodInfo = MethodInfo.builder()\n            .invoker(this)\n            .method(\"perform\")\n            .args(new Object[]{request})\n            .retValue(this.successResponse)\n            .build();\n\n        Context context = EaseAgent.getContext();\n        interceptor.init(config, \"\", \"\", \"\");\n        interceptor.before(methodInfo, context);\n        interceptor.after(methodInfo, context);\n        this.assertMetric(interceptor.getElasticsearchMetric().getNameFactory(),\n            interceptor.getElasticsearchMetric().getMetricRegistry(), true);\n\n    }\n\n    @Test\n    public void performFail() {\n        MethodInfo methodInfo = MethodInfo.builder()\n            .invoker(this)\n            .method(\"perform\")\n            .args(new Object[]{request})\n            .retValue(this.failResponse)\n            .build();\n\n        Context context = EaseAgent.getContext();\n        interceptor.init(config, \"\", \"\", \"\");\n        interceptor.before(methodInfo, context);\n        interceptor.after(methodInfo, context);\n        this.assertMetric(interceptor.getElasticsearchMetric().getNameFactory(),\n            interceptor.getElasticsearchMetric().getMetricRegistry(), false);\n    }\n\n    @Test\n    public void performFailThrowable() {\n        MethodInfo methodInfo = MethodInfo.builder()\n            .invoker(this)\n            .method(\"perform\")\n            .args(new Object[]{request})\n            .throwable(new RuntimeException(errMsg))\n            .build();\n\n        Context context = EaseAgent.getContext();\n        interceptor.init(config, \"\", \"\", \"\");\n        interceptor.before(methodInfo, context);\n        interceptor.after(methodInfo, context);\n        this.assertMetric(interceptor.getElasticsearchMetric().getNameFactory(),\n            interceptor.getElasticsearchMetric().getMetricRegistry(), false);\n    }\n}\n"
  },
  {
    "path": "plugins/elasticsearch/src/test/java/com/megaease/easeagent/plugin/elasticsearch/interceptor/ElasticsearchPerformRequestTraceInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.elasticsearch.interceptor;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class ElasticsearchPerformRequestTraceInterceptorTest extends ElasticsearchBaseTest {\n    ElasticsearchPerformRequestTraceInterceptor interceptor;\n\n    @Before\n    public void before() {\n        super.before();\n        interceptor = new ElasticsearchPerformRequestTraceInterceptor();\n    }\n\n    @Test\n    public void performSuccess() {\n        MethodInfo methodInfo = MethodInfo.builder()\n            .invoker(this)\n            .method(\"perform\")\n            .args(new Object[]{request})\n            .retValue(this.successResponse)\n            .build();\n\n        Context context = EaseAgent.getContext();\n        interceptor.before(methodInfo, context);\n        interceptor.after(methodInfo, context);\n        this.assertTrace(true, null);\n    }\n\n    @Test\n    public void performFail() {\n        MethodInfo methodInfo = MethodInfo.builder()\n            .invoker(this)\n            .method(\"perform\")\n            .args(new Object[]{request})\n            .retValue(this.failResponse)\n            .build();\n\n        Context context = EaseAgent.getContext();\n        interceptor.before(methodInfo, context);\n        interceptor.after(methodInfo, context);\n        this.assertTrace(false, \"500\");\n    }\n\n    @Test\n    public void performFailThrowable() {\n        MethodInfo methodInfo = MethodInfo.builder()\n            .invoker(this)\n            .method(\"perform\")\n            .args(new Object[]{request})\n            .retValue(this.failResponse)\n            .throwable(new RuntimeException(errMsg))\n            .build();\n\n        Context context = EaseAgent.getContext();\n        interceptor.before(methodInfo, context);\n        interceptor.after(methodInfo, context);\n        this.assertTrace(false, errMsg);\n    }\n}\n"
  },
  {
    "path": "plugins/healthy/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright (c) 2021, MegaEase\n  ~ All rights reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~   http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>plugins</artifactId>\n        <groupId>com.megaease.easeagent</groupId>\n        <version>2.3.0</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>healthy</artifactId>\n\n\n    <dependencies>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>plugin-api</artifactId>\n            <scope>provided</scope>\n        </dependency>\n        <!--<dependency>-->\n            <!--<groupId>org.springframework</groupId>-->\n            <!--<artifactId>spring-context</artifactId>-->\n            <!--<version>5.2.4.RELEASE</version>-->\n            <!--<scope>provided</scope>-->\n        <!--</dependency>-->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot</artifactId>\n            <version>2.6.0</version>\n            <scope>test</scope>\n        </dependency>\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "plugins/healthy/src/main/java/com/megaease/easeagent/plugin/healthy/HealthPlugin.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.healthy;\n\nimport com.megaease.easeagent.plugin.AgentPlugin;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\n\npublic class HealthPlugin implements AgentPlugin {\n    @Override\n    public String getNamespace() {\n        return ConfigConst.Namespace.HEALTH;\n    }\n\n    @Override\n    public String getDomain() {\n        return ConfigConst.INTEGRABILITY;\n    }\n}\n"
  },
  {
    "path": "plugins/healthy/src/main/java/com/megaease/easeagent/plugin/healthy/OnApplicationEventInterceptor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.healthy;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.health.AgentHealth;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.utils.ClassUtils;\n\n@AdviceTo(value = SpringApplicationAdminMXBeanRegistrarAdvice.class)\npublic class OnApplicationEventInterceptor implements Interceptor {\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n\n    }\n\n    @Override\n    public void after(MethodInfo methodInfo, Context context) {\n        Object applicationEvent = methodInfo.getArgs()[0];\n        if (ClassUtils.isInstance(\"org.springframework.boot.context.event.ApplicationReadyEvent\", applicationEvent)) {\n            AgentHealth.INSTANCE.setReady(true);\n        } else if (ClassUtils.isInstance(\"org.springframework.boot.context.event.ApplicationFailedEvent\", applicationEvent)) {\n            AgentHealth.INSTANCE.setReady(false);\n        }\n    }\n\n    @Override\n    public String getType() {\n        return \"healthReady\";\n    }\n\n    @Override\n    public int order() {\n        return Order.HIGH.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/healthy/src/main/java/com/megaease/easeagent/plugin/healthy/SpringApplicationAdminMXBeanRegistrarAdvice.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.healthy;\n\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\nimport com.megaease.easeagent.plugin.tools.matcher.MethodMatcherUtils;\n\nimport java.util.Set;\n\nimport static com.megaease.easeagent.plugin.tools.matcher.ClassMatcherUtils.name;\n\npublic class SpringApplicationAdminMXBeanRegistrarAdvice implements Points {\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return name(\"org.springframework.boot.admin.SpringApplicationAdminMXBeanRegistrar\");\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.multiBuilder()\n            .match(MethodMatcherUtils.name(\"onApplicationEvent\"))\n            .build();\n    }\n}\n"
  },
  {
    "path": "plugins/healthy/src/test/java/com/megaease/easeagent/plugin/healthy/OnApplicationEventInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.healthy;\n\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.api.health.AgentHealth;\nimport com.megaease.easeagent.plugin.bridge.NoOpContext;\nimport org.junit.Assert;\nimport org.junit.Test;\nimport org.springframework.boot.context.event.ApplicationFailedEvent;\nimport org.springframework.boot.context.event.ApplicationReadyEvent;\nimport org.springframework.context.ApplicationEvent;\n\nimport static org.mockito.Mockito.mock;\n\npublic class OnApplicationEventInterceptorTest {\n    @Test\n    public void invokeSuccess() {\n        OnApplicationEventInterceptor interceptor = new OnApplicationEventInterceptor();\n        ApplicationEvent event = mock(ApplicationReadyEvent.class);\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{event}).build();\n        interceptor.after(methodInfo, NoOpContext.NO_OP_CONTEXT);\n        Assert.assertTrue(AgentHealth.INSTANCE.isAlive());\n        Assert.assertTrue(AgentHealth.INSTANCE.isReady());\n    }\n\n    @Test\n    public void invokeFail() {\n        OnApplicationEventInterceptor interceptor = new OnApplicationEventInterceptor();\n        ApplicationEvent event = mock(ApplicationFailedEvent.class);\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{event}).build();\n        interceptor.after(methodInfo, NoOpContext.NO_OP_CONTEXT);\n        Assert.assertTrue(AgentHealth.INSTANCE.isAlive());\n        Assert.assertFalse(AgentHealth.INSTANCE.isReady());\n    }\n}\n"
  },
  {
    "path": "plugins/httpclient/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright (c) 2017, MegaEase\n  ~ All rights reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>plugins</artifactId>\n        <groupId>com.megaease.easeagent</groupId>\n        <version>2.3.0</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>httpclient</artifactId>\n\n\n    <dependencies>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>plugin-api</artifactId>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.apache.httpcomponents</groupId>\n            <artifactId>httpclient</artifactId>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.apache.httpcomponents.client5</groupId>\n            <artifactId>httpclient5</artifactId>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>plugin-api-mock</artifactId>\n            <version>${project.version}</version>\n            <scope>test</scope>\n        </dependency>\n    </dependencies>\n</project>\n"
  },
  {
    "path": "plugins/httpclient/src/main/java/com/megaease/easeagent/plugin/httpclient/ForwardedPlugin.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.httpclient;\n\nimport com.megaease.easeagent.plugin.AgentPlugin;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\n\npublic class ForwardedPlugin implements AgentPlugin {\n    @Override\n    public String getNamespace() {\n        return ConfigConst.Namespace.FORWARDED;\n    }\n\n    @Override\n    public String getDomain() {\n        return ConfigConst.INTEGRABILITY;\n    }\n}\n"
  },
  {
    "path": "plugins/httpclient/src/main/java/com/megaease/easeagent/plugin/httpclient/HttpClientPlugin.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.httpclient;\n\nimport com.megaease.easeagent.plugin.AgentPlugin;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\n\npublic class HttpClientPlugin implements AgentPlugin {\n    @Override\n    public String getNamespace() {\n        return ConfigConst.Namespace.HTTPCLIENT;\n    }\n\n    @Override\n    public String getDomain() {\n        return ConfigConst.OBSERVABILITY;\n    }\n}\n"
  },
  {
    "path": "plugins/httpclient/src/main/java/com/megaease/easeagent/plugin/httpclient/advice/HttpClient5AsyncAdvice.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.httpclient.advice;\n\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.Set;\n\npublic class HttpClient5AsyncAdvice implements Points {\n\n    //        return def.type(hasSuperType(named(\"org.apache.hc.client5.http.async.HttpAsyncClient\"))) // enhanced client class\n    //                .transform(adviceExecute(named(\"execute\")\n    //                        .and(takesArguments(5))\n    //                        .and(returns(named(\"java.util.concurrent.Future\")))\n    //                ))\n    //                .end();\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return ClassMatcher.builder()\n            .hasSuperClass(\"org.apache.hc.client5.http.async.HttpAsyncClient\")\n            .build();\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.multiBuilder()\n            .match(MethodMatcher.builder().named(\"execute\")\n                .argsLength(5)\n                .returnType(\"java.util.concurrent.Future\")\n                .qualifier(\"default\")\n                .build())\n            .build();\n    }\n}\n"
  },
  {
    "path": "plugins/httpclient/src/main/java/com/megaease/easeagent/plugin/httpclient/advice/HttpClient5DoExecuteAdvice.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.httpclient.advice;\n\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.Set;\n\npublic class HttpClient5DoExecuteAdvice implements Points {\n    //return def.type(hasSuperType(named(\"org.apache.hc.client5.http.classic.HttpClient\"))) // enhanced client class\n    //                .transform(adviceExecute(named(\"doExecute\")\n    //                        .and(takesArguments(3))\n    //                        .and(returns(named(\"org.apache.hc.client5.http.impl.classic.CloseableHttpResponse\")))\n    //                ))\n    //                .end();\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return ClassMatcher.builder()\n            .hasSuperClass(\"org.apache.hc.client5.http.classic.HttpClient\")\n            .build();\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.multiBuilder()\n            .match(MethodMatcher.builder().named(\"doExecute\")\n                .argsLength(3)\n                .returnType(\"org.apache.hc.client5.http.impl.classic.CloseableHttpResponse\")\n                .qualifier(\"default\")\n                .build())\n            .build();\n    }\n}\n"
  },
  {
    "path": "plugins/httpclient/src/main/java/com/megaease/easeagent/plugin/httpclient/advice/HttpClientDoExecuteAdvice.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.httpclient.advice;\n\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.Set;\n\npublic class HttpClientDoExecuteAdvice implements Points {\n    //return def.type(hasSuperType(named(\"org.apache.http.client.HttpClient\"))) // enhanced client class\n    //                .transform(adviceExecute(named(\"doExecute\")\n    //                        .and(takesArguments(3))\n    //                        .and(returns(named(\"org.apache.http.client.methods.CloseableHttpResponse\")))\n    //                ))\n    //                .end();\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return ClassMatcher.builder()\n            .hasSuperClass(\"org.apache.http.client.HttpClient\")\n            .build();\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.multiBuilder()\n            .match(MethodMatcher.builder().named(\"doExecute\")\n                .argsLength(3)\n                .returnType(\"org.apache.http.client.methods.CloseableHttpResponse\")\n                .qualifier(\"default\")\n                .build())\n            .build();\n    }\n}\n"
  },
  {
    "path": "plugins/httpclient/src/main/java/com/megaease/easeagent/plugin/httpclient/interceptor/HttpClient5AsyncForwardedInterceptor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.httpclient.interceptor;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\nimport com.megaease.easeagent.plugin.httpclient.ForwardedPlugin;\nimport com.megaease.easeagent.plugin.httpclient.advice.HttpClient5AsyncAdvice;\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport org.apache.hc.core5.http.HttpRequest;\nimport org.apache.hc.core5.http.nio.AsyncRequestProducer;\n\n@AdviceTo(value = HttpClient5AsyncAdvice.class, plugin = ForwardedPlugin.class)\npublic class HttpClient5AsyncForwardedInterceptor implements Interceptor {\n\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n        AsyncRequestProducer requestProducer = (AsyncRequestProducer) methodInfo.getArgs()[0];\n        HttpRequest request = AgentFieldReflectAccessor.getFieldValue(requestProducer, \"request\");\n        if (request == null) {\n            return;\n        }\n        context.injectForwardedHeaders(request::addHeader);\n    }\n\n    @Override\n    public String getType() {\n        return ConfigConst.PluginID.FORWARDED;\n    }\n\n    @Override\n    public int order() {\n        return Order.FORWARDED.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/httpclient/src/main/java/com/megaease/easeagent/plugin/httpclient/interceptor/HttpClient5AsyncTracingInterceptor.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.httpclient.interceptor;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.RequestContext;\nimport com.megaease.easeagent.plugin.api.trace.Scope;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\nimport com.megaease.easeagent.plugin.httpclient.HttpClientPlugin;\nimport com.megaease.easeagent.plugin.httpclient.advice.HttpClient5AsyncAdvice;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.interceptor.NonReentrantInterceptor;\nimport com.megaease.easeagent.plugin.tools.trace.HttpUtils;\nimport org.apache.hc.core5.concurrent.FutureCallback;\nimport org.apache.hc.core5.http.Header;\nimport org.apache.hc.core5.http.HttpRequest;\nimport org.apache.hc.core5.http.HttpResponse;\nimport org.apache.hc.core5.http.nio.AsyncRequestProducer;\n\nimport java.net.URISyntaxException;\n\n@AdviceTo(value = HttpClient5AsyncAdvice.class, plugin = HttpClientPlugin.class)\npublic class HttpClient5AsyncTracingInterceptor implements NonReentrantInterceptor {\n    @Override\n    public void doBefore(MethodInfo methodInfo, Context context) {\n        AsyncRequestProducer requestProducer = (AsyncRequestProducer) methodInfo.getArgs()[0];\n        HttpRequest request = AgentFieldReflectAccessor.getFieldValue(requestProducer, \"request\");\n        if (request == null) {\n            return;\n        }\n        InternalRequest internalRequest = new InternalRequest(request);\n        RequestContext requestContext = context.clientRequest(internalRequest);\n        HttpUtils.handleReceive(requestContext.span().start(), internalRequest);\n        context.put(HttpClient5AsyncTracingInterceptor.class, requestContext);\n        @SuppressWarnings(\"unchecked\")\n        FutureCallback<HttpResponse> callback = (FutureCallback<HttpResponse>) methodInfo.getArgs()[4];\n        InternalFutureCallback internalFutureCallback = new InternalFutureCallback(callback, request, requestContext);\n        methodInfo.changeArg(4, internalFutureCallback);\n    }\n\n    @Override\n    public void doAfter(MethodInfo methodInfo, Context context) {\n        RequestContext requestContext = context.remove(HttpClient5AsyncTracingInterceptor.class);\n        try (Scope ignored = requestContext.scope()) {\n            if (methodInfo.isSuccess()) {\n                return;\n            }\n            requestContext.span().error(methodInfo.getThrowable()).finish();\n        }\n    }\n\n    @Override\n    public int order() {\n        return Order.TRACING.getOrder();\n    }\n\n    public static class InternalFutureCallback implements FutureCallback<HttpResponse> {\n\n        private final FutureCallback<HttpResponse> delegate;\n        private final HttpRequest request;\n        private final RequestContext requestContext;\n\n        public InternalFutureCallback(FutureCallback<HttpResponse> delegate, HttpRequest request, RequestContext requestContext) {\n            this.delegate = delegate;\n            this.request = request;\n            this.requestContext = requestContext;\n        }\n\n        @Override\n        public void completed(HttpResponse result) {\n            this.delegate.completed(result);\n            if (this.requestContext != null) {\n                InternalResponse internalResponse = new InternalResponse(null, request, result);\n                HttpUtils.save(requestContext.span(), internalResponse);\n                requestContext.finish(internalResponse);\n            }\n        }\n\n        @Override\n        public void failed(Exception ex) {\n            this.delegate.failed(ex);\n            if (this.requestContext != null) {\n                this.requestContext.span().abandon();\n            }\n        }\n\n        @Override\n        public void cancelled() {\n            this.delegate.cancelled();\n            if (this.requestContext != null) {\n                this.requestContext.span().abandon();\n            }\n        }\n    }\n\n    static class InternalRequest implements com.megaease.easeagent.plugin.tools.trace.HttpRequest {\n        private final HttpRequest httpRequest;\n\n        public InternalRequest(HttpRequest httpRequestBase) {\n            this.httpRequest = httpRequestBase;\n        }\n\n        @Override\n        public String method() {\n            return httpRequest.getMethod();\n        }\n\n        @Override\n        public String path() {\n            try {\n                return httpRequest.getUri().toString();\n            } catch (URISyntaxException e) {\n                return httpRequest.getRequestUri();\n            }\n        }\n\n        @Override\n        public String route() {\n            return null;\n        }\n\n        @Override\n        public String getRemoteAddr() {\n            return null;\n        }\n\n        @Override\n        public int getRemotePort() {\n            return 0;\n        }\n\n        @Override\n        public Span.Kind kind() {\n            return Span.Kind.CLIENT;\n        }\n\n        @Override\n        public String header(String name) {\n            Header header = httpRequest.getFirstHeader(name);\n            if (header != null) {\n                return header.getValue();\n            }\n            return null;\n        }\n\n        @Override\n        public boolean cacheScope() {\n            return false;\n        }\n\n        @Override\n        public void setHeader(String name, String value) {\n            httpRequest.addHeader(name, value);\n        }\n\n    }\n\n    static class InternalResponse implements com.megaease.easeagent.plugin.tools.trace.HttpResponse {\n        private final Throwable caught;\n        private final HttpRequest request;\n        private final org.apache.hc.core5.http.HttpResponse httpResponse;\n\n        public InternalResponse(Throwable caught, HttpRequest request, org.apache.hc.core5.http.HttpResponse httpResponse) {\n            this.caught = caught;\n            this.request = request;\n            this.httpResponse = httpResponse;\n        }\n\n        @Override\n        public String method() {\n            return request.getMethod();\n        }\n\n        @Override\n        public String route() {\n            return null;\n        }\n\n        @Override\n        public int statusCode() {\n            return this.httpResponse.getCode();\n        }\n\n        @Override\n        public Throwable maybeError() {\n            return caught;\n        }\n\n\n        @Override\n        public String header(String name) {\n            Header header = httpResponse.getFirstHeader(name);\n            if (header == null) {\n                return null;\n            }\n            return header.getValue();\n        }\n    }\n}\n\n"
  },
  {
    "path": "plugins/httpclient/src/main/java/com/megaease/easeagent/plugin/httpclient/interceptor/HttpClient5DoExecuteForwardedInterceptor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.httpclient.interceptor;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.httpclient.ForwardedPlugin;\nimport com.megaease.easeagent.plugin.httpclient.advice.HttpClient5DoExecuteAdvice;\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport org.apache.hc.client5.http.classic.methods.HttpUriRequestBase;\n\n@AdviceTo(value = HttpClient5DoExecuteAdvice.class, qualifier = \"default\", plugin = ForwardedPlugin.class)\npublic class HttpClient5DoExecuteForwardedInterceptor implements Interceptor {\n\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n        if (methodInfo.getArgs() == null) {\n            return;\n        }\n        for (Object arg : methodInfo.getArgs()) {\n            if (arg instanceof HttpUriRequestBase) {\n                final HttpUriRequestBase httpRequestBase = (HttpUriRequestBase) arg;\n                context.injectForwardedHeaders(httpRequestBase::setHeader);\n                return;\n            }\n        }\n    }\n\n    @Override\n    public String getType() {\n        return ConfigConst.PluginID.FORWARDED;\n    }\n\n    @Override\n    public int order() {\n        return Order.FORWARDED.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/httpclient/src/main/java/com/megaease/easeagent/plugin/httpclient/interceptor/HttpClient5DoExecuteInterceptor.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.httpclient.interceptor;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.httpclient.HttpClientPlugin;\nimport com.megaease.easeagent.plugin.httpclient.advice.HttpClient5DoExecuteAdvice;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.tools.trace.BaseHttpClientTracingInterceptor;\nimport com.megaease.easeagent.plugin.tools.trace.HttpRequest;\nimport com.megaease.easeagent.plugin.tools.trace.HttpResponse;\nimport org.apache.hc.client5.http.classic.methods.HttpUriRequestBase;\nimport org.apache.hc.core5.http.Header;\n\nimport java.net.URISyntaxException;\n\n@AdviceTo(value = HttpClient5DoExecuteAdvice.class, qualifier = \"default\", plugin = HttpClientPlugin.class)\npublic class HttpClient5DoExecuteInterceptor extends BaseHttpClientTracingInterceptor {\n    @Override\n    public Object getProgressKey() {\n        return HttpClient5DoExecuteInterceptor.class;\n    }\n\n    @Override\n    protected HttpRequest getRequest(MethodInfo methodInfo, Context context) {\n        return new InternalRequest(getHttpRequestBase(methodInfo.getArgs()));\n    }\n\n    private HttpUriRequestBase getHttpRequestBase(Object[] args) {\n        HttpUriRequestBase httpRequestBase = null;\n        if (args != null) {\n            for (Object arg : args) {\n                if (arg instanceof HttpUriRequestBase) {\n                    httpRequestBase = (HttpUriRequestBase) arg;\n                    break;\n                }\n            }\n        }\n        return httpRequestBase;\n    }\n\n    @Override\n    protected HttpResponse getResponse(MethodInfo methodInfo, Context context) {\n        return new InternalResponse(methodInfo.getThrowable(), getHttpRequestBase(methodInfo.getArgs()), (org.apache.hc.core5.http.HttpResponse) methodInfo.getRetValue());\n    }\n\n\n    static class InternalRequest implements HttpRequest {\n\n        private final HttpUriRequestBase httpRequestBase;\n\n        public InternalRequest(HttpUriRequestBase httpRequestBase) {\n            this.httpRequestBase = httpRequestBase;\n        }\n\n\n        @Override\n        public String method() {\n            return httpRequestBase.getMethod();\n        }\n\n        @Override\n        public String path() {\n            try {\n                return httpRequestBase.getUri().toString();\n            } catch (URISyntaxException e) {\n                return httpRequestBase.getRequestUri();\n            }\n        }\n\n        @Override\n        public String route() {\n            return null;\n        }\n\n        @Override\n        public String getRemoteAddr() {\n            return null;\n        }\n\n        @Override\n        public int getRemotePort() {\n            return 0;\n        }\n\n        @Override\n        public Span.Kind kind() {\n            return Span.Kind.CLIENT;\n        }\n\n        @Override\n        public String header(String name) {\n            Header header = httpRequestBase.getFirstHeader(name);\n            if (header != null) {\n                return header.getValue();\n            }\n            return null;\n        }\n\n        @Override\n        public boolean cacheScope() {\n            return false;\n        }\n\n        @Override\n        public void setHeader(String name, String value) {\n            httpRequestBase.addHeader(name, value);\n        }\n\n    }\n\n    static class InternalResponse implements HttpResponse {\n        private final Throwable caught;\n        private final HttpUriRequestBase httpRequestBase;\n        private final org.apache.hc.core5.http.HttpResponse httpResponse;\n\n        public InternalResponse(Throwable caught, HttpUriRequestBase httpRequestBase, org.apache.hc.core5.http.HttpResponse httpResponse) {\n            this.caught = caught;\n            this.httpRequestBase = httpRequestBase;\n            this.httpResponse = httpResponse;\n        }\n\n        @Override\n        public String method() {\n            return httpRequestBase.getMethod();\n        }\n\n        @Override\n        public String route() {\n            return null;\n        }\n\n        @Override\n        public int statusCode() {\n            return httpResponse.getCode();\n        }\n\n        @Override\n        public Throwable maybeError() {\n            return caught;\n        }\n\n        @Override\n        public String header(String name) {\n            Header header = httpResponse.getFirstHeader(name);\n            if (header == null) {\n                return null;\n            }\n            return header.getValue();\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/httpclient/src/main/java/com/megaease/easeagent/plugin/httpclient/interceptor/HttpClientDoExecuteForwardedInterceptor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.httpclient.interceptor;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.httpclient.ForwardedPlugin;\nimport com.megaease.easeagent.plugin.httpclient.advice.HttpClientDoExecuteAdvice;\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport org.apache.http.client.methods.HttpRequestBase;\n\n@AdviceTo(value = HttpClientDoExecuteAdvice.class, qualifier = \"default\", plugin = ForwardedPlugin.class)\npublic class HttpClientDoExecuteForwardedInterceptor implements Interceptor {\n\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n        if (methodInfo.getArgs() == null) {\n            return;\n        }\n        for (Object arg : methodInfo.getArgs()) {\n            if (arg instanceof HttpRequestBase) {\n                final HttpRequestBase httpRequestBase = (HttpRequestBase) arg;\n                context.injectForwardedHeaders(httpRequestBase::setHeader);\n                return;\n            }\n        }\n    }\n\n    @Override\n    public String getType() {\n        return ConfigConst.PluginID.FORWARDED;\n    }\n\n    @Override\n    public int order() {\n        return Order.FORWARDED.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/httpclient/src/main/java/com/megaease/easeagent/plugin/httpclient/interceptor/HttpClientDoExecuteInterceptor.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.httpclient.interceptor;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.httpclient.HttpClientPlugin;\nimport com.megaease.easeagent.plugin.httpclient.advice.HttpClientDoExecuteAdvice;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.tools.trace.BaseHttpClientTracingInterceptor;\nimport com.megaease.easeagent.plugin.tools.trace.HttpRequest;\nimport com.megaease.easeagent.plugin.tools.trace.HttpResponse;\nimport org.apache.http.Header;\nimport org.apache.http.client.methods.HttpRequestBase;\n\n@AdviceTo(value = HttpClientDoExecuteAdvice.class, qualifier = \"default\", plugin = HttpClientPlugin.class)\npublic class HttpClientDoExecuteInterceptor extends BaseHttpClientTracingInterceptor {\n    @Override\n    public Object getProgressKey() {\n        return HttpClientDoExecuteInterceptor.class;\n    }\n\n    @Override\n    protected HttpRequest getRequest(MethodInfo methodInfo, Context context) {\n        return new InternalRequest(getHttpRequestBase(methodInfo.getArgs()));\n    }\n\n    private HttpRequestBase getHttpRequestBase(Object[] args) {\n        HttpRequestBase httpRequestBase = null;\n        if (args != null) {\n            for (Object arg : args) {\n                if (arg instanceof HttpRequestBase) {\n                    httpRequestBase = (HttpRequestBase) arg;\n                    break;\n                }\n            }\n        }\n        return httpRequestBase;\n    }\n\n    @Override\n    protected HttpResponse getResponse(MethodInfo methodInfo, Context context) {\n        return new InternalResponse(methodInfo.getThrowable(), getHttpRequestBase(methodInfo.getArgs()), (org.apache.http.HttpResponse) methodInfo.getRetValue());\n    }\n\n\n    static class InternalRequest implements HttpRequest {\n\n        private final HttpRequestBase httpRequestBase;\n\n        public InternalRequest(HttpRequestBase httpRequestBase) {\n            this.httpRequestBase = httpRequestBase;\n        }\n\n\n        @Override\n        public String method() {\n            return httpRequestBase.getMethod();\n        }\n\n        @Override\n        public String path() {\n            return httpRequestBase.getURI().toString();\n        }\n\n        @Override\n        public String route() {\n            return null;\n        }\n\n        @Override\n        public String getRemoteAddr() {\n            return null;\n        }\n\n        @Override\n        public int getRemotePort() {\n            return 0;\n        }\n\n        @Override\n        public Span.Kind kind() {\n            return Span.Kind.CLIENT;\n        }\n\n        @Override\n        public String header(String name) {\n            Header header = httpRequestBase.getFirstHeader(name);\n            if (header != null) {\n                return header.getValue();\n            }\n            return null;\n        }\n\n        @Override\n        public boolean cacheScope() {\n            return false;\n        }\n\n        @Override\n        public void setHeader(String name, String value) {\n            httpRequestBase.addHeader(name, value);\n        }\n\n    }\n\n    static class InternalResponse implements HttpResponse {\n        private final Throwable caught;\n        private final HttpRequestBase httpRequestBase;\n        private final org.apache.http.HttpResponse httpResponse;\n\n        public InternalResponse(Throwable caught, HttpRequestBase httpRequestBase, org.apache.http.HttpResponse httpResponse) {\n            this.caught = caught;\n            this.httpRequestBase = httpRequestBase;\n            this.httpResponse = httpResponse;\n        }\n\n        @Override\n        public String method() {\n            return httpRequestBase.getMethod();\n        }\n\n        @Override\n        public String route() {\n            return null;\n        }\n\n        @Override\n        public int statusCode() {\n            return this.httpResponse.getStatusLine().getStatusCode();\n        }\n\n        @Override\n        public Throwable maybeError() {\n            return caught;\n        }\n\n\n        @Override\n        public String header(String name) {\n            Header header = httpResponse.getFirstHeader(name);\n            if (header == null) {\n                return null;\n            }\n            return header.getValue();\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/httpclient/src/test/java/com/megaease/easeagent/plugin/httpclient/interceptor/HttpClient5AsyncForwardedInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.httpclient.interceptor;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport org.apache.hc.client5.http.async.methods.SimpleHttpRequest;\nimport org.apache.hc.client5.http.async.methods.SimpleRequestProducer;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class HttpClient5AsyncForwardedInterceptorTest {\n\n    @Test\n    public void before() {\n        SimpleHttpRequest simpleHttpRequest = SimpleHttpRequest.create(\"GET\", \"http://127.0.0.1:8080\");\n        SimpleRequestProducer simpleRequestProducer = SimpleRequestProducer.create(simpleHttpRequest);\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{simpleRequestProducer}).build();\n        HttpClient5AsyncForwardedInterceptor httpClientDoExecuteForwardedInterceptor = new HttpClient5AsyncForwardedInterceptor();\n        Context context = EaseAgent.getContext();\n        httpClientDoExecuteForwardedInterceptor.before(methodInfo, context);\n        assertNull(simpleHttpRequest.getFirstHeader(TestConst.FORWARDED_NAME));\n        context.put(TestConst.FORWARDED_NAME, TestConst.FORWARDED_VALUE);\n        try {\n            httpClientDoExecuteForwardedInterceptor.before(methodInfo, context);\n            assertNotNull(simpleHttpRequest.getFirstHeader(TestConst.FORWARDED_NAME));\n            assertEquals(TestConst.FORWARDED_VALUE, simpleHttpRequest.getFirstHeader(TestConst.FORWARDED_NAME).getValue());\n        } finally {\n            context.remove(TestConst.FORWARDED_NAME);\n        }\n    }\n\n    @Test\n    public void getType() {\n        HttpClient5AsyncForwardedInterceptor httpClient5AsyncForwardedInterceptor = new HttpClient5AsyncForwardedInterceptor();\n        assertEquals(ConfigConst.PluginID.FORWARDED, httpClient5AsyncForwardedInterceptor.getType());\n    }\n}\n"
  },
  {
    "path": "plugins/httpclient/src/test/java/com/megaease/easeagent/plugin/httpclient/interceptor/HttpClient5AsyncTracingInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.httpclient.interceptor;\n\nimport com.megaease.easeagent.mock.plugin.api.MockEaseAgent;\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.trace.Scope;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport org.apache.hc.client5.http.async.methods.SimpleHttpRequest;\nimport org.apache.hc.client5.http.async.methods.SimpleRequestProducer;\nimport org.apache.hc.core5.concurrent.FutureCallback;\nimport org.apache.hc.core5.http.HttpResponse;\nimport org.apache.hc.core5.http.message.BasicHttpResponse;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.function.Consumer;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class HttpClient5AsyncTracingInterceptorTest {\n\n    @Test\n    public void doBefore() throws InterruptedException {\n        BasicHttpResponse basicHttpResponse = new BasicHttpResponse(200);\n        basicHttpResponse.setHeader(TestConst.RESPONSE_TAG_NAME, TestConst.RESPONSE_TAG_VALUE);\n        ReportSpan mockSpan = runOne(httpResponseFutureCallback -> {\n            httpResponseFutureCallback.completed(basicHttpResponse);\n        });\n        assertNotNull(mockSpan);\n        assertEquals(Span.Kind.CLIENT.name(), mockSpan.kind());\n        assertEquals(TestConst.RESPONSE_TAG_VALUE, mockSpan.tag(TestConst.RESPONSE_TAG_NAME));\n        assertNull(mockSpan.parentId());\n\n        Context context = EaseAgent.getContext();\n        Span span = context.nextSpan();\n        try (Scope scope = span.maybeScope()) {\n            mockSpan = runOne(httpResponseFutureCallback -> {\n                httpResponseFutureCallback.completed(basicHttpResponse);\n            });\n            assertNotNull(mockSpan);\n            assertEquals(span.traceIdString(), mockSpan.traceId());\n            assertEquals(span.spanIdString(), mockSpan.parentId());\n            assertNotNull(mockSpan.id());\n        }\n        span.abandon();\n\n        mockSpan = runOne(httpResponseFutureCallback -> {\n            httpResponseFutureCallback.failed(new RuntimeException(\"test error\"));\n        });\n        assertNull(mockSpan);\n\n    }\n\n    private static ReportSpan runOne(final Consumer<FutureCallback<HttpResponse>> consumer) throws InterruptedException {\n        SimpleHttpRequest simpleHttpRequest = SimpleHttpRequest.create(\"GET\", \"http://127.0.0.1:8080\");\n        SimpleRequestProducer simpleRequestProducer = SimpleRequestProducer.create(simpleHttpRequest);\n        FutureCallback<HttpResponse> callback = new FutureCallback<HttpResponse>() {\n            @Override\n            public void completed(HttpResponse httpResponse) {\n\n            }\n\n            @Override\n            public void failed(Exception e) {\n\n            }\n\n            @Override\n            public void cancelled() {\n\n            }\n        };\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{\n            simpleRequestProducer, null, null, null, callback\n        }).build();\n        MockEaseAgent.cleanLastSpan();\n        HttpClient5AsyncTracingInterceptor httpClient5AsyncTracingInterceptor = new HttpClient5AsyncTracingInterceptor();\n        httpClient5AsyncTracingInterceptor.doBefore(methodInfo, EaseAgent.getContext());\n        assertNotNull(EaseAgent.getContext().get(HttpClient5AsyncTracingInterceptor.class));\n        httpClient5AsyncTracingInterceptor.doAfter(methodInfo, EaseAgent.getContext());\n        assertNull(EaseAgent.getContext().get(HttpClient5AsyncTracingInterceptor.class));\n        AtomicReference<FutureCallback<HttpResponse>> newCallBack = new AtomicReference<>();\n        for (Object o : methodInfo.getArgs()) {\n            if (o instanceof FutureCallback) {\n                newCallBack.set((FutureCallback<HttpResponse>) o);\n            }\n        }\n        assertNotNull(newCallBack.get());\n        assertNull(MockEaseAgent.getLastSpan());\n\n        Thread thread = new Thread(() -> consumer.accept(newCallBack.get()));\n        thread.start();\n        thread.join();\n        return MockEaseAgent.getLastSpan();\n    }\n\n    @Test\n    public void doAfter() throws InterruptedException {\n        doBefore();\n    }\n}\n"
  },
  {
    "path": "plugins/httpclient/src/test/java/com/megaease/easeagent/plugin/httpclient/interceptor/HttpClient5DoExecuteForwardedInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.httpclient.interceptor;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport org.apache.hc.client5.http.classic.methods.HttpGet;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class HttpClient5DoExecuteForwardedInterceptorTest {\n\n    @Test\n    public void before() {\n        HttpGet httpGet = new HttpGet(\"http://127.0.0.1:8080\");\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{httpGet}).build();\n        HttpClient5DoExecuteForwardedInterceptor httpClientDoExecuteForwardedInterceptor = new HttpClient5DoExecuteForwardedInterceptor();\n        Context context = EaseAgent.getContext();\n        httpClientDoExecuteForwardedInterceptor.before(methodInfo, context);\n        assertNull(httpGet.getFirstHeader(TestConst.FORWARDED_NAME));\n        context.put(TestConst.FORWARDED_NAME, TestConst.FORWARDED_VALUE);\n        try {\n            httpClientDoExecuteForwardedInterceptor.before(methodInfo, context);\n            assertNotNull(httpGet.getFirstHeader(TestConst.FORWARDED_NAME));\n            assertEquals(TestConst.FORWARDED_VALUE, httpGet.getFirstHeader(TestConst.FORWARDED_NAME).getValue());\n        } finally {\n            context.remove(TestConst.FORWARDED_NAME);\n        }\n    }\n\n    @Test\n    public void getType() {\n        HttpClient5DoExecuteForwardedInterceptor httpClient5DoExecuteForwardedInterceptor = new HttpClient5DoExecuteForwardedInterceptor();\n        assertEquals(ConfigConst.PluginID.FORWARDED, httpClient5DoExecuteForwardedInterceptor.getType());\n    }\n}\n"
  },
  {
    "path": "plugins/httpclient/src/test/java/com/megaease/easeagent/plugin/httpclient/interceptor/HttpClient5DoExecuteInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.httpclient.interceptor;\n\nimport com.megaease.easeagent.mock.plugin.api.MockEaseAgent;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.trace.Scope;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport com.megaease.easeagent.plugin.tools.trace.HttpRequest;\nimport com.megaease.easeagent.plugin.tools.trace.HttpResponse;\nimport org.apache.hc.client5.http.classic.methods.HttpGet;\nimport org.apache.hc.core5.http.message.BasicHttpResponse;\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\npublic class HttpClient5DoExecuteInterceptorTest {\n\n\n    @Test\n    public void before() {\n        Context context = EaseAgent.getContext();\n        HttpGet httpGet = new HttpGet(\"http://127.0.0.1:8080\");\n        BasicHttpResponse basicHttpResponse = new BasicHttpResponse(200);\n        basicHttpResponse.setHeader(TestConst.RESPONSE_TAG_NAME, TestConst.RESPONSE_TAG_VALUE);\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{httpGet}).retValue(basicHttpResponse).build();\n\n        HttpClient5DoExecuteInterceptor httpClient5DoExecuteInterceptor = new HttpClient5DoExecuteInterceptor();\n        MockEaseAgent.cleanLastSpan();\n        httpClient5DoExecuteInterceptor.before(methodInfo, context);\n        httpClient5DoExecuteInterceptor.after(methodInfo, context);\n        ReportSpan mockSpan = MockEaseAgent.getLastSpan();\n        assertNotNull(mockSpan);\n        assertEquals(Span.Kind.CLIENT.name(), mockSpan.kind());\n        assertEquals(TestConst.RESPONSE_TAG_VALUE, mockSpan.tag(TestConst.RESPONSE_TAG_NAME));\n        assertNull(mockSpan.parentId());\n\n        Span span = context.nextSpan();\n        try (Scope ignored = span.maybeScope()) {\n            httpClient5DoExecuteInterceptor.doBefore(methodInfo, context);\n            httpClient5DoExecuteInterceptor.doAfter(methodInfo, context);\n            mockSpan = MockEaseAgent.getLastSpan();\n            assertEquals(span.traceIdString(), mockSpan.traceId());\n            assertEquals(span.spanIdString(), mockSpan.parentId());\n            assertNotNull(mockSpan.id());\n        }\n    }\n\n    @Test\n    public void after() {\n        before();\n    }\n\n\n    @Test\n    public void getRequest() {\n        Context context = EaseAgent.getContext();\n        HttpGet httpGet = new HttpGet(\"http://127.0.0.1:8080\");\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{httpGet}).build();\n\n        HttpClient5DoExecuteInterceptor httpClient5DoExecuteInterceptor = new HttpClient5DoExecuteInterceptor();\n        HttpRequest request = httpClient5DoExecuteInterceptor.getRequest(methodInfo, context);\n        assertEquals(Span.Kind.CLIENT, request.kind());\n        assertEquals(\"GET\", request.method());\n    }\n\n    @Test\n    public void getResponse() {\n        Context context = EaseAgent.getContext();\n        HttpGet httpGet = new HttpGet(\"http://127.0.0.1:8080\");\n        BasicHttpResponse basicHttpResponse = new BasicHttpResponse(200);\n        basicHttpResponse.setHeader(\"aa\", \"bb\");\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{httpGet}).retValue(basicHttpResponse).build();\n\n        HttpClient5DoExecuteInterceptor httpClientDoExecuteInterceptor = new HttpClient5DoExecuteInterceptor();\n\n        HttpResponse httpResponse = httpClientDoExecuteInterceptor.getResponse(methodInfo, context);\n        assertEquals(200, httpResponse.statusCode());\n    }\n}\n"
  },
  {
    "path": "plugins/httpclient/src/test/java/com/megaease/easeagent/plugin/httpclient/interceptor/HttpClientDoExecuteForwardedInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.httpclient.interceptor;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport org.apache.http.client.methods.HttpGet;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNull;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class HttpClientDoExecuteForwardedInterceptorTest {\n\n    @Test\n    public void before() {\n        Context context = EaseAgent.getContext();\n        HttpGet httpGet = new HttpGet();\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{httpGet}).build();\n        HttpClientDoExecuteForwardedInterceptor httpClientDoExecuteForwardedInterceptor = new HttpClientDoExecuteForwardedInterceptor();\n\n\n        httpClientDoExecuteForwardedInterceptor.before(methodInfo, context);\n        assertNull(httpGet.getFirstHeader(TestConst.FORWARDED_NAME));\n        context.put(TestConst.FORWARDED_NAME, TestConst.FORWARDED_VALUE);\n        httpClientDoExecuteForwardedInterceptor.before(methodInfo, context);\n        assertEquals(TestConst.FORWARDED_VALUE, httpGet.getFirstHeader(TestConst.FORWARDED_NAME).getValue());\n        context.remove(TestConst.FORWARDED_NAME);\n    }\n\n    @Test\n    public void getType() {\n        HttpClientDoExecuteForwardedInterceptor httpClientDoExecuteForwardedInterceptor = new HttpClientDoExecuteForwardedInterceptor();\n        assertEquals(ConfigConst.PluginID.FORWARDED, httpClientDoExecuteForwardedInterceptor.getType());\n    }\n}\n"
  },
  {
    "path": "plugins/httpclient/src/test/java/com/megaease/easeagent/plugin/httpclient/interceptor/HttpClientDoExecuteInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.httpclient.interceptor;\n\nimport com.megaease.easeagent.mock.plugin.api.MockEaseAgent;\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.trace.Scope;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport com.megaease.easeagent.plugin.tools.trace.HttpRequest;\nimport com.megaease.easeagent.plugin.tools.trace.HttpResponse;\nimport org.apache.http.ProtocolVersion;\nimport org.apache.http.client.methods.HttpGet;\nimport org.apache.http.message.BasicHttpResponse;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class HttpClientDoExecuteInterceptorTest {\n\n\n    @Test\n    public void before() {\n        Context context = EaseAgent.getContext();\n        HttpGet httpGet = new HttpGet(\"http://127.0.0.1:8080\");\n        ProtocolVersion protocolVersion = new ProtocolVersion(\"testProtocol\", 10, 1);\n        BasicHttpResponse basicHttpResponse = new BasicHttpResponse(protocolVersion, 200, \"\");\n        basicHttpResponse.setHeader(TestConst.RESPONSE_TAG_NAME, TestConst.RESPONSE_TAG_VALUE);\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{httpGet}).retValue(basicHttpResponse).build();\n\n        HttpClientDoExecuteInterceptor httpClientDoExecuteInterceptor = new HttpClientDoExecuteInterceptor();\n        MockEaseAgent.cleanLastSpan();\n        httpClientDoExecuteInterceptor.before(methodInfo, context);\n        httpClientDoExecuteInterceptor.after(methodInfo, context);\n        ReportSpan mockSpan = MockEaseAgent.getLastSpan();\n        assertNotNull(mockSpan);\n        assertEquals(Span.Kind.CLIENT.name(), mockSpan.kind());\n        assertEquals(TestConst.RESPONSE_TAG_VALUE, mockSpan.tag(TestConst.RESPONSE_TAG_NAME));\n        assertNull(mockSpan.parentId());\n\n        Span span = context.nextSpan();\n        try (Scope ignored = span.maybeScope()) {\n            httpClientDoExecuteInterceptor.doBefore(methodInfo, context);\n            httpClientDoExecuteInterceptor.doAfter(methodInfo, context);\n            mockSpan = MockEaseAgent.getLastSpan();\n            assertEquals(span.traceIdString(), mockSpan.traceId());\n            assertEquals(span.spanIdString(), mockSpan.parentId());\n            assertNotNull(mockSpan.id());\n        }\n        span.abandon();\n    }\n\n    @Test\n    public void after() {\n        before();\n    }\n\n\n    @Test\n    public void getRequest() {\n        Context context = EaseAgent.getContext();\n        HttpGet httpGet = new HttpGet();\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{httpGet}).build();\n\n        HttpClientDoExecuteInterceptor httpClientDoExecuteInterceptor = new HttpClientDoExecuteInterceptor();\n        HttpRequest request = httpClientDoExecuteInterceptor.getRequest(methodInfo, context);\n        assertEquals(com.megaease.easeagent.plugin.api.trace.Span.Kind.CLIENT, request.kind());\n        assertEquals(\"GET\", request.method());\n    }\n\n    @Test\n    public void getResponse() {\n        Context context = EaseAgent.getContext();\n        HttpGet httpGet = new HttpGet();\n        ProtocolVersion protocolVersion = new ProtocolVersion(\"testProtocol\", 10, 1);\n        BasicHttpResponse basicHttpResponse = new BasicHttpResponse(protocolVersion, 200, \"\");\n        basicHttpResponse.setHeader(\"aa\", \"bb\");\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{httpGet}).retValue(basicHttpResponse).build();\n\n        HttpClientDoExecuteInterceptor httpClientDoExecuteInterceptor = new HttpClientDoExecuteInterceptor();\n\n        HttpResponse httpResponse = httpClientDoExecuteInterceptor.getResponse(methodInfo, context);\n        assertEquals(200, httpResponse.statusCode());\n    }\n}\n"
  },
  {
    "path": "plugins/httpclient/src/test/java/com/megaease/easeagent/plugin/httpclient/interceptor/TestConst.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.httpclient.interceptor;\n\npublic class TestConst {\n    public static final String FORWARDED_NAME = \"X-Forwarded-For\";\n    public static final String FORWARDED_VALUE = \"testForwarded\";\n    public static final String RESPONSE_TAG_NAME = \"X-EG-Test\";\n    public static final String RESPONSE_TAG_VALUE = \"X-EG-Test-Value\";\n}\n"
  },
  {
    "path": "plugins/httpclient/src/test/resources/mock_agent.properties",
    "content": "easeagent.progress.forwarded.headers=X-Forwarded-For\nobservability.tracings.tag.response.headers.eg.0=X-EG-Test\n"
  },
  {
    "path": "plugins/httpservlet/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright (c) 2021, MegaEase\n  ~ All rights reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~   http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>plugins</artifactId>\n        <groupId>com.megaease.easeagent</groupId>\n        <version>2.3.0</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>httpservlet</artifactId>\n\n\n    <dependencies>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>plugin-api</artifactId>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>javax.servlet</groupId>\n            <artifactId>javax.servlet-api</artifactId>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>plugin-api-mock</artifactId>\n            <version>${project.version}</version>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework</groupId>\n            <artifactId>spring-test</artifactId>\n            <version>5.3.18</version>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework</groupId>\n            <artifactId>spring-webmvc</artifactId>\n            <version>5.3.18</version>\n            <scope>test</scope>\n        </dependency>\n    </dependencies>\n</project>\n"
  },
  {
    "path": "plugins/httpservlet/src/main/java/com/megaease/easeagent/plugin/httpservlet/AccessPlugin.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.httpservlet;\n\nimport com.megaease.easeagent.plugin.AgentPlugin;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.enums.Order;\n\npublic class AccessPlugin implements AgentPlugin {\n    @Override\n    public String getNamespace() {\n        return ConfigConst.Namespace.ACCESS;\n    }\n\n    @Override\n    public String getDomain() {\n        return ConfigConst.OBSERVABILITY;\n    }\n\n    @Override\n    public int order() {\n        return Order.HIGH.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/httpservlet/src/main/java/com/megaease/easeagent/plugin/httpservlet/ForwardedPlugin.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.httpservlet;\n\nimport com.megaease.easeagent.plugin.AgentPlugin;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.enums.Order;\n\npublic class ForwardedPlugin implements AgentPlugin {\n    @Override\n    public String getNamespace() {\n        return ConfigConst.Namespace.FORWARDED;\n    }\n\n    @Override\n    public String getDomain() {\n        return ConfigConst.INTEGRABILITY;\n    }\n\n    @Override\n    public int order() {\n        return Order.FORWARDED.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/httpservlet/src/main/java/com/megaease/easeagent/plugin/httpservlet/HttpServletPlugin.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.httpservlet;\n\nimport com.megaease.easeagent.plugin.AgentPlugin;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.enums.Order;\n\nimport static com.megaease.easeagent.plugin.api.config.ConfigConst.Namespace.HTTP_SERVLET;\n\npublic class HttpServletPlugin implements AgentPlugin {\n    @Override\n    public String getNamespace() {\n        return HTTP_SERVLET;\n    }\n\n    @Override\n    public String getDomain() {\n        return ConfigConst.OBSERVABILITY;\n    }\n\n    @Override\n    public int order() {\n        return Order.HIGH.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/httpservlet/src/main/java/com/megaease/easeagent/plugin/httpservlet/advice/DoFilterPoints.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.httpservlet.advice;\n\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.Set;\n\npublic class DoFilterPoints implements Points {\n    private static final String FILTER_NAME = \"javax.servlet.Filter\";\n    private static final String HTTP_SERVLET_NAME = \"javax.servlet.http.HttpServlet\";\n    static final String SERVLET_REQUEST = \"javax.servlet.ServletRequest\";\n    static final String SERVLET_RESPONSE = \"javax.servlet.ServletResponse\";\n\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return ClassMatcher.builder()\n            .hasInterface(FILTER_NAME)\n            .build().or(ClassMatcher.builder()\n                .hasSuperClass(HTTP_SERVLET_NAME)\n                .build());\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.multiBuilder()\n            .match(MethodMatcher.builder().named(\"doFilter\")\n                .arg(0, SERVLET_REQUEST)\n                .arg(1, SERVLET_RESPONSE)\n                .or()\n                .named(\"service\")\n                .arg(0, SERVLET_REQUEST)\n                .arg(1, SERVLET_RESPONSE)\n                .qualifier(\"default\")\n                .build())\n            .build();\n    }\n}\n"
  },
  {
    "path": "plugins/httpservlet/src/main/java/com/megaease/easeagent/plugin/httpservlet/interceptor/BaseServletInterceptor.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.httpservlet.interceptor;\n\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.httpservlet.utils.InternalAsyncListener;\nimport com.megaease.easeagent.plugin.httpservlet.utils.ServletUtils;\nimport com.megaease.easeagent.plugin.interceptor.NonReentrantInterceptor;\n\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\n\npublic abstract class BaseServletInterceptor implements NonReentrantInterceptor {\n\n    @Override\n    public void doBefore(MethodInfo methodInfo, Context context) {\n        HttpServletRequest httpServletRequest = (HttpServletRequest) methodInfo.getArgs()[0];\n        ServletUtils.startTime(httpServletRequest);\n    }\n\n    @Override\n    public void doAfter(MethodInfo methodInfo, Context context) {\n        HttpServletRequest httpServletRequest = (HttpServletRequest) methodInfo.getArgs()[0];\n        final long start = ServletUtils.startTime(httpServletRequest);\n        if (ServletUtils.markProcessed(httpServletRequest, getAfterMark())) {\n            return;\n        }\n        String httpRoute = ServletUtils.getHttpRouteAttributeFromRequest(httpServletRequest);\n        final String key = httpServletRequest.getMethod() + \" \" + httpRoute;\n        HttpServletResponse httpServletResponse = (HttpServletResponse) methodInfo.getArgs()[1];\n        if (methodInfo.getThrowable() != null) {\n            internalAfter(methodInfo.getThrowable(), key, httpServletRequest, httpServletResponse, start);\n        } else if (httpServletRequest.isAsyncStarted()) {\n            httpServletRequest.getAsyncContext().addListener(new InternalAsyncListener(\n                    asyncEvent -> {\n                        HttpServletResponse suppliedResponse = (HttpServletResponse) asyncEvent.getSuppliedResponse();\n                        internalAfter(asyncEvent.getThrowable(), key, httpServletRequest, suppliedResponse, start);\n                    }\n\n                )\n            );\n        } else {\n            internalAfter(null, key, httpServletRequest, httpServletResponse, start);\n        }\n    }\n\n    abstract String getAfterMark();\n\n    abstract void internalAfter(Throwable throwable, String key, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, long start);\n}\n"
  },
  {
    "path": "plugins/httpservlet/src/main/java/com/megaease/easeagent/plugin/httpservlet/interceptor/DoFilterForwardedInterceptor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.httpservlet.interceptor;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Cleaner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.httpservlet.ForwardedPlugin;\nimport com.megaease.easeagent.plugin.httpservlet.advice.DoFilterPoints;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.interceptor.NonReentrantInterceptor;\nimport com.megaease.easeagent.plugin.tools.trace.HttpRequest;\n\nimport javax.servlet.http.HttpServletRequest;\n\n@AdviceTo(value = DoFilterPoints.class, qualifier = \"default\", plugin = ForwardedPlugin.class)\npublic class DoFilterForwardedInterceptor implements NonReentrantInterceptor {\n    private static final Object FORWARDED_KEY = new Object();\n\n    @Override\n    public void doBefore(MethodInfo methodInfo, Context context) {\n        HttpServletRequest httpServletRequest = (HttpServletRequest) methodInfo.getArgs()[0];\n        HttpRequest httpRequest = new HttpServerRequest(httpServletRequest);\n        Cleaner cleaner = context.importForwardedHeaders(httpRequest);\n        context.put(FORWARDED_KEY, cleaner);\n    }\n\n    @Override\n    public void doAfter(MethodInfo methodInfo, Context context) {\n        Cleaner cleaner = context.remove(FORWARDED_KEY);\n        if (cleaner != null) {\n            cleaner.close();\n        }\n    }\n\n    @Override\n    public String getType() {\n        return ConfigConst.PluginID.FORWARDED;\n    }\n\n    @Override\n    public int order() {\n        return Order.FORWARDED.getOrder();\n    }\n}\n\n"
  },
  {
    "path": "plugins/httpservlet/src/main/java/com/megaease/easeagent/plugin/httpservlet/interceptor/DoFilterMetricInterceptor.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.httpservlet.interceptor;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.metric.ServiceMetricRegistry;\nimport com.megaease.easeagent.plugin.api.metric.name.Tags;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.httpservlet.HttpServletPlugin;\nimport com.megaease.easeagent.plugin.httpservlet.advice.DoFilterPoints;\nimport com.megaease.easeagent.plugin.tools.metrics.ServerMetric;\n\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\n\n@AdviceTo(value = DoFilterPoints.class, qualifier = \"default\", plugin = HttpServletPlugin.class)\npublic class DoFilterMetricInterceptor extends BaseServletInterceptor {\n    private static final String AFTER_MARK = DoFilterMetricInterceptor.class.getName() + \"$AfterMark\";\n    private static volatile ServerMetric SERVER_METRIC = null;\n\n    @Override\n    public void init(IPluginConfig config, String className, String methodName, String methodDescriptor) {\n        SERVER_METRIC = ServiceMetricRegistry.getOrCreate(config, new Tags(\"application\", \"http-request\", \"url\"), ServerMetric.SERVICE_METRIC_SUPPLIER);\n    }\n\n    @Override\n    String getAfterMark() {\n        return AFTER_MARK;\n    }\n\n    @Override\n    public void internalAfter(Throwable throwable, String key, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, long start) {\n        long end = System.currentTimeMillis();\n        SERVER_METRIC.collectMetric(key, httpServletResponse.getStatus(), throwable, start, end);\n    }\n\n    @Override\n    public String getType() {\n        return Order.METRIC.getName();\n    }\n\n    @Override\n    public int order() {\n        return Order.METRIC.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/httpservlet/src/main/java/com/megaease/easeagent/plugin/httpservlet/interceptor/DoFilterTraceInterceptor.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.httpservlet.interceptor;\n\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.RequestContext;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.httpservlet.HttpServletPlugin;\nimport com.megaease.easeagent.plugin.httpservlet.advice.DoFilterPoints;\nimport com.megaease.easeagent.plugin.httpservlet.utils.ServletUtils;\nimport com.megaease.easeagent.plugin.interceptor.NonReentrantInterceptor;\nimport com.megaease.easeagent.plugin.tools.trace.HttpRequest;\nimport com.megaease.easeagent.plugin.tools.trace.HttpResponse;\nimport com.megaease.easeagent.plugin.tools.trace.HttpUtils;\nimport com.megaease.easeagent.plugin.tools.trace.TraceConst;\n\nimport javax.servlet.AsyncEvent;\nimport javax.servlet.AsyncListener;\nimport javax.servlet.ServletRequest;\nimport javax.servlet.UnavailableException;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\n@AdviceTo(value = DoFilterPoints.class, plugin = HttpServletPlugin.class)\npublic class DoFilterTraceInterceptor implements NonReentrantInterceptor {\n    private static final String AFTER_MARK = DoFilterTraceInterceptor.class.getName() + \"$AfterMark\";\n    private static final String ERROR_KEY = \"error\";\n\n    @Override\n    public void doBefore(MethodInfo methodInfo, Context context) {\n        HttpServletRequest httpServletRequest = (HttpServletRequest) methodInfo.getArgs()[0];\n        RequestContext requestContext = (RequestContext) httpServletRequest.getAttribute(ServletUtils.PROGRESS_CONTEXT);\n        if (requestContext != null) {\n            return;\n        }\n        HttpRequest httpRequest = new HttpServerRequest(httpServletRequest);\n        requestContext = context.serverReceive(httpRequest);\n        httpServletRequest.setAttribute(ServletUtils.PROGRESS_CONTEXT, requestContext);\n        HttpUtils.handleReceive(requestContext.span().start(), httpRequest);\n    }\n\n    @Override\n    public void doAfter(MethodInfo methodInfo, Context context) {\n        HttpServletRequest httpServletRequest = (HttpServletRequest) methodInfo.getArgs()[0];\n        if (ServletUtils.markProcessed(httpServletRequest, AFTER_MARK)) {\n            return;\n        }\n        HttpServletResponse httpServletResponse = (HttpServletResponse) methodInfo.getArgs()[1];\n        RequestContext requestContext = (RequestContext) httpServletRequest.getAttribute(ServletUtils.PROGRESS_CONTEXT);\n        try {\n            Span span = requestContext.span();\n            if (!httpServletRequest.isAsyncStarted()) {\n                span.tag(TraceConst.HTTP_TAG_ROUTE, ServletUtils.getHttpRouteAttributeFromRequest(httpServletRequest));\n                HttpUtils.finish(span, new Response(methodInfo.getThrowable(), httpServletRequest, httpServletResponse));\n            } else if (methodInfo.getThrowable() != null) {\n                span.error(methodInfo.getThrowable());\n                span.finish();\n            } else {\n                httpServletRequest.getAsyncContext().addListener(new TracingAsyncListener(requestContext), httpServletRequest, httpServletResponse);\n            }\n        } finally {\n            requestContext.scope().close();\n        }\n    }\n\n    @Override\n    public int order() {\n        return Order.TRACING.getOrder();\n    }\n\n\n    public static class Response implements HttpResponse {\n        private final Throwable caught;\n        private final HttpServletRequest httpServletRequest;\n        private final HttpServletResponse httpServletResponse;\n\n        public Response(Throwable caught, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {\n            this.caught = caught;\n            this.httpServletRequest = httpServletRequest;\n            this.httpServletResponse = httpServletResponse;\n        }\n\n        @Override\n        public String method() {\n            return httpServletRequest.getMethod();\n        }\n\n        @Override\n        public String route() {\n            Object maybeRoute = httpServletRequest.getAttribute(TraceConst.HTTP_ATTRIBUTE_ROUTE);\n            return maybeRoute instanceof String ? (String) maybeRoute : null;\n        }\n\n        @Override\n        public int statusCode() {\n            if (httpServletResponse == null) {\n                return 0;\n            }\n            int result = httpServletResponse.getStatus();\n            if (caught != null && result == 200) {\n                if (caught instanceof UnavailableException) {\n                    return ((UnavailableException) caught).isPermanent() ? 404 : 503;\n                } else {\n                    return 500;\n                }\n            } else {\n                return result;\n            }\n        }\n\n        @Override\n        public Throwable maybeError() {\n            if (caught != null) {\n                return caught;\n            }\n            Object maybeError = httpServletRequest.getAttribute(ERROR_KEY);\n            if (maybeError instanceof Throwable) {\n                return (Throwable) maybeError;\n            } else {\n                maybeError = httpServletRequest.getAttribute(\"javax.servlet.error.exception\");\n                return maybeError instanceof Throwable ? (Throwable) maybeError : null;\n            }\n        }\n\n        @Override\n        public String header(String name) {\n            return httpServletResponse.getHeader(name);\n        }\n    }\n\n    public static final class TracingAsyncListener implements AsyncListener {\n        final RequestContext requestContext;\n        final AtomicBoolean sendHandled = new AtomicBoolean();\n\n        TracingAsyncListener(RequestContext requestContext) {\n            this.requestContext = requestContext;\n        }\n\n        public void onComplete(AsyncEvent e) {\n            HttpServletRequest req = (HttpServletRequest) e.getSuppliedRequest();\n            if (sendHandled.compareAndSet(false, true)) {\n                HttpServletResponse res = (HttpServletResponse) e.getSuppliedResponse();\n                Response response = new Response(e.getThrowable(), req, res);\n                HttpUtils.save(requestContext.span(), response);\n                requestContext.finish(response);\n            }\n\n        }\n\n        public void onTimeout(AsyncEvent e) {\n            onError(e);\n        }\n\n        public void onError(AsyncEvent e) {\n            ServletRequest request = e.getSuppliedRequest();\n            if (request.getAttribute(ERROR_KEY) == null) {\n                request.setAttribute(ERROR_KEY, e.getThrowable());\n            }\n        }\n\n        public void onStartAsync(AsyncEvent e) {\n            javax.servlet.AsyncContext eventAsyncContext = e.getAsyncContext();\n            if (eventAsyncContext != null) {\n                eventAsyncContext.addListener(this, e.getSuppliedRequest(), e.getSuppliedResponse());\n            }\n        }\n\n        public String toString() {\n            return \"TracingAsyncListener{\" + this.requestContext + \"}\";\n        }\n    }\n}\n\n"
  },
  {
    "path": "plugins/httpservlet/src/main/java/com/megaease/easeagent/plugin/httpservlet/interceptor/HttpServerRequest.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.httpservlet.interceptor;\n\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.tools.trace.HttpRequest;\nimport com.megaease.easeagent.plugin.tools.trace.TraceConst;\n\nimport javax.servlet.http.HttpServletRequest;\n\npublic class HttpServerRequest implements HttpRequest {\n    protected final HttpServletRequest delegate;\n\n    public HttpServerRequest(HttpServletRequest httpServletRequest) {\n        this.delegate = httpServletRequest;\n    }\n\n    @Override\n    public Span.Kind kind() {\n        return Span.Kind.SERVER;\n    }\n\n    @Override\n    public String method() {\n        return delegate.getMethod();\n    }\n\n    @Override\n    public String path() {\n        return delegate.getRequestURI();\n    }\n\n    @Override\n    public String route() {\n        Object maybeRoute = this.delegate.getAttribute(TraceConst.HTTP_ATTRIBUTE_ROUTE);\n        return maybeRoute instanceof String ? (String) maybeRoute : null;\n    }\n\n    @Override\n    public String getRemoteAddr() {\n        return this.delegate.getRemoteAddr();\n    }\n\n    @Override\n    public int getRemotePort() {\n        return this.delegate.getRemotePort();\n    }\n\n    @Override\n    public String header(String name) {\n        return this.delegate.getHeader(name);\n    }\n\n    @Override\n    public boolean cacheScope() {\n        return false;\n    }\n\n    @Override\n    public void setHeader(String name, String value) {\n//            this.delegate.setAttribute(name, value);\n    }\n}\n\n"
  },
  {
    "path": "plugins/httpservlet/src/main/java/com/megaease/easeagent/plugin/httpservlet/interceptor/ServletAccessLogServerInfo.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.httpservlet.interceptor;\n\nimport com.megaease.easeagent.plugin.httpservlet.utils.ServletUtils;\nimport com.megaease.easeagent.plugin.tools.metrics.AccessLogServerInfo;\nimport com.megaease.easeagent.plugin.utils.common.StringUtils;\n\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport java.util.Enumeration;\nimport java.util.HashMap;\nimport java.util.Map;\n\npublic class ServletAccessLogServerInfo implements AccessLogServerInfo {\n\n    private HttpServletRequest request;\n    private HttpServletResponse response;\n\n    public void load(HttpServletRequest request, HttpServletResponse response) {\n        this.request = request;\n        this.response = response;\n    }\n\n    @Override\n    public String getMethod() {\n        return request.getMethod();\n    }\n\n    @Override\n    public String getHeader(String key) {\n        return request.getHeader(key);\n    }\n\n    @Override\n    public String getRemoteAddr() {\n        return request.getRemoteAddr();\n    }\n\n    @Override\n    public String getRequestURI() {\n        return request.getRequestURI();\n    }\n\n    @Override\n    public int getResponseBufferSize() {\n        return response.getBufferSize();\n    }\n\n    @Override\n    public String getMatchURL() {\n        String matchURL = ServletUtils.matchUrlBySpringWeb(request);\n        if (StringUtils.isEmpty(matchURL)) {\n            return \"\";\n        }\n        return request.getMethod() + \" \" + matchURL;\n    }\n\n    @Override\n    public Map<String, String> findHeaders() {\n        Map<String, String> headers = new HashMap<>();\n        final Enumeration<String> headerNames = request.getHeaderNames();\n        while (headerNames.hasMoreElements()) {\n            final String key = headerNames.nextElement();\n            headers.put(key, request.getHeader(key));\n        }\n        return headers;\n    }\n\n    @Override\n    public Map<String, String> findQueries() {\n        return ServletUtils.getQueries4SingleValue(request);\n    }\n\n    @Override\n    public String getStatusCode() {\n        return String.valueOf(response.getStatus());\n    }\n\n}\n"
  },
  {
    "path": "plugins/httpservlet/src/main/java/com/megaease/easeagent/plugin/httpservlet/interceptor/ServletHttpLogInterceptor.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.httpservlet.interceptor;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.RequestContext;\nimport com.megaease.easeagent.plugin.api.logging.AccessLogInfo;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.httpservlet.AccessPlugin;\nimport com.megaease.easeagent.plugin.httpservlet.advice.DoFilterPoints;\nimport com.megaease.easeagent.plugin.httpservlet.utils.ServletUtils;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.tools.metrics.AccessLogServerInfo;\nimport com.megaease.easeagent.plugin.tools.metrics.HttpLog;\n\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\n\n@AdviceTo(value = DoFilterPoints.class, plugin = AccessPlugin.class)\npublic class ServletHttpLogInterceptor extends BaseServletInterceptor {\n    private static final String BEFORE_MARK = ServletHttpLogInterceptor.class.getName() + \"$BeforeMark\";\n    private static final String AFTER_MARK = ServletHttpLogInterceptor.class.getName() + \"$AfterMark\";\n    private final HttpLog httpLog = new HttpLog();\n\n    public AccessLogServerInfo serverInfo(HttpServletRequest request, HttpServletResponse response) {\n        ServletAccessLogServerInfo serverInfo = (ServletAccessLogServerInfo) request.getAttribute(ServletAccessLogServerInfo.class.getName());\n        if (serverInfo == null) {\n            serverInfo = new ServletAccessLogServerInfo();\n            request.setAttribute(ServletAccessLogServerInfo.class.getName(), serverInfo);\n        }\n        serverInfo.load(request, response);\n        return serverInfo;\n    }\n\n    private Span getSpan(HttpServletRequest httpServletRequest, Context context) {\n        RequestContext requestContext = (RequestContext) httpServletRequest.getAttribute(ServletUtils.PROGRESS_CONTEXT);\n        if (requestContext != null) {\n            return requestContext.span();\n        }\n        return context.currentTracing().currentSpan();\n    }\n\n    private String getSystem() {\n        return EaseAgent.getConfig(\"system\");\n    }\n\n    private String getServiceName() {\n        return EaseAgent.getConfig(\"name\");\n    }\n\n\n    @Override\n    public void doBefore(MethodInfo methodInfo, Context context) {\n        HttpServletRequest httpServletRequest = (HttpServletRequest) methodInfo.getArgs()[0];\n        if (ServletUtils.markProcessed(httpServletRequest, BEFORE_MARK)) {\n            return;\n        }\n        HttpServletResponse httpServletResponse = (HttpServletResponse) methodInfo.getArgs()[1];\n        Long beginTime = ServletUtils.startTime(httpServletRequest);\n        Span span = getSpan(httpServletRequest, context);\n        AccessLogServerInfo serverInfo = this.serverInfo(httpServletRequest, httpServletResponse);\n        AccessLogInfo accessLog = this.httpLog.prepare(getSystem(), getServiceName(), beginTime, span, serverInfo);\n        httpServletRequest.setAttribute(AccessLogInfo.class.getName(), accessLog);\n    }\n\n    @Override\n    String getAfterMark() {\n        return AFTER_MARK;\n    }\n\n    @Override\n    void internalAfter(Throwable throwable, String key, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, long start) {\n        Long beginTime = ServletUtils.startTime(httpServletRequest);\n        AccessLogInfo accessLog = (AccessLogInfo) httpServletRequest.getAttribute(AccessLogInfo.class.getName());\n        AccessLogServerInfo serverInfo = this.serverInfo(httpServletRequest, httpServletResponse);\n        this.httpLog.finish(accessLog, throwable == null, beginTime, serverInfo);\n        EaseAgent.agentReport.report(accessLog);\n    }\n\n    @Override\n    public String getType() {\n        return Order.LOG.getName();\n    }\n\n    @Override\n    public int order() {\n        return Order.LOG.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/httpservlet/src/main/java/com/megaease/easeagent/plugin/httpservlet/utils/InternalAsyncListener.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.httpservlet.utils;\n\nimport javax.servlet.AsyncContext;\nimport javax.servlet.AsyncEvent;\nimport javax.servlet.AsyncListener;\nimport java.util.function.Consumer;\n\npublic class InternalAsyncListener implements AsyncListener {\n\n    private final Consumer<AsyncEvent> consumer;\n\n    public InternalAsyncListener(Consumer<AsyncEvent> consumer) {\n        this.consumer = consumer;\n    }\n\n    @Override\n    public void onComplete(AsyncEvent event) {\n        this.consumer.accept(event);\n    }\n\n    @Override\n    public void onTimeout(AsyncEvent event) {\n    }\n\n    @Override\n    public void onError(AsyncEvent event) {\n    }\n\n    @Override\n    public void onStartAsync(AsyncEvent event) {\n        AsyncContext eventAsyncContext = event.getAsyncContext();\n        if (eventAsyncContext != null) {\n            eventAsyncContext.addListener(this, event.getSuppliedRequest(), event.getSuppliedResponse());\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/httpservlet/src/main/java/com/megaease/easeagent/plugin/httpservlet/utils/ServletUtils.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.httpservlet.utils;\n\nimport com.megaease.easeagent.plugin.api.logging.Logger;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.httpservlet.interceptor.DoFilterTraceInterceptor;\nimport com.megaease.easeagent.plugin.utils.ClassUtils;\nimport lombok.SneakyThrows;\n\nimport javax.servlet.http.HttpServletRequest;\nimport java.net.URLDecoder;\nimport java.util.HashMap;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Map;\n\npublic class ServletUtils {\n    public static final Logger LOGGER = EaseAgent.getLogger(ServletUtils.class);\n    public static final String START_TIME = ServletUtils.class.getName() + \"$StartTime\";\n    public static final String PROGRESS_CONTEXT = DoFilterTraceInterceptor.class.getName() + \".RequestContext\";\n    public static final String HANDLER_MAPPING_CLASS = \"org.springframework.web.servlet.HandlerMapping\";\n    public static final String BEST_MATCHING_PATTERN_ATTRIBUTE_FIELD_NAME = \"BEST_MATCHING_PATTERN_ATTRIBUTE\";\n    public static final String BEST_MATCHING_PATTERN_ATTRIBUTE;\n\n    static {\n        String pattern = null;\n        Object field = ClassUtils.getStaticField(HANDLER_MAPPING_CLASS, BEST_MATCHING_PATTERN_ATTRIBUTE_FIELD_NAME);\n        if (field == null) {\n            if (LOGGER.isDebugEnabled()) {\n                LOGGER.debug(\"class<{}>.{} not found \", HANDLER_MAPPING_CLASS, BEST_MATCHING_PATTERN_ATTRIBUTE_FIELD_NAME);\n            }\n            pattern = \"org.springframework.web.servlet.HandlerMapping.bestMatchingPattern\";\n        } else if (field instanceof String) {\n            pattern = (String) field;\n        } else {\n            if (LOGGER.isDebugEnabled()) {\n                LOGGER.debug(\"class<{}>.{} is not String\", HANDLER_MAPPING_CLASS, BEST_MATCHING_PATTERN_ATTRIBUTE_FIELD_NAME);\n            }\n            pattern = \"org.springframework.web.servlet.HandlerMapping.bestMatchingPattern\";\n        }\n        BEST_MATCHING_PATTERN_ATTRIBUTE = pattern;\n    }\n\n    public static String matchUrlBySpringWeb(HttpServletRequest request) {\n        return (String) request.getAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE);\n    }\n\n    public static String getHttpRouteAttributeFromRequest(HttpServletRequest request) {\n        Object httpRoute = request.getAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE);\n        return httpRoute != null ? httpRoute.toString() : \"\";\n    }\n\n    public static boolean markProcessed(HttpServletRequest request, String mark) {\n        if (request.getAttribute(mark) != null) {\n            return true;\n        }\n        request.setAttribute(mark, \"m\");\n        return false;\n    }\n\n    public static long startTime(HttpServletRequest httpServletRequest) {\n        Object startObj = httpServletRequest.getAttribute(START_TIME);\n        Long start = null;\n        if (startObj == null) {\n            start = System.currentTimeMillis();\n            httpServletRequest.setAttribute(START_TIME, start);\n        } else {\n            start = (Long) startObj;\n        }\n        return start;\n    }\n\n\n    @SneakyThrows\n    public static Map<String, List<String>> getQueries(HttpServletRequest httpServletRequest) {\n        Map<String, List<String>> map = new HashMap<>();\n        String queryString = httpServletRequest.getQueryString();\n        if (queryString == null || queryString.isEmpty()) {\n            return map;\n        }\n        String[] pairs = queryString.split(\"&\");\n        for (String pair : pairs) {\n            int idx = pair.indexOf(\"=\");\n            String key = idx > 0 ? URLDecoder.decode(pair.substring(0, idx), \"UTF-8\") : pair;\n            if (!map.containsKey(key)) {\n                map.put(key, new LinkedList<>());\n            }\n            String value = idx > 0 && pair.length() > idx + 1 ? URLDecoder.decode(pair.substring(idx + 1), \"UTF-8\") : null;\n            map.get(key).add(value);\n        }\n        return map;\n    }\n\n    public static Map<String, String> getQueries4SingleValue(HttpServletRequest httpServletRequest) {\n        Map<String, List<String>> map = getQueries(httpServletRequest);\n        Map<String, String> singleValueMap = new HashMap<>();\n        map.forEach((key, values) -> {\n            if (values != null && values.size() > 0) {\n                singleValueMap.put(key, values.get(0));\n            }\n        });\n        return singleValueMap;\n    }\n}\n"
  },
  {
    "path": "plugins/httpservlet/src/test/java/com/megaease/easeagent/plugin/httpservlet/interceptor/BaseServletInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.httpservlet.interceptor;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.httpservlet.utils.ServletUtils;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.mock.web.MockAsyncContext;\nimport org.springframework.mock.web.MockHttpServletRequest;\n\nimport javax.servlet.AsyncContext;\nimport javax.servlet.AsyncEvent;\nimport javax.servlet.AsyncListener;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\n\nimport java.io.IOException;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.function.Consumer;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class BaseServletInterceptorTest {\n\n    @Test\n    public void doBefore() {\n        HttpServletRequest httpServletRequest = TestServletUtils.buildMockRequest();\n        HttpServletResponse response = TestServletUtils.buildMockResponse();\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{httpServletRequest, response}).build();\n\n        MockBaseServletInterceptor mockBaseServletInterceptor = new MockBaseServletInterceptor();\n        mockBaseServletInterceptor.doBefore(methodInfo, EaseAgent.getContext());\n        assertNotNull(httpServletRequest.getAttribute(ServletUtils.START_TIME));\n    }\n\n    @Test\n    public void doAfter() {\n        internalAfter(null);\n    }\n\n    @Test\n    public void getAfterMark() {\n        MockBaseServletInterceptor mockBaseServletInterceptor = new MockBaseServletInterceptor();\n        assertEquals(MockBaseServletInterceptor.BEFORE_MARK, mockBaseServletInterceptor.getAfterMark());\n    }\n\n    @Test\n    public void internalAfter() {\n        internalAfter(new RuntimeException(\"test error\"));\n    }\n\n    private void internalAfter(Throwable error) {\n        HttpServletRequest httpServletRequest = TestServletUtils.buildMockRequest();\n        HttpServletResponse response = TestServletUtils.buildMockResponse();\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{httpServletRequest, response}).throwable(error).build();\n\n        MockBaseServletInterceptor mockBaseServletInterceptor = new MockBaseServletInterceptor();\n        mockBaseServletInterceptor.throwable = error;\n        mockBaseServletInterceptor.key = TestConst.METHOD + \" \" + TestConst.ROUTE;\n        mockBaseServletInterceptor.httpServletRequest = httpServletRequest;\n        mockBaseServletInterceptor.httpServletResponse = response;\n        mockBaseServletInterceptor.doBefore(methodInfo, EaseAgent.getContext());\n        mockBaseServletInterceptor.start = (long) httpServletRequest.getAttribute(ServletUtils.START_TIME);\n        mockBaseServletInterceptor.doAfter(methodInfo, EaseAgent.getContext());\n        assertTrue(mockBaseServletInterceptor.isRan.get());\n    }\n\n\n    @Test\n    public void testAsync() throws InterruptedException {\n        runAsyncOne(AsyncContext::complete, null);\n\n        String errorInfo = \"timeout eror\";\n        RuntimeException error = new RuntimeException(errorInfo);\n        runAsyncOne(asyncContext -> {\n            AsyncEvent asyncEvent = new AsyncEvent(asyncContext, error);\n            MockAsyncContext mockAsyncContext = (MockAsyncContext) asyncContext;\n            for (AsyncListener asyncListener : mockAsyncContext.getListeners()) {\n                try {\n                    asyncListener.onComplete(asyncEvent);\n                } catch (IOException e) {\n                    throw new RuntimeException(\"error\");\n                }\n            }\n        }, error);\n    }\n\n    private void runAsyncOne(Consumer<AsyncContext> asyncContextConsumer, Throwable error) throws InterruptedException {\n        MockHttpServletRequest httpServletRequest = TestServletUtils.buildMockRequest();\n        HttpServletResponse response = TestServletUtils.buildMockResponse();\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{httpServletRequest, response}).build();\n        MockBaseServletInterceptor mockBaseServletInterceptor = new MockBaseServletInterceptor();\n        mockBaseServletInterceptor.key = TestConst.METHOD + \" \" + TestConst.ROUTE;\n        mockBaseServletInterceptor.httpServletRequest = httpServletRequest;\n        mockBaseServletInterceptor.httpServletResponse = response;\n        mockBaseServletInterceptor.throwable = error;\n\n        mockBaseServletInterceptor.doBefore(methodInfo, EaseAgent.getContext());\n        mockBaseServletInterceptor.start = (long) httpServletRequest.getAttribute(ServletUtils.START_TIME);\n        httpServletRequest.setAsyncSupported(true);\n        final AsyncContext asyncContext = httpServletRequest.startAsync(httpServletRequest, response);\n        mockBaseServletInterceptor.doAfter(methodInfo, EaseAgent.getContext());\n\n        Thread thread = new Thread(() -> asyncContextConsumer.accept(asyncContext));\n        thread.start();\n        thread.join();\n        assertTrue(mockBaseServletInterceptor.isRan.get());\n    }\n\n    private static class MockBaseServletInterceptor extends BaseServletInterceptor {\n        private static final String BEFORE_MARK = MockBaseServletInterceptor.class.getName() + \"$BeforeMark\";\n        private AtomicBoolean isRan = new AtomicBoolean(false);\n        private Throwable throwable;\n        private String key;\n        private HttpServletRequest httpServletRequest;\n        private HttpServletResponse httpServletResponse;\n        private long start;\n\n        @Override\n        public int order() {\n            return Order.HIGH.getOrder();\n        }\n\n\n        @Override\n        String getAfterMark() {\n            return BEFORE_MARK;\n        }\n\n        @Override\n        void internalAfter(Throwable throwable, String key, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, long start) {\n            isRan.set(true);\n            if (this.throwable == null) {\n                assertNull(throwable);\n            } else {\n                assertSame(this.throwable, throwable);\n            }\n            assertEquals(this.key, key);\n            assertSame(this.httpServletRequest, httpServletRequest);\n            assertSame(this.httpServletResponse, httpServletResponse);\n            assertEquals(this.start, start);\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/httpservlet/src/test/java/com/megaease/easeagent/plugin/httpservlet/interceptor/DoFilterForwardedInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.httpservlet.interceptor;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport javax.servlet.http.HttpServletRequest;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNull;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class DoFilterForwardedInterceptorTest {\n\n    public void testForwarded() {\n        HttpServletRequest httpServletRequest = TestServletUtils.buildMockRequest();\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{httpServletRequest}).build();\n        DoFilterForwardedInterceptor doFilterForwardedInterceptor = new DoFilterForwardedInterceptor();\n        doFilterForwardedInterceptor.doBefore(methodInfo, EaseAgent.getContext());\n        assertEquals(TestConst.FORWARDED_VALUE, EaseAgent.getContext().get(TestConst.FORWARDED_NAME));\n        doFilterForwardedInterceptor.doAfter(methodInfo, EaseAgent.getContext());\n        assertNull(EaseAgent.getContext().get(TestConst.FORWARDED_NAME));\n    }\n\n    @Test\n    public void doBefore() {\n        testForwarded();\n    }\n\n    @Test\n    public void doAfter() {\n        testForwarded();\n    }\n\n    @Test\n    public void getType() {\n        DoFilterForwardedInterceptor doFilterForwardedInterceptor = new DoFilterForwardedInterceptor();\n        assertEquals(ConfigConst.PluginID.FORWARDED, doFilterForwardedInterceptor.getType());\n    }\n}\n"
  },
  {
    "path": "plugins/httpservlet/src/test/java/com/megaease/easeagent/plugin/httpservlet/interceptor/DoFilterMetricInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.httpservlet.interceptor;\n\nimport com.megaease.easeagent.mock.plugin.api.MockEaseAgent;\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.mock.plugin.api.utils.TagVerifier;\nimport com.megaease.easeagent.mock.report.impl.LastJsonReporter;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.metric.ServiceMetric;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\nimport com.megaease.easeagent.plugin.httpservlet.HttpServletPlugin;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.mock.web.MockHttpServletRequest;\n\nimport javax.servlet.http.HttpServletResponse;\nimport java.util.Map;\nimport java.util.Objects;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotNull;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class DoFilterMetricInterceptorTest {\n\n    @Test\n    public void init() {\n        DoFilterMetricInterceptor doFilterMetricInterceptor = new DoFilterMetricInterceptor();\n        HttpServletPlugin httpServletPlugin = new HttpServletPlugin();\n        IPluginConfig iPluginConfig = EaseAgent.getConfig(httpServletPlugin.getDomain(), httpServletPlugin.getNamespace(), doFilterMetricInterceptor.getType());\n        doFilterMetricInterceptor.init(iPluginConfig, \"\", \"\", \"\");\n        assertNotNull(AgentFieldReflectAccessor.getFieldValue(doFilterMetricInterceptor, \"SERVER_METRIC\"));\n\n    }\n\n    @Test\n    public void getAfterMark() {\n        DoFilterMetricInterceptor doFilterMetricInterceptor = new DoFilterMetricInterceptor();\n        assertNotNull(doFilterMetricInterceptor.getAfterMark());\n\n    }\n\n    public Map<String, Object> getMetric(LastJsonReporter lastJsonReporter) throws InterruptedException {\n        return lastJsonReporter.flushAndOnlyOne();\n    }\n\n    @Test\n    public void internalAfter() throws InterruptedException {\n        MockHttpServletRequest httpServletRequest = TestServletUtils.buildMockRequest();\n        HttpServletResponse response = TestServletUtils.buildMockResponse();\n\n        DoFilterMetricInterceptor doFilterMetricInterceptor = new DoFilterMetricInterceptor();\n        HttpServletPlugin httpServletPlugin = new HttpServletPlugin();\n        IPluginConfig iPluginConfig = EaseAgent.getConfig(httpServletPlugin.getDomain(), httpServletPlugin.getNamespace(), doFilterMetricInterceptor.getType());\n        int interval = iPluginConfig.getInt(\"interval\");\n        assertEquals(1, interval);\n        doFilterMetricInterceptor.init(iPluginConfig, \"\", \"\", \"\");\n\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{httpServletRequest, response}).build();\n        doFilterMetricInterceptor.doBefore(methodInfo, EaseAgent.getContext());\n        TagVerifier tagVerifier = new TagVerifier()\n            .add(\"category\", \"application\")\n            .add(\"type\", \"http-request\")\n            .add(\"url\", TestConst.METHOD + \" \" + TestConst.ROUTE);\n        LastJsonReporter lastJsonReporter = MockEaseAgent.lastMetricJsonReporter(tagVerifier::verifyAnd);\n\n        doFilterMetricInterceptor.doAfter(methodInfo, EaseAgent.getContext());\n        Map<String, Object> metric = getMetric(lastJsonReporter);\n\n        assertEquals(1, (int) metric.get(\"cnt\"));\n        assertEquals(0, (int) metric.get(\"errcnt\"));\n\n        MockEaseAgent.cleanMetric(Objects.requireNonNull(AgentFieldReflectAccessor.<ServiceMetric>getFieldValue(doFilterMetricInterceptor, \"SERVER_METRIC\")));\n        lastJsonReporter.clean();\n        httpServletRequest = TestServletUtils.buildMockRequest();\n        response = TestServletUtils.buildMockResponse();\n        methodInfo = MethodInfo.builder().args(new Object[]{httpServletRequest, response}).throwable(new RuntimeException(\"test error\")).build();\n        doFilterMetricInterceptor.doBefore(methodInfo, EaseAgent.getContext());\n        doFilterMetricInterceptor.doAfter(methodInfo, EaseAgent.getContext());\n\n        metric = getMetric(lastJsonReporter);\n        assertEquals(1, (int) metric.get(\"cnt\"));\n        assertEquals(1, (int) metric.get(\"errcnt\"));\n    }\n\n    @Test\n    public void getType() {\n        DoFilterMetricInterceptor doFilterMetricInterceptor = new DoFilterMetricInterceptor();\n        assertEquals(Order.METRIC.getName(), doFilterMetricInterceptor.getType());\n\n    }\n}\n"
  },
  {
    "path": "plugins/httpservlet/src/test/java/com/megaease/easeagent/plugin/httpservlet/interceptor/DoFilterTraceInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.httpservlet.interceptor;\n\nimport com.megaease.easeagent.mock.plugin.api.MockEaseAgent;\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.RequestContext;\nimport com.megaease.easeagent.plugin.api.trace.Request;\nimport com.megaease.easeagent.plugin.api.trace.Setter;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.httpservlet.utils.ServletUtils;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport com.megaease.easeagent.plugin.tools.trace.TraceConst;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.mock.web.MockAsyncContext;\nimport org.springframework.mock.web.MockHttpServletRequest;\n\nimport javax.servlet.AsyncContext;\nimport javax.servlet.AsyncEvent;\nimport javax.servlet.AsyncListener;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.util.function.Consumer;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class DoFilterTraceInterceptorTest {\n\n    private void testTrace() {\n        HttpServletRequest httpServletRequest = TestServletUtils.buildMockRequest();\n        HttpServletResponse response = TestServletUtils.buildMockResponse();\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{httpServletRequest, response}).build();\n\n        DoFilterTraceInterceptor doFilterTraceInterceptor = new DoFilterTraceInterceptor();\n        doFilterTraceInterceptor.doBefore(methodInfo, EaseAgent.getContext());\n        Object o = httpServletRequest.getAttribute(ServletUtils.PROGRESS_CONTEXT);\n        assertNotNull(o);\n        assertTrue(o instanceof RequestContext);\n        doFilterTraceInterceptor.doBefore(methodInfo, EaseAgent.getContext());\n        Object o2 = httpServletRequest.getAttribute(ServletUtils.PROGRESS_CONTEXT);\n        assertNotNull(o2);\n        assertSame(o, o2);\n        doFilterTraceInterceptor.doAfter(methodInfo, EaseAgent.getContext());\n        ReportSpan mockSpan = MockEaseAgent.getLastSpan();\n        assertNotNull(mockSpan);\n        assertNull(mockSpan.parentId());\n        checkServerSpan(mockSpan);\n\n        MockEaseAgent.cleanLastSpan();\n        doFilterTraceInterceptor.doAfter(methodInfo, EaseAgent.getContext());\n        assertNull(MockEaseAgent.getLastSpan());\n    }\n\n\n    private void checkServerSpan(ReportSpan mockSpan) {\n        assertEquals(Span.Kind.SERVER.name(), mockSpan.kind());\n        assertEquals(TestConst.ROUTE, mockSpan.tag(TraceConst.HTTP_ATTRIBUTE_ROUTE));\n        assertEquals(TestConst.METHOD, mockSpan.tag(\"http.method\"));\n        assertEquals(TestConst.URL, mockSpan.tag(\"http.path\"));\n        assertEquals(TestConst.REMOTE_ADDR, mockSpan.remoteEndpoint().ipv4());\n        assertEquals(TestConst.REMOTE_PORT, mockSpan.remoteEndpoint().port());\n    }\n\n    @Test\n    public void testErrorTracing() {\n        HttpServletRequest httpServletRequest = TestServletUtils.buildMockRequest();\n        HttpServletResponse response = TestServletUtils.buildMockResponse();\n        String errorInfo = \"test error\";\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{httpServletRequest, response}).throwable(new RuntimeException(errorInfo)).build();\n        DoFilterTraceInterceptor doFilterTraceInterceptor = new DoFilterTraceInterceptor();\n        doFilterTraceInterceptor.doBefore(methodInfo, EaseAgent.getContext());\n        doFilterTraceInterceptor.doAfter(methodInfo, EaseAgent.getContext());\n        ReportSpan mockSpan = MockEaseAgent.getLastSpan();\n        assertNotNull(mockSpan);\n        assertEquals(Span.Kind.SERVER.name(), mockSpan.kind());\n        assertNull(mockSpan.parentId());\n        checkServerSpan(mockSpan);\n        assertEquals(errorInfo, mockSpan.tag(\"error\"));\n    }\n\n    @Test\n    public void testHasPassHeader() {\n        MockHttpServletRequest httpServletRequest = TestServletUtils.buildMockRequest();\n        Context context = EaseAgent.getContext();\n        RequestContext requestContext = context.clientRequest(new MockClientRequest(httpServletRequest::addHeader));\n        requestContext.scope().close();\n        assertFalse(context.currentTracing().hasCurrentSpan());\n        requestContext.span().abandon();\n\n        HttpServletResponse response = TestServletUtils.buildMockResponse();\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{httpServletRequest, response}).build();\n        DoFilterTraceInterceptor doFilterTraceInterceptor = new DoFilterTraceInterceptor();\n        doFilterTraceInterceptor.doBefore(methodInfo, EaseAgent.getContext());\n        doFilterTraceInterceptor.doAfter(methodInfo, EaseAgent.getContext());\n        ReportSpan mockSpan = MockEaseAgent.getLastSpan();\n        assertNotNull(mockSpan);\n        assertEquals(requestContext.span().traceIdString(), mockSpan.traceId());\n        assertEquals(requestContext.span().spanIdString(), mockSpan.id());\n        assertEquals(requestContext.span().parentIdString(), mockSpan.parentId());\n        checkServerSpan(mockSpan);\n    }\n\n    @Test\n    public void testAsync() throws InterruptedException {\n        ReportSpan mockSpan = runAsyncOne(AsyncContext::complete);\n        assertNotNull(mockSpan);\n        assertEquals(Span.Kind.SERVER.name(), mockSpan.kind());\n        assertNull(mockSpan.parentId());\n        checkServerSpan(mockSpan);\n\n        String errorInfo = \"timeout eror\";\n        mockSpan = runAsyncOne(asyncContext -> {\n            AsyncEvent asyncEvent = new AsyncEvent(asyncContext, new RuntimeException(errorInfo));\n            MockAsyncContext mockAsyncContext = (MockAsyncContext) asyncContext;\n            for (AsyncListener asyncListener : mockAsyncContext.getListeners()) {\n                try {\n                    asyncListener.onTimeout(asyncEvent);\n                } catch (IOException e) {\n                    throw new RuntimeException(\"error\");\n                }\n            }\n            asyncContext.complete();\n        });\n        assertNotNull(mockSpan);\n        assertEquals(Span.Kind.SERVER.name(), mockSpan.kind());\n        assertNull(mockSpan.parentId());\n        checkServerSpan(mockSpan);\n        assertEquals(errorInfo, mockSpan.tag(\"error\"));\n    }\n\n    public ReportSpan runAsyncOne(Consumer<AsyncContext> asyncContextConsumer) throws InterruptedException {\n        MockHttpServletRequest httpServletRequest = TestServletUtils.buildMockRequest();\n        HttpServletResponse response = TestServletUtils.buildMockResponse();\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{httpServletRequest, response}).build();\n        DoFilterTraceInterceptor doFilterTraceInterceptor = new DoFilterTraceInterceptor();\n        doFilterTraceInterceptor.doBefore(methodInfo, EaseAgent.getContext());\n        httpServletRequest.setAsyncSupported(true);\n        final AsyncContext asyncContext = httpServletRequest.startAsync(httpServletRequest, response);\n        doFilterTraceInterceptor.doAfter(methodInfo, EaseAgent.getContext());\n\n        MockEaseAgent.cleanLastSpan();\n        Thread thread = new Thread(() -> asyncContextConsumer.accept(asyncContext));\n        thread.start();\n        thread.join();\n        return MockEaseAgent.getLastSpan();\n    }\n\n    @Test\n    public void doBefore() {\n        testTrace();\n    }\n\n    @Test\n    public void doAfter() {\n        testTrace();\n    }\n\n    public class MockClientRequest implements Request {\n        private final Setter setter;\n\n        public MockClientRequest(Setter setter) {\n            this.setter = setter;\n        }\n\n        @Override\n        public Span.Kind kind() {\n            return Span.Kind.CLIENT;\n        }\n\n        @Override\n        public String header(String name) {\n            return null;\n        }\n\n        @Override\n        public String name() {\n            return \"test\";\n        }\n\n        @Override\n        public boolean cacheScope() {\n            return false;\n        }\n\n        @Override\n        public void setHeader(String name, String value) {\n            setter.setHeader(name, value);\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/httpservlet/src/test/java/com/megaease/easeagent/plugin/httpservlet/interceptor/HttpServerRequestTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.httpservlet.interceptor;\n\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\npublic class HttpServerRequestTest {\n    public HttpServerRequest build() {\n        return new HttpServerRequest(TestServletUtils.buildMockRequest());\n    }\n\n    @Test\n    public void kind() {\n        assertEquals(Span.Kind.SERVER, build().kind());\n    }\n\n    @Test\n    public void method() {\n        assertEquals(TestConst.METHOD, build().method());\n    }\n\n    @Test\n    public void path() {\n        assertEquals(TestConst.URL, build().path());\n    }\n\n    @Test\n    public void route() {\n        assertEquals(TestConst.ROUTE, build().route());\n    }\n\n    @Test\n    public void getRemoteAddr() {\n        assertEquals(TestConst.REMOTE_ADDR, build().getRemoteAddr());\n    }\n\n    @Test\n    public void getRemotePort() {\n        assertEquals(TestConst.REMOTE_PORT, build().getRemotePort());\n    }\n\n    @Test\n    public void header() {\n        assertEquals(TestConst.FORWARDED_VALUE, build().header(TestConst.FORWARDED_NAME));\n    }\n\n    @Test\n    public void cacheScope() {\n        assertEquals(false, build().cacheScope());\n    }\n}\n"
  },
  {
    "path": "plugins/httpservlet/src/test/java/com/megaease/easeagent/plugin/httpservlet/interceptor/ServletAccessLogInfoServerInfoTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.httpservlet.interceptor;\n\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\nimport org.junit.Test;\nimport org.springframework.mock.web.MockHttpServletRequest;\n\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\n\nimport java.util.Map;\n\nimport static org.junit.Assert.*;\n\npublic class ServletAccessLogInfoServerInfoTest {\n\n    private ServletAccessLogServerInfo loadMock() {\n        ServletAccessLogServerInfo servletAccessLogServerInfo = new ServletAccessLogServerInfo();\n        MockHttpServletRequest httpServletRequest = TestServletUtils.buildMockRequest();\n        HttpServletResponse response = TestServletUtils.buildMockResponse();\n        servletAccessLogServerInfo.load(httpServletRequest, response);\n        return servletAccessLogServerInfo;\n    }\n\n    @Test\n    public void load() {\n        ServletAccessLogServerInfo serverInfo = loadMock();\n        assertNotNull(AgentFieldReflectAccessor.getFieldValue(serverInfo, \"request\"));\n        assertNotNull(AgentFieldReflectAccessor.getFieldValue(serverInfo, \"response\"));\n    }\n\n    @Test\n    public void getMethod() {\n        assertEquals(TestConst.METHOD, loadMock().getMethod());\n    }\n\n    @Test\n    public void getHeader() {\n        assertEquals(TestConst.FORWARDED_VALUE, loadMock().getHeader(TestConst.FORWARDED_NAME));\n    }\n\n    @Test\n    public void getRemoteAddr() {\n        assertEquals(TestConst.REMOTE_ADDR, loadMock().getRemoteAddr());\n    }\n\n    @Test\n    public void getRequestURI() {\n        assertEquals(TestConst.URL, loadMock().getRequestURI());\n    }\n\n    @Test\n    public void getResponseBufferSize() {\n        assertEquals(TestConst.RESPONSE_BUFFER_SIZE, loadMock().getResponseBufferSize());\n    }\n\n    @Test\n    public void getMatchURL() {\n        assertEquals(TestConst.METHOD + \" \" + TestConst.ROUTE, loadMock().getMatchURL());\n    }\n\n    @Test\n    public void findHeaders() {\n        assertEquals(TestConst.FORWARDED_VALUE, loadMock().findHeaders().get(TestConst.FORWARDED_NAME));\n    }\n\n    @Test\n    public void findQueries() {\n        Map<String, String> queries = loadMock().findQueries();\n        assertEquals(\"10\", queries.get(\"q1\"));\n        assertEquals(\"testq\", queries.get(\"q2\"));\n    }\n\n    @Test\n    public void getStatusCode() {\n        assertEquals(\"200\", loadMock().getStatusCode());\n    }\n}\n"
  },
  {
    "path": "plugins/httpservlet/src/test/java/com/megaease/easeagent/plugin/httpservlet/interceptor/ServletHttpLogInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.httpservlet.interceptor;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.megaease.easeagent.mock.plugin.api.MockEaseAgent;\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.mock.report.MockReport;\nimport com.megaease.easeagent.mock.report.impl.LastJsonReporter;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.httpservlet.AccessPlugin;\nimport com.megaease.easeagent.plugin.httpservlet.utils.ServletUtils;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.tools.metrics.AccessLogServerInfo;\nimport com.megaease.easeagent.plugin.api.logging.AccessLogInfo;\nimport com.megaease.easeagent.plugin.utils.common.HostAddress;\nimport com.megaease.easeagent.plugin.utils.common.JsonUtil;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.mock.web.MockHttpServletRequest;\n\nimport javax.servlet.http.HttpServletResponse;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class ServletHttpLogInterceptorTest {\n\n    @Test\n    public void serverInfo() {\n        MockHttpServletRequest httpServletRequest = TestServletUtils.buildMockRequest();\n        HttpServletResponse response = TestServletUtils.buildMockResponse();\n        ServletHttpLogInterceptor servletHttpLogInterceptor = new ServletHttpLogInterceptor();\n        AccessLogServerInfo accessLogServerInfo = servletHttpLogInterceptor.serverInfo(httpServletRequest, response);\n        assertSame(accessLogServerInfo, servletHttpLogInterceptor.serverInfo(httpServletRequest, response));\n    }\n\n    @Test\n    public void doBefore() throws JsonProcessingException {\n        internalAfter();\n    }\n\n    public void verify(AccessLogInfo accessLog, long startTime) {\n        assertEquals(EaseAgent.getConfig(\"system\"), accessLog.getSystem());\n        assertEquals(EaseAgent.getConfig(\"name\"), accessLog.getService());\n        assertEquals(HostAddress.localhost(), accessLog.getHostName());\n        assertEquals(HostAddress.getHostIpv4(), accessLog.getHostIpv4());\n        assertEquals(TestConst.METHOD + \" \" + TestConst.URL, accessLog.getUrl());\n        assertEquals(TestConst.METHOD, accessLog.getMethod());\n        assertEquals(TestConst.FORWARDED_VALUE, accessLog.getHeaders().get(TestConst.FORWARDED_NAME));\n        assertEquals(startTime, accessLog.getBeginTime());\n        assertEquals(\"10\", accessLog.getQueries().get(\"q1\"));\n        assertEquals(\"testq\", accessLog.getQueries().get(\"q2\"));\n        assertEquals(TestConst.FORWARDED_VALUE, accessLog.getClientIP());\n        assertTrue(accessLog.getBeginCpuTime() > 0);\n    }\n\n    @Test\n    public void getAfterMark() {\n        ServletHttpLogInterceptor servletHttpLogInterceptor = new ServletHttpLogInterceptor();\n        assertNotNull(servletHttpLogInterceptor.getAfterMark());\n    }\n\n    private AccessLogInfo getRequestInfo(LastJsonReporter lastJsonReporter) {\n        String result = JsonUtil.toJson(lastJsonReporter.getLastOnlyOne());\n        assertNotNull(result);\n        return JsonUtil.toObject(result, AccessLogInfo.TYPE_REFERENCE);\n    }\n\n    @Test\n    public void internalAfter() throws JsonProcessingException {\n        EaseAgent.agentReport = MockReport.getAgentReport();\n        MockHttpServletRequest httpServletRequest = TestServletUtils.buildMockRequest();\n        HttpServletResponse response = TestServletUtils.buildMockResponse();\n        ServletHttpLogInterceptor servletHttpLogInterceptor = new ServletHttpLogInterceptor();\n        AccessPlugin accessPlugin = new AccessPlugin();\n        IPluginConfig iPluginConfig = EaseAgent.getConfig(accessPlugin.getDomain(), accessPlugin.getNamespace(), servletHttpLogInterceptor.getType());\n        servletHttpLogInterceptor.init(iPluginConfig, \"\", \"\", \"\");\n\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{httpServletRequest, response}).build();\n        servletHttpLogInterceptor.doBefore(methodInfo, EaseAgent.getContext());\n        Object requestInfoO = httpServletRequest.getAttribute(AccessLogInfo.class.getName());\n        assertNotNull(requestInfoO);\n        assertTrue(requestInfoO instanceof AccessLogInfo);\n        AccessLogInfo accessLog = (AccessLogInfo) requestInfoO;\n        long start = (long) httpServletRequest.getAttribute(ServletUtils.START_TIME);\n        verify(accessLog, start);\n        LastJsonReporter lastJsonReporter = MockEaseAgent.lastMetricJsonReporter(stringObjectMap -> {\n            Object type = stringObjectMap.get(\"type\");\n            return type instanceof String && \"access-log\".equals(type);\n        });\n        servletHttpLogInterceptor.doAfter(methodInfo, EaseAgent.getContext());\n        // AccessLogInfo info = getRequestInfo(lastJsonReporter);\n        AccessLogInfo info = MockEaseAgent.getLastLog();\n        verify(info, start);\n        assertEquals(\"200\", info.getStatusCode());\n\n        lastJsonReporter.clean();\n        httpServletRequest = TestServletUtils.buildMockRequest();\n        response = TestServletUtils.buildMockResponse();\n        methodInfo = MethodInfo.builder().args(new Object[]{httpServletRequest, response}).throwable(new RuntimeException(\"test error\")).build();\n        servletHttpLogInterceptor.doBefore(methodInfo, EaseAgent.getContext());\n        servletHttpLogInterceptor.doAfter(methodInfo, EaseAgent.getContext());\n        // info = getRequestInfo(lastJsonReporter);\n        info = MockEaseAgent.getLastLog();\n        start = (long) httpServletRequest.getAttribute(ServletUtils.START_TIME);\n        verify(info, start);\n        assertEquals(\"500\", info.getStatusCode());\n    }\n\n    @Test\n    public void getType() {\n        ServletHttpLogInterceptor servletHttpLogInterceptor = new ServletHttpLogInterceptor();\n        assertEquals(Order.LOG.getName(), servletHttpLogInterceptor.getType());\n    }\n}\n"
  },
  {
    "path": "plugins/httpservlet/src/test/java/com/megaease/easeagent/plugin/httpservlet/interceptor/TestConst.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.httpservlet.interceptor;\n\npublic class TestConst {\n    public static final String FORWARDED_NAME = \"X-Forwarded-For\";\n    public static final String FORWARDED_VALUE = \"testForwarded\";\n    public static final String RESPONSE_TAG_NAME = \"X-EG-Test\";\n    public static final String RESPONSE_TAG_VALUE = \"X-EG-Test-Value\";\n    public static final String METHOD = \"GET\";\n    public static final String URL = \"http://192.168.5.1:8080/test\";\n    public static final String QUERY_STRING = \"q1=10&q2=testq\";\n    public static final String ROUTE = \"/test\";\n    public static final String REMOTE_ADDR = \"192.168.5.1\";\n    public static final int REMOTE_PORT = 8080;\n    public static final int RESPONSE_BUFFER_SIZE = 1024;\n}\n"
  },
  {
    "path": "plugins/httpservlet/src/test/java/com/megaease/easeagent/plugin/httpservlet/interceptor/TestServletUtils.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.httpservlet.interceptor;\n\nimport com.megaease.easeagent.plugin.httpservlet.utils.ServletUtils;\nimport com.megaease.easeagent.plugin.tools.trace.TraceConst;\nimport org.springframework.mock.web.MockHttpServletRequest;\nimport org.springframework.mock.web.MockHttpServletResponse;\n\nimport javax.servlet.http.HttpServletResponse;\n\npublic class TestServletUtils {\n    public static MockHttpServletRequest buildMockRequest() {\n        MockHttpServletRequest httpServletRequest = new MockHttpServletRequest(TestConst.METHOD, TestConst.URL);\n        httpServletRequest.setQueryString(TestConst.QUERY_STRING);\n        httpServletRequest.addHeader(TestConst.FORWARDED_NAME, TestConst.FORWARDED_VALUE);\n        httpServletRequest.setAttribute(TraceConst.HTTP_ATTRIBUTE_ROUTE, TestConst.ROUTE);\n        httpServletRequest.setAttribute(ServletUtils.BEST_MATCHING_PATTERN_ATTRIBUTE, TestConst.ROUTE);\n        httpServletRequest.setRemoteAddr(TestConst.REMOTE_ADDR);\n        httpServletRequest.setRemotePort(TestConst.REMOTE_PORT);\n        return httpServletRequest;\n    }\n\n    public static HttpServletResponse buildMockResponse() {\n        MockHttpServletResponse response = new MockHttpServletResponse();\n        response.setBufferSize(TestConst.RESPONSE_BUFFER_SIZE);\n        response.setStatus(200);\n        return response;\n    }\n}\n"
  },
  {
    "path": "plugins/httpservlet/src/test/resources/mock_agent.properties",
    "content": "name=test-httpservlet-service\nsystem=test-httpservlet-system\neaseagent.progress.forwarded.headers=X-Forwarded-For\nobservability.tracings.tag.response.headers.eg.0=X-EG-Test\n# plugin.observability.httpServlet.metric.enabled=true\nplugin.observability.httpServlet.metric.interval=1\n"
  },
  {
    "path": "plugins/httpurlconnection/README.md",
    "content": "# jdk http url connection \n\n## Points\n\n`java.net.HttpURLConnection:getResponseCode`\n\n### points code version jdk:jdk8 config and default\n\n```properties\nruntime.code.version.points.jdk=jdk8\n```\nwhen not config `runtime.code.version.points.jdk` it is load\n\n## config\n\n### tracing config\n```properties\nplugin.observability.httpURLConnection.tracing.enabled=true\n```\n\n### support forwarded\n```properties\nplugin.integrability.forwarded.forwarded.enabled=true\n```\n\n\n\n"
  },
  {
    "path": "plugins/httpurlconnection/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright (c) 2023, MegaEase\n  ~ All rights reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>plugins</artifactId>\n        <groupId>com.megaease.easeagent</groupId>\n        <version>2.3.0</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>httpurlconnection</artifactId>\n\n\n    <dependencies>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>plugin-api</artifactId>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.google.guava</groupId>\n            <artifactId>guava</artifactId>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>plugin-api-mock</artifactId>\n            <version>${project.version}</version>\n            <scope>test</scope>\n        </dependency>\n    </dependencies>\n</project>\n"
  },
  {
    "path": "plugins/httpurlconnection/src/main/java/com/megaease/easeagent/plugin/httpurlconnection/ForwardedPlugin.java",
    "content": "/*\n * Copyright (c) 2023, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.httpurlconnection;\n\nimport com.megaease.easeagent.plugin.AgentPlugin;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\n\npublic class ForwardedPlugin implements AgentPlugin {\n    @Override\n    public String getNamespace() {\n        return ConfigConst.Namespace.FORWARDED;\n    }\n\n    @Override\n    public String getDomain() {\n        return ConfigConst.INTEGRABILITY;\n    }\n}\n"
  },
  {
    "path": "plugins/httpurlconnection/src/main/java/com/megaease/easeagent/plugin/httpurlconnection/HttpURLConnectionPlugin.java",
    "content": "/*\n * Copyright (c) 2023, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.httpurlconnection;\n\nimport com.megaease.easeagent.plugin.AgentPlugin;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\n\npublic class HttpURLConnectionPlugin implements AgentPlugin {\n    @Override\n    public String getNamespace() {\n        return ConfigConst.Namespace.HTTPURLCONNECTION;\n    }\n\n    @Override\n    public String getDomain() {\n        return ConfigConst.OBSERVABILITY;\n    }\n}\n"
  },
  {
    "path": "plugins/httpurlconnection/src/main/java/com/megaease/easeagent/plugin/httpurlconnection/advice/HttpURLConnectionGetResponseCodeAdvice.java",
    "content": "/*\n * Copyright (c) 2023, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.httpurlconnection.advice;\n\nimport com.megaease.easeagent.plugin.CodeVersion;\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.Set;\n\npublic class HttpURLConnectionGetResponseCodeAdvice implements Points {\n    private final static CodeVersion VERSIONS = CodeVersion.builder()\n        .key(ConfigConst.CodeVersion.KEY_JDK)\n        .add(Points.DEFAULT_VERSION)\n        .add(ConfigConst.CodeVersion.VERSION_JDK8).build();\n\n    @Override\n    public CodeVersion codeVersions() {\n        return VERSIONS;\n    }\n\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return ClassMatcher.builder()\n            .hasSuperClass(\"java.net.HttpURLConnection\")\n            .build();\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.multiBuilder()\n            .match(MethodMatcher.builder().isPublic().named(\"getResponseCode\").build())\n            .build();\n    }\n}\n"
  },
  {
    "path": "plugins/httpurlconnection/src/main/java/com/megaease/easeagent/plugin/httpurlconnection/interceptor/HttpURLConnectionGetResponseCodeForwardedInterceptor.java",
    "content": "/*\n * Copyright (c) 2023, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.httpurlconnection.interceptor;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.httpurlconnection.ForwardedPlugin;\nimport com.megaease.easeagent.plugin.httpurlconnection.HttpURLConnectionPlugin;\nimport com.megaease.easeagent.plugin.httpurlconnection.advice.HttpURLConnectionGetResponseCodeAdvice;\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\n\nimport java.net.HttpURLConnection;\n\n@AdviceTo(value = HttpURLConnectionGetResponseCodeAdvice.class, qualifier = \"default\", plugin = ForwardedPlugin.class)\npublic class HttpURLConnectionGetResponseCodeForwardedInterceptor implements Interceptor {\n\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n        HttpURLConnection httpURLConnection = (HttpURLConnection) methodInfo.getInvoker();\n        context.injectForwardedHeaders(httpURLConnection::setRequestProperty);\n    }\n\n    @Override\n    public String getType() {\n        return ConfigConst.PluginID.FORWARDED;\n    }\n\n    @Override\n    public int order() {\n        return Order.FORWARDED.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/httpurlconnection/src/main/java/com/megaease/easeagent/plugin/httpurlconnection/interceptor/HttpURLConnectionGetResponseCodeInterceptor.java",
    "content": "/*\n * Copyright (c) 2023, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.httpurlconnection.interceptor;\n\nimport com.google.common.collect.ArrayListMultimap;\nimport com.google.common.collect.Multimap;\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.httpurlconnection.HttpURLConnectionPlugin;\nimport com.megaease.easeagent.plugin.httpurlconnection.advice.HttpURLConnectionGetResponseCodeAdvice;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.tools.trace.BaseHttpClientTracingInterceptor;\nimport com.megaease.easeagent.plugin.tools.trace.HttpRequest;\nimport com.megaease.easeagent.plugin.tools.trace.HttpResponse;\nimport lombok.SneakyThrows;\n\nimport java.net.HttpURLConnection;\nimport java.util.List;\nimport java.util.Map;\n\n\n@AdviceTo(value = HttpURLConnectionGetResponseCodeAdvice.class, qualifier = \"default\", plugin = HttpURLConnectionPlugin.class)\npublic class HttpURLConnectionGetResponseCodeInterceptor extends BaseHttpClientTracingInterceptor {\n    @Override\n    public Object getProgressKey() {\n        return HttpURLConnectionGetResponseCodeInterceptor.class;\n    }\n\n    @Override\n    protected HttpRequest getRequest(MethodInfo methodInfo, Context context) {\n        return new InternalRequest((HttpURLConnection) methodInfo.getInvoker());\n    }\n\n\n    @Override\n    protected HttpResponse getResponse(MethodInfo methodInfo, Context context) {\n        return new InternalResponse(methodInfo.getThrowable(), (HttpURLConnection) methodInfo.getInvoker());\n    }\n\n\n    final static class InternalRequest implements HttpRequest {\n\n        private final HttpURLConnection httpURLConn;\n\n        public InternalRequest(HttpURLConnection httpURLConn) {\n            this.httpURLConn = httpURLConn;\n        }\n\n\n        @Override\n        public String method() {\n            return httpURLConn.getRequestMethod();\n        }\n\n        @Override\n        public String path() {\n            return httpURLConn.getURL().toString();\n        }\n\n        @Override\n        public String route() {\n            return null;\n        }\n\n        @Override\n        public String getRemoteAddr() {\n            return null;\n        }\n\n        @Override\n        public int getRemotePort() {\n            return 0;\n        }\n\n        @Override\n        public Span.Kind kind() {\n            return Span.Kind.CLIENT;\n        }\n\n        @Override\n        public String header(String name) {\n            return httpURLConn.getRequestProperty(name);\n        }\n\n        @Override\n        public boolean cacheScope() {\n            return false;\n        }\n\n        @Override\n        public void setHeader(String name, String value) {\n            httpURLConn.setRequestProperty(name, value);\n        }\n\n    }\n\n    final static class InternalResponse implements HttpResponse {\n        private final Throwable caught;\n        private final HttpURLConnection httpURLConn;\n        private final Multimap<String, String> headers;\n\n        public InternalResponse(Throwable caught, HttpURLConnection httpURLConn) {\n            this.caught = caught;\n            this.httpURLConn = httpURLConn;\n            this.headers = getResponseHeaders(httpURLConn);\n        }\n\n        @Override\n        public String method() {\n            return httpURLConn.getRequestMethod();\n        }\n\n        @Override\n        public String route() {\n            return null;\n        }\n\n        @SneakyThrows\n        @Override\n        public int statusCode() {\n            return this.httpURLConn.getResponseCode();\n        }\n\n        @Override\n        public Throwable maybeError() {\n            return caught;\n        }\n\n\n        @Override\n        public String header(String name) {\n            return this.headers.get(name).stream().findFirst().orElse(null);\n\n        }\n\n        private Multimap<String, String> getResponseHeaders(HttpURLConnection uc) {\n            Multimap<String, String> headers = ArrayListMultimap.create();\n            for (Map.Entry<String, List<String>> e : uc.getHeaderFields().entrySet()) {\n                if (e.getKey() != null) {\n                    headers.putAll(e.getKey(), e.getValue());\n                }\n            }\n            return headers;\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/httpurlconnection/src/test/java/com/megaease/easeagent/plugin/httpurlconnection/interceptor/HttpURLConnectionGetResponseCodeForwardedInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2023, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.httpurlconnection.interceptor;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport java.net.HttpURLConnection;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNull;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class HttpURLConnectionGetResponseCodeForwardedInterceptorTest {\n\n    @Test\n    public void before() {\n        Context context = EaseAgent.getContext();\n        HttpURLConnection conn = TestUtils.mockHttpURLConnection();\n        MethodInfo methodInfo = TestUtils.mockMethodInfo(conn);\n        HttpURLConnectionGetResponseCodeForwardedInterceptor httpClientDoExecuteForwardedInterceptor = new HttpURLConnectionGetResponseCodeForwardedInterceptor();\n\n        httpClientDoExecuteForwardedInterceptor.before(methodInfo, context);\n        assertNull(conn.getRequestProperty(TestUtils.FORWARDED_NAME));\n        context.put(TestUtils.FORWARDED_NAME, TestUtils.FORWARDED_VALUE);\n        httpClientDoExecuteForwardedInterceptor.before(methodInfo, context);\n        assertEquals(TestUtils.FORWARDED_VALUE, conn.getRequestProperty(TestUtils.FORWARDED_NAME));\n        context.remove(TestUtils.FORWARDED_NAME);\n    }\n\n    @Test\n    public void getType() {\n        HttpURLConnectionGetResponseCodeForwardedInterceptor httpClientDoExecuteForwardedInterceptor = new HttpURLConnectionGetResponseCodeForwardedInterceptor();\n        assertEquals(ConfigConst.PluginID.FORWARDED, httpClientDoExecuteForwardedInterceptor.getType());\n    }\n}\n"
  },
  {
    "path": "plugins/httpurlconnection/src/test/java/com/megaease/easeagent/plugin/httpurlconnection/interceptor/HttpURLConnectionGetResponseCodeInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2023, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.httpurlconnection.interceptor;\n\nimport com.megaease.easeagent.mock.plugin.api.MockEaseAgent;\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.trace.Scope;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport com.megaease.easeagent.plugin.tools.trace.HttpRequest;\nimport com.megaease.easeagent.plugin.tools.trace.HttpResponse;\nimport lombok.SneakyThrows;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class HttpURLConnectionGetResponseCodeInterceptorTest {\n\n    @SneakyThrows\n    @Test\n    public void before() {\n        Context context = EaseAgent.getContext();\n        MethodInfo methodInfo = TestUtils.mockMethodInfo();\n\n        HttpURLConnectionGetResponseCodeInterceptor httpURLConnectionWriteRequestsInterceptor = new HttpURLConnectionGetResponseCodeInterceptor();\n        MockEaseAgent.cleanLastSpan();\n        httpURLConnectionWriteRequestsInterceptor.before(methodInfo, context);\n        httpURLConnectionWriteRequestsInterceptor.after(methodInfo, context);\n        ReportSpan mockSpan = MockEaseAgent.getLastSpan();\n        assertNotNull(mockSpan);\n        assertEquals(Span.Kind.CLIENT.name(), mockSpan.kind());\n        assertEquals(TestUtils.RESPONSE_TAG_VALUE, mockSpan.tag(TestUtils.RESPONSE_TAG_NAME));\n        assertNull(mockSpan.parentId());\n\n        Span span = context.nextSpan();\n        try (Scope ignored = span.maybeScope()) {\n            httpURLConnectionWriteRequestsInterceptor.doBefore(methodInfo, context);\n            httpURLConnectionWriteRequestsInterceptor.doAfter(methodInfo, context);\n            mockSpan = MockEaseAgent.getLastSpan();\n            assertEquals(span.traceIdString(), mockSpan.traceId());\n            assertEquals(span.spanIdString(), mockSpan.parentId());\n            assertNotNull(mockSpan.id());\n        }\n        span.abandon();\n    }\n\n    @Test\n    public void after() {\n        before();\n    }\n\n\n    @SneakyThrows\n    @Test\n    public void getRequest() {\n        Context context = EaseAgent.getContext();\n        MethodInfo methodInfo = TestUtils.mockMethodInfo();\n\n        HttpURLConnectionGetResponseCodeInterceptor httpClientDoExecuteInterceptor = new HttpURLConnectionGetResponseCodeInterceptor();\n        HttpRequest request = httpClientDoExecuteInterceptor.getRequest(methodInfo, context);\n        assertEquals(Span.Kind.CLIENT, request.kind());\n        assertEquals(\"GET\", request.method());\n    }\n\n    @SneakyThrows\n    @Test\n    public void getResponse() {\n        Context context = EaseAgent.getContext();\n        MethodInfo methodInfo = TestUtils.mockMethodInfo();\n\n        HttpURLConnectionGetResponseCodeInterceptor httpClientDoExecuteInterceptor = new HttpURLConnectionGetResponseCodeInterceptor();\n\n        HttpResponse httpResponse = httpClientDoExecuteInterceptor.getResponse(methodInfo, context);\n        assertEquals(200, httpResponse.statusCode());\n    }\n}\n"
  },
  {
    "path": "plugins/httpurlconnection/src/test/java/com/megaease/easeagent/plugin/httpurlconnection/interceptor/TestUtils.java",
    "content": "/*\n * Copyright (c) 2023, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.httpurlconnection.interceptor;\n\nimport com.google.common.collect.ImmutableMap;\nimport com.google.common.collect.Lists;\nimport com.google.common.net.HttpHeaders;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport lombok.SneakyThrows;\n\nimport java.io.IOException;\nimport java.net.HttpURLConnection;\nimport java.net.URL;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nfinal class TestUtils {\n    public static final String FORWARDED_NAME = \"X-Forwarded-For\";\n    public static final String FORWARDED_VALUE = \"testForwarded\";\n    public static final String RESPONSE_TAG_NAME = \"X-EG-Test\";\n    public static final String RESPONSE_TAG_VALUE = \"X-EG-Test-Value\";\n\n    @SneakyThrows\n    static MethodInfo mockMethodInfo() {\n        HttpURLConnection httpURLConnection = mockHttpURLConnection();\n        return mockMethodInfo(httpURLConnection);\n    }\n\n    static MethodInfo mockMethodInfo(HttpURLConnection httpURLConnection) {\n        MethodInfo methodInfo = MethodInfo.builder()\n            .invoker(httpURLConnection).retValue(httpURLConnection)\n            .build();\n        return methodInfo;\n    }\n\n    @SneakyThrows\n    static HttpURLConnection mockHttpURLConnection() {\n        URL url = new URL(\"http://127.0.0.1:8080\");\n        Map<String, String> responseHeader = ImmutableMap.of(TestUtils.RESPONSE_TAG_NAME, TestUtils.RESPONSE_TAG_VALUE);\n        return getConnection(url, \"GET\", null, responseHeader);\n    }\n\n    static HttpURLConnection getConnection(\n        URL url, String method, Map<String, String> requestHeaders, Map<String, String> responseHeader) throws IOException {\n\n        HttpURLConnection conn = new HttpURLConnection(url) {\n\n            @Override\n            public void connect() throws IOException {\n\n            }\n\n            @Override\n            public void disconnect() {\n\n            }\n\n            @Override\n            public boolean usingProxy() {\n                return false;\n            }\n\n            @Override\n            public int getResponseCode() throws IOException {\n                return 200;\n            }\n\n            @Override\n            public Map<String, List<String>> getHeaderFields() {\n                Map<String, List<String>> fields = new HashMap<>();\n                for (String key : responseHeader.keySet()) {\n                    fields.put(key, Lists.newArrayList(responseHeader.get(key)));\n                }\n                return fields;\n            }\n        };\n        conn.setRequestMethod(method);\n        conn.setDoInput(true);\n        conn.setDoOutput(true);\n        conn.setRequestProperty(HttpHeaders.HOST, url.getHost());\n        if (requestHeaders != null) {\n            for (String key : requestHeaders.keySet()) {\n                conn.setRequestProperty(key, requestHeaders.get(key));\n            }\n        }\n\n        return conn;\n    }\n}\n"
  },
  {
    "path": "plugins/httpurlconnection/src/test/resources/mock_agent.properties",
    "content": "#\n# Copyright (c) 2023, MegaEase\n# All rights reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\neaseagent.progress.forwarded.headers=X-Forwarded-For\nobservability.tracings.tag.response.headers.eg.0=X-EG-Test\n"
  },
  {
    "path": "plugins/httpurlconnection-jdk17/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright (c) 2023, MegaEase\n  ~ All rights reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>plugins</artifactId>\n        <groupId>com.megaease.easeagent</groupId>\n        <version>2.3.0</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>httpurlconnection-jdk17</artifactId>\n\n\n    <dependencies>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>plugin-api</artifactId>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>plugin-api-mock</artifactId>\n            <version>${project.version}</version>\n            <scope>test</scope>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-compiler-plugin</artifactId>\n                <version>3.11.0</version>\n                <configuration>\n                    <source>17</source>\n                    <target>17</target>\n                </configuration>\n            </plugin>\n        </plugins>\n    </build>\n</project>\n"
  },
  {
    "path": "plugins/httpurlconnection-jdk17/src/main/java/com/megaease/easeagent/plugin/httpurlconnection/jdk17/ForwardedPlugin.java",
    "content": "/*\n * Copyright (c) 2023, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.httpurlconnection.jdk17;\n\nimport com.megaease.easeagent.plugin.AgentPlugin;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\n\npublic class ForwardedPlugin implements AgentPlugin {\n    @Override\n    public String getNamespace() {\n        return ConfigConst.Namespace.FORWARDED;\n    }\n\n    @Override\n    public String getDomain() {\n        return ConfigConst.INTEGRABILITY;\n    }\n}\n"
  },
  {
    "path": "plugins/httpurlconnection-jdk17/src/main/java/com/megaease/easeagent/plugin/httpurlconnection/jdk17/HttpURLConnectionPlugin.java",
    "content": "/*\n * Copyright (c) 2023, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.httpurlconnection.jdk17;\n\nimport com.megaease.easeagent.plugin.AgentPlugin;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\n\npublic class HttpURLConnectionPlugin implements AgentPlugin {\n    @Override\n    public String getNamespace() {\n        return ConfigConst.Namespace.HTTPURLCONNECTION;\n    }\n\n    @Override\n    public String getDomain() {\n        return ConfigConst.OBSERVABILITY;\n    }\n}\n"
  },
  {
    "path": "plugins/httpurlconnection-jdk17/src/main/java/com/megaease/easeagent/plugin/httpurlconnection/jdk17/advice/HttpURLConnectionAdvice.java",
    "content": "/*\n * Copyright (c) 2023, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.httpurlconnection.jdk17.advice;\n\nimport com.megaease.easeagent.plugin.CodeVersion;\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.Set;\n\npublic class HttpURLConnectionAdvice implements Points {\n    private final static CodeVersion VERSIONS = CodeVersion.builder()\n        .key(ConfigConst.CodeVersion.KEY_JDK)\n        .add(Points.DEFAULT_VERSION)\n        .add(ConfigConst.CodeVersion.VERSION_JDK17).build();\n\n    private final String typeFieldAccessor = \"connected\";\n\n    @Override\n    public CodeVersion codeVersions() {\n        return VERSIONS;\n    }\n\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return ClassMatcher.builder()\n            .hasSuperClass(\"java.net.HttpURLConnection\")\n            .or().hasInterface(\"java.net.URLConnection\")\n            .build();\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.multiBuilder()\n            .match(MethodMatcher.builder().isPublic()\n                .named(\"getResponseCode\")\n                .or().named(\"connect\")\n                .build())\n            .build();\n    }\n\n    @Override\n    public boolean isAddDynamicField() {\n        return true;\n    }\n\n    @Override\n    public String getTypeFieldAccessor() {\n        return typeFieldAccessor;\n    }\n}\n"
  },
  {
    "path": "plugins/httpurlconnection-jdk17/src/main/java/com/megaease/easeagent/plugin/httpurlconnection/jdk17/interceptor/DynamicFieldUtils.java",
    "content": "package com.megaease.easeagent.plugin.httpurlconnection.jdk17.interceptor;\n\nimport com.megaease.easeagent.plugin.api.logging.Logger;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.field.AgentDynamicFieldAccessor;\nimport com.megaease.easeagent.plugin.field.DynamicFieldAccessor;\n\nimport java.util.HashSet;\nimport java.util.Set;\n\npublic class DynamicFieldUtils {\n    private static final Logger logger = EaseAgent.getLogger(DynamicFieldUtils.class);\n\n    public static Set<String> getOrCreateSet(Object obj) {\n        if (!(obj instanceof DynamicFieldAccessor)) {\n            logger.warn(\"java.net.HttpURLConnection must implements \" + DynamicFieldAccessor.class.getName());\n            return null;\n        }\n        Object fieldValue = AgentDynamicFieldAccessor.getDynamicFieldValue(obj);\n        Set<String> set;\n        if (fieldValue instanceof Set) {\n            set = AgentDynamicFieldAccessor.getDynamicFieldValue(obj);\n        } else {\n            set = new HashSet<>();\n            AgentDynamicFieldAccessor.setDynamicFieldValue(obj, set);\n        }\n        return set;\n    }\n\n    public static boolean enterKey(Object obj, String key) {\n        Set<String> set = getOrCreateSet(obj);\n        if (set == null) {\n            return false;\n        }\n        return set.add(key);\n    }\n}\n"
  },
  {
    "path": "plugins/httpurlconnection-jdk17/src/main/java/com/megaease/easeagent/plugin/httpurlconnection/jdk17/interceptor/HttpURLConnectionForwardedInterceptor.java",
    "content": "/*\n * Copyright (c) 2023, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.httpurlconnection.jdk17.interceptor;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.api.logging.Logger;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.httpurlconnection.jdk17.ForwardedPlugin;\nimport com.megaease.easeagent.plugin.httpurlconnection.jdk17.advice.HttpURLConnectionAdvice;\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\n\nimport java.net.HttpURLConnection;\n\n@AdviceTo(value = HttpURLConnectionAdvice.class, qualifier = \"default\", plugin = ForwardedPlugin.class)\npublic class HttpURLConnectionForwardedInterceptor implements Interceptor {\n    private static final Logger log = EaseAgent.getLogger(HttpURLConnectionForwardedInterceptor.class);\n\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n        Object invoker = methodInfo.getInvoker();\n        if (!DynamicFieldUtils.enterKey(invoker, \"HttpURLConnectionGetResponseCodeForwardedInterceptor.before\")) {\n            return;\n        }\n        if (HttpURLConnectionUtils.isConnected(invoker)) {\n            if (log.isDebugEnabled()) {\n                log.debug(\"the HttpURLConnection Already connected, skip HttpURLConnectionGetResponseCodeForwardedInterceptor\");\n            }\n            return;\n        }\n        HttpURLConnection httpURLConnection = (HttpURLConnection) methodInfo.getInvoker();\n        context.injectForwardedHeaders(httpURLConnection::setRequestProperty);\n    }\n\n    @Override\n    public String getType() {\n        return ConfigConst.PluginID.FORWARDED;\n    }\n\n    @Override\n    public int order() {\n        return Order.FORWARDED.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/httpurlconnection-jdk17/src/main/java/com/megaease/easeagent/plugin/httpurlconnection/jdk17/interceptor/HttpURLConnectionInterceptor.java",
    "content": "/*\n * Copyright (c) 2023, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.httpurlconnection.jdk17.interceptor;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.logging.Logger;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.httpurlconnection.jdk17.HttpURLConnectionPlugin;\nimport com.megaease.easeagent.plugin.httpurlconnection.jdk17.advice.HttpURLConnectionAdvice;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.tools.trace.BaseHttpClientTracingInterceptor;\nimport com.megaease.easeagent.plugin.tools.trace.HttpRequest;\nimport com.megaease.easeagent.plugin.tools.trace.HttpResponse;\nimport lombok.SneakyThrows;\n\nimport java.net.HttpURLConnection;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\n\n@AdviceTo(value = HttpURLConnectionAdvice.class, qualifier = \"default\", plugin = HttpURLConnectionPlugin.class)\npublic class HttpURLConnectionInterceptor extends BaseHttpClientTracingInterceptor {\n    private static final Logger log = EaseAgent.getLogger(HttpURLConnectionInterceptor.class);\n\n    @Override\n    public Object getProgressKey() {\n        return HttpURLConnectionInterceptor.class;\n    }\n\n    @Override\n    public void doBefore(MethodInfo methodInfo, Context context) {\n        Object invoker = methodInfo.getInvoker();\n        if (!DynamicFieldUtils.enterKey(invoker, \"HttpURLConnectionGetResponseCodeInterceptor.before\")) {\n            return;\n        }\n        if (HttpURLConnectionUtils.isConnected(invoker)) {\n            if (log.isDebugEnabled()) {\n                log.debug(\"the HttpURLConnection Already connected, skip HttpURLConnectionGetResponseCodeInterceptor\");\n            }\n            DynamicFieldUtils.enterKey(methodInfo.getInvoker(), \"HttpURLConnectionGetResponseCodeInterceptor.connected\");\n            return;\n        }\n        super.doBefore(methodInfo, context);\n    }\n\n    @Override\n    public void doAfter(MethodInfo methodInfo, Context context) {\n        if (DynamicFieldUtils.enterKey(methodInfo.getInvoker(), \"HttpURLConnectionGetResponseCodeInterceptor.connected\")\n            && DynamicFieldUtils.enterKey(methodInfo.getInvoker(), \"HttpURLConnectionGetResponseCodeInterceptor.after\")) {\n            super.doAfter(methodInfo, context);\n        }\n    }\n\n    @Override\n    protected HttpRequest getRequest(MethodInfo methodInfo, Context context) {\n        return new InternalRequest((HttpURLConnection) methodInfo.getInvoker());\n    }\n\n\n    @Override\n    protected HttpResponse getResponse(MethodInfo methodInfo, Context context) {\n        return new InternalResponse(methodInfo.getThrowable(), (HttpURLConnection) methodInfo.getInvoker());\n    }\n\n\n    final static class InternalRequest implements HttpRequest {\n\n        private final HttpURLConnection httpURLConn;\n\n        public InternalRequest(HttpURLConnection httpURLConn) {\n            this.httpURLConn = httpURLConn;\n        }\n\n\n        @Override\n        public String method() {\n            return httpURLConn.getRequestMethod();\n        }\n\n        @Override\n        public String path() {\n            return httpURLConn.getURL().toString();\n        }\n\n        @Override\n        public String route() {\n            return null;\n        }\n\n        @Override\n        public String getRemoteAddr() {\n            return null;\n        }\n\n        @Override\n        public int getRemotePort() {\n            return 0;\n        }\n\n        @Override\n        public Span.Kind kind() {\n            return Span.Kind.CLIENT;\n        }\n\n        @Override\n        public String header(String name) {\n            return httpURLConn.getRequestProperty(name);\n        }\n\n        @Override\n        public boolean cacheScope() {\n            return false;\n        }\n\n        @Override\n        public void setHeader(String name, String value) {\n            httpURLConn.setRequestProperty(name, value);\n        }\n\n    }\n\n    final static class InternalResponse implements HttpResponse {\n        private final Throwable caught;\n        private final HttpURLConnection httpURLConn;\n        private final Map<String, List<String>> headers;\n\n        public InternalResponse(Throwable caught, HttpURLConnection httpURLConn) {\n            this.caught = caught;\n            this.httpURLConn = httpURLConn;\n            this.headers = getResponseHeaders(httpURLConn);\n        }\n\n        @Override\n        public String method() {\n            return httpURLConn.getRequestMethod();\n        }\n\n        @Override\n        public String route() {\n            return null;\n        }\n\n        @SneakyThrows\n        @Override\n        public int statusCode() {\n            return this.httpURLConn.getResponseCode();\n        }\n\n        @Override\n        public Throwable maybeError() {\n            return caught;\n        }\n\n\n        @Override\n        public String header(String name) {\n            List<String> h = this.headers.get(name);\n            if (h == null) {\n                return null;\n            }\n            return h.stream().findFirst().orElse(null);\n        }\n\n        private Map<String, List<String>> getResponseHeaders(HttpURLConnection uc) {\n            Map<String, List<String>> headers = new HashMap<>();\n            for (Map.Entry<String, List<String>> e : uc.getHeaderFields().entrySet()) {\n                if (e.getKey() != null) {\n                    headers.put(e.getKey(), Collections.unmodifiableList(e.getValue()));\n                }\n            }\n            return headers;\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/httpurlconnection-jdk17/src/main/java/com/megaease/easeagent/plugin/httpurlconnection/jdk17/interceptor/HttpURLConnectionUtils.java",
    "content": "package com.megaease.easeagent.plugin.httpurlconnection.jdk17.interceptor;\n\nimport com.megaease.easeagent.plugin.field.TypeFieldGetter;\n\npublic class HttpURLConnectionUtils {\n    public static boolean isConnected(Object obj) {\n        Boolean connected = TypeFieldGetter.get(obj);\n        return Boolean.TRUE.equals(connected);\n    }\n}\n"
  },
  {
    "path": "plugins/httpurlconnection-jdk17/src/test/java/com/megaease/easeagent/plugin/httpurlconnection/jdk17/interceptor/HttpURLConnectionForwardedInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2023, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.httpurlconnection.jdk17.interceptor;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.field.AgentDynamicFieldAccessor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport java.net.HttpURLConnection;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNull;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class HttpURLConnectionForwardedInterceptorTest {\n\n    @Test\n    public void before() {\n        Context context = EaseAgent.getContext();\n        HttpURLConnection conn = TestUtils.mockHttpURLConnection();\n        MethodInfo methodInfo = TestUtils.mockMethodInfo(conn);\n        HttpURLConnectionForwardedInterceptor httpClientDoExecuteForwardedInterceptor = new HttpURLConnectionForwardedInterceptor();\n\n        httpClientDoExecuteForwardedInterceptor.before(methodInfo, context);\n        assertNull(conn.getRequestProperty(TestUtils.FORWARDED_NAME));\n        context.put(TestUtils.FORWARDED_NAME, TestUtils.FORWARDED_VALUE);\n        AgentDynamicFieldAccessor.setDynamicFieldValue(conn, null);\n        httpClientDoExecuteForwardedInterceptor.before(methodInfo, context);\n        assertEquals(TestUtils.FORWARDED_VALUE, conn.getRequestProperty(TestUtils.FORWARDED_NAME));\n        context.remove(TestUtils.FORWARDED_NAME);\n    }\n\n    @Test\n    public void getType() {\n        HttpURLConnectionForwardedInterceptor httpClientDoExecuteForwardedInterceptor = new HttpURLConnectionForwardedInterceptor();\n        assertEquals(ConfigConst.PluginID.FORWARDED, httpClientDoExecuteForwardedInterceptor.getType());\n    }\n}\n"
  },
  {
    "path": "plugins/httpurlconnection-jdk17/src/test/java/com/megaease/easeagent/plugin/httpurlconnection/jdk17/interceptor/HttpURLConnectionInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2023, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.httpurlconnection.jdk17.interceptor;\n\nimport com.megaease.easeagent.mock.plugin.api.MockEaseAgent;\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.trace.Scope;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.field.AgentDynamicFieldAccessor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport com.megaease.easeagent.plugin.tools.trace.HttpRequest;\nimport com.megaease.easeagent.plugin.tools.trace.HttpResponse;\nimport lombok.SneakyThrows;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class HttpURLConnectionInterceptorTest {\n\n    @SneakyThrows\n    @Test\n    public void before() {\n        Context context = EaseAgent.getContext();\n        MethodInfo methodInfo = TestUtils.mockMethodInfo();\n\n        HttpURLConnectionInterceptor httpURLConnectionWriteRequestsInterceptor = new HttpURLConnectionInterceptor();\n        MockEaseAgent.cleanLastSpan();\n        httpURLConnectionWriteRequestsInterceptor.before(methodInfo, context);\n        httpURLConnectionWriteRequestsInterceptor.after(methodInfo, context);\n        ReportSpan mockSpan = MockEaseAgent.getLastSpan();\n        assertNotNull(mockSpan);\n        assertEquals(Span.Kind.CLIENT.name(), mockSpan.kind());\n        assertEquals(TestUtils.RESPONSE_TAG_VALUE, mockSpan.tag(TestUtils.RESPONSE_TAG_NAME));\n        assertNull(mockSpan.parentId());\n\n        Span span = context.nextSpan();\n        try (Scope ignored = span.maybeScope()) {\n            AgentDynamicFieldAccessor.setDynamicFieldValue(methodInfo.getInvoker(), null);\n            httpURLConnectionWriteRequestsInterceptor.doBefore(methodInfo, context);\n            httpURLConnectionWriteRequestsInterceptor.doAfter(methodInfo, context);\n            mockSpan = MockEaseAgent.getLastSpan();\n            assertEquals(span.traceIdString(), mockSpan.traceId());\n            assertEquals(span.spanIdString(), mockSpan.parentId());\n            assertNotNull(mockSpan.id());\n        }\n        span.abandon();\n    }\n\n    @Test\n    public void after() {\n        before();\n    }\n\n\n    @SneakyThrows\n    @Test\n    public void getRequest() {\n        Context context = EaseAgent.getContext();\n        MethodInfo methodInfo = TestUtils.mockMethodInfo();\n\n        HttpURLConnectionInterceptor httpClientDoExecuteInterceptor = new HttpURLConnectionInterceptor();\n        HttpRequest request = httpClientDoExecuteInterceptor.getRequest(methodInfo, context);\n        assertEquals(Span.Kind.CLIENT, request.kind());\n        assertEquals(\"GET\", request.method());\n    }\n\n    @SneakyThrows\n    @Test\n    public void getResponse() {\n        Context context = EaseAgent.getContext();\n        MethodInfo methodInfo = TestUtils.mockMethodInfo();\n\n        HttpURLConnectionInterceptor httpClientDoExecuteInterceptor = new HttpURLConnectionInterceptor();\n\n        HttpResponse httpResponse = httpClientDoExecuteInterceptor.getResponse(methodInfo, context);\n        assertEquals(200, httpResponse.statusCode());\n    }\n}\n"
  },
  {
    "path": "plugins/httpurlconnection-jdk17/src/test/java/com/megaease/easeagent/plugin/httpurlconnection/jdk17/interceptor/TestUtils.java",
    "content": "/*\n * Copyright (c) 2023, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.httpurlconnection.jdk17.interceptor;\n\nimport com.google.common.collect.ImmutableMap;\nimport com.google.common.collect.Lists;\nimport com.google.common.net.HttpHeaders;\nimport com.megaease.easeagent.plugin.field.DynamicFieldAccessor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport lombok.SneakyThrows;\n\nimport java.io.IOException;\nimport java.net.HttpURLConnection;\nimport java.net.URL;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nfinal class TestUtils {\n    public static final String FORWARDED_NAME = \"X-Forwarded-For\";\n    public static final String FORWARDED_VALUE = \"testForwarded\";\n    public static final String RESPONSE_TAG_NAME = \"X-EG-Test\";\n    public static final String RESPONSE_TAG_VALUE = \"X-EG-Test-Value\";\n\n    @SneakyThrows\n    static MethodInfo mockMethodInfo() {\n        HttpURLConnection httpURLConnection = mockHttpURLConnection();\n        return mockMethodInfo(httpURLConnection);\n    }\n\n    static MethodInfo mockMethodInfo(HttpURLConnection httpURLConnection) {\n        MethodInfo methodInfo = MethodInfo.builder()\n            .invoker(httpURLConnection).retValue(httpURLConnection)\n            .build();\n        return methodInfo;\n    }\n\n    @SneakyThrows\n    static HttpURLConnection mockHttpURLConnection() {\n        URL url = new URL(\"http://127.0.0.1:8080\");\n        Map<String, String> responseHeader = ImmutableMap.of(TestUtils.RESPONSE_TAG_NAME, TestUtils.RESPONSE_TAG_VALUE);\n        return getConnection(url, \"GET\", null, responseHeader);\n    }\n\n    static HttpURLConnection getConnection(\n        URL url, String method, Map<String, String> requestHeaders, Map<String, String> responseHeader) throws IOException {\n\n        HttpURLConnection conn = new HttpURLConnectionTest(url, responseHeader);\n        conn.setRequestMethod(method);\n        conn.setDoInput(true);\n        conn.setDoOutput(true);\n        conn.setRequestProperty(HttpHeaders.HOST, url.getHost());\n        if (requestHeaders != null) {\n            for (String key : requestHeaders.keySet()) {\n                conn.setRequestProperty(key, requestHeaders.get(key));\n            }\n        }\n\n        return conn;\n    }\n\n    public static class HttpURLConnectionTest extends HttpURLConnection implements DynamicFieldAccessor {\n        Map<String, String> responseHeader;\n        Object easeAgent$$DynamicField$$Data;\n\n        public HttpURLConnectionTest(URL u, Map<String, String> responseHeader) {\n            super(u);\n            this.responseHeader = responseHeader;\n        }\n\n        @Override\n        public void setEaseAgent$$DynamicField$$Data(Object data) {\n            this.easeAgent$$DynamicField$$Data = data;\n        }\n\n        @Override\n        public Object getEaseAgent$$DynamicField$$Data() {\n            return easeAgent$$DynamicField$$Data;\n        }\n\n\n        @Override\n        public void connect() throws IOException {\n\n        }\n\n        @Override\n        public void disconnect() {\n\n        }\n\n        @Override\n        public boolean usingProxy() {\n            return false;\n        }\n\n        @Override\n        public int getResponseCode() throws IOException {\n            return 200;\n        }\n\n        @Override\n        public Map<String, List<String>> getHeaderFields() {\n            Map<String, List<String>> fields = new HashMap<>();\n            for (String key : responseHeader.keySet()) {\n                fields.put(key, Lists.newArrayList(responseHeader.get(key)));\n            }\n            return fields;\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/httpurlconnection-jdk17/src/test/resources/mock_agent.properties",
    "content": "#\n# Copyright (c) 2023, MegaEase\n# All rights reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\neaseagent.progress.forwarded.headers=X-Forwarded-For\nobservability.tracings.tag.response.headers.eg.0=X-EG-Test\n"
  },
  {
    "path": "plugins/jdbc/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright (c) 2021, MegaEase\n  ~ All rights reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~   http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <parent>\n        <groupId>com.megaease.easeagent</groupId>\n        <artifactId>plugins</artifactId>\n        <version>2.3.0</version>\n    </parent>\n\n    <!--\n    <groupId>com.megaease.easeagent.plugin</groupId>\n    -->\n    <artifactId>jdbc</artifactId>\n\n    <packaging>jar</packaging>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>plugin-api</artifactId>\n            <scope>provided</scope>\n        </dependency>\n\n        <dependency>\n            <groupId>com.google.guava</groupId>\n            <artifactId>guava</artifactId>\n            <scope>provided</scope>\n        </dependency>\n\n        <dependency>\n            <groupId>commons-codec</groupId>\n            <artifactId>commons-codec</artifactId>\n            <version>${version.commons-codec}</version>\n        </dependency>\n\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>plugin-api-mock</artifactId>\n            <version>${project.version}</version>\n            <scope>test</scope>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-compiler-plugin</artifactId>\n            </plugin>\n\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-resources-plugin</artifactId>\n                <version>3.0.1</version>\n            </plugin>\n\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-shade-plugin</artifactId>\n                <version>${version.maven-shade-plugin}</version>\n                <configuration>\n                    <minimizeJar>true</minimizeJar>\n                </configuration>\n                <executions>\n                    <execution>\n                        <phase>package</phase>\n                        <goals>\n                            <goal>shade</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n\n            <!--\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-jar-plugin</artifactId>\n                <configuration>\n                    <archive>\n                        <manifest>\n                            <addDefaultImplementationEntries>true</addDefaultImplementationEntries>\n                            <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>\n                        </manifest>\n                    </archive>\n                </configuration>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-release-plugin</artifactId>\n                <version>2.5.3</version>\n            </plugin>\n            -->\n        </plugins>\n    </build>\n</project>\n"
  },
  {
    "path": "plugins/jdbc/src/main/java/com/megaease/easeagent/plugin/jdbc/JdbcConnectionMetricPlugin.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.jdbc;\n\nimport com.megaease.easeagent.plugin.AgentPlugin;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\n\nimport static com.megaease.easeagent.plugin.api.config.ConfigConst.Namespace.JDBC_CONNECTION;\n\npublic class JdbcConnectionMetricPlugin implements AgentPlugin {\n    @Override\n    public String getNamespace() {\n        return JDBC_CONNECTION;\n    }\n\n    @Override\n    public String getDomain() {\n        return ConfigConst.OBSERVABILITY;\n    }\n}\n"
  },
  {
    "path": "plugins/jdbc/src/main/java/com/megaease/easeagent/plugin/jdbc/JdbcDataSourceMetricPlugin.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.jdbc;\n\nimport com.megaease.easeagent.plugin.AgentPlugin;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\n\nimport static com.megaease.easeagent.plugin.api.config.ConfigConst.Namespace.JDBC_STATEMENT;\n\npublic class JdbcDataSourceMetricPlugin implements AgentPlugin {\n    @Override\n    public String getNamespace() {\n        return JDBC_STATEMENT;\n    }\n\n    @Override\n    public String getDomain() {\n        return ConfigConst.OBSERVABILITY;\n    }\n}\n"
  },
  {
    "path": "plugins/jdbc/src/main/java/com/megaease/easeagent/plugin/jdbc/JdbcRedirectPlugin.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.jdbc;\n\nimport com.megaease.easeagent.plugin.AgentPlugin;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.enums.Order;\n\npublic class JdbcRedirectPlugin implements AgentPlugin {\n    @Override\n    public String getNamespace() {\n        return ConfigConst.Namespace.JDBC;\n    }\n\n    @Override\n    public String getDomain() {\n        return ConfigConst.INTEGRABILITY;\n    }\n\n    @Override\n    public int order() {\n        return Order.REDIRECT.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/jdbc/src/main/java/com/megaease/easeagent/plugin/jdbc/JdbcTracingPlugin.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.jdbc;\n\nimport com.megaease.easeagent.plugin.AgentPlugin;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\n\npublic class JdbcTracingPlugin implements AgentPlugin {\n    @Override\n    public String getNamespace() {\n        return ConfigConst.Namespace.JDBC;\n    }\n\n    @Override\n    public String getDomain() {\n        return ConfigConst.OBSERVABILITY;\n    }\n}\n"
  },
  {
    "path": "plugins/jdbc/src/main/java/com/megaease/easeagent/plugin/jdbc/advice/HikariDataSourceAdvice.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.jdbc.advice;\n\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.Set;\n\npublic class HikariDataSourceAdvice implements Points {\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return ClassMatcher.builder()\n            .hasSuperClass(\"com.zaxxer.hikari.HikariConfig\")\n            .build();\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.builder()\n            .nameStartWith(\"set\")\n            .build().toSet();\n    }\n}\n"
  },
  {
    "path": "plugins/jdbc/src/main/java/com/megaease/easeagent/plugin/jdbc/advice/JdbcConnectionAdvice.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.jdbc.advice;\n\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.Set;\n\npublic class JdbcConnectionAdvice implements Points {\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return ClassMatcher.builder()\n            .hasInterface(\"java.sql.Connection\")\n            .build();\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.builder()\n            .named(\"createStatement\").isPublic()\n            .or().named(\"prepareCall\").isPublic()\n            .or().named(\"prepareStatement\").isPublic()\n            .build().toSet();\n    }\n}\n"
  },
  {
    "path": "plugins/jdbc/src/main/java/com/megaease/easeagent/plugin/jdbc/advice/JdbcDataSourceAdvice.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.jdbc.advice;\n\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.Set;\n\npublic class JdbcDataSourceAdvice implements Points {\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return ClassMatcher.builder()\n            .hasInterface(\"javax.sql.DataSource\")\n            .build();\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.builder()\n            .named(\"getConnection\")\n            .returnType(\"java.sql.Connection\")\n            .build().toSet();\n    }\n}\n"
  },
  {
    "path": "plugins/jdbc/src/main/java/com/megaease/easeagent/plugin/jdbc/advice/JdbcStatementAdvice.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.jdbc.advice;\n\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.Set;\n\npublic class JdbcStatementAdvice implements Points {\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return ClassMatcher.builder()\n            .hasInterface(\"java.sql.Statement\")\n            .notAbstract()\n            .notInterface()\n            .build();\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        IClassMatcher overriddenFrom = ClassMatcher.builder()\n            .hasClassName(\"java.sql.Statement\")\n            .or().hasClassName(\"java.sql.PreparedStatement\")\n            .build();\n\n        return MethodMatcher.multiBuilder()\n            .match(MethodMatcher.builder()\n                .nameStartWith(\"execute\")\n                .isOverriddenFrom(overriddenFrom)\n                .build())\n            .match(MethodMatcher.builder()\n                .named(\"addBatch\")\n                .or().named(\"clearBatch\")\n                .qualifier(\"batch\")\n                .build())\n            .build();\n    }\n\n    @Override\n    public boolean isAddDynamicField() {\n        return true;\n    }\n}\n"
  },
  {
    "path": "plugins/jdbc/src/main/java/com/megaease/easeagent/plugin/jdbc/common/DatabaseInfo.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.jdbc.common;\n\nimport com.megaease.easeagent.plugin.utils.common.StringUtils;\nimport lombok.Builder;\nimport lombok.Data;\n\nimport java.net.URI;\nimport java.sql.Connection;\nimport java.sql.SQLException;\n\n@Builder\n@Data\npublic class DatabaseInfo {\n    private String databaseType;\n    private String database;\n    private String host;\n    private int port;\n\n    public static DatabaseInfo getFromConnection(Connection connection) {\n        try {\n            String jdbcURL = connection.getMetaData().getURL();\n            URI url = URI.create(jdbcURL.substring(5)); // strip \"jdbc:\"\n            String remoteServiceName = url.getScheme(); // e.g. mysql, postgresql, oracle\n            String databaseName = connection.getCatalog();\n            return new DatabaseInfo(remoteServiceName, databaseName,\n                StringUtils.isNotEmpty(url.getHost()) ? url.getHost() : \"\",\n                url.getPort() == -1 ? 3306 : url.getPort());\n        } catch (SQLException ignored) {\n        }\n        return null;\n    }\n}\n"
  },
  {
    "path": "plugins/jdbc/src/main/java/com/megaease/easeagent/plugin/jdbc/common/JdbcUtils.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.jdbc.common;\n\nimport java.sql.Connection;\nimport java.sql.DatabaseMetaData;\nimport java.sql.SQLException;\n\npublic class JdbcUtils {\n\n    public static String getUrl(Connection con) {\n        try {\n            final DatabaseMetaData meta = con.getMetaData();\n            final String url = meta.getURL();\n            int idx = url.indexOf('?');\n            if (idx == -1) {\n                return url;\n            }\n            return url.substring(0, idx);\n        } catch (SQLException ignored) {\n        }\n        return null;\n    }\n}\n"
  },
  {
    "path": "plugins/jdbc/src/main/java/com/megaease/easeagent/plugin/jdbc/common/MD5DictionaryItem.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.jdbc.common;\n\nimport com.megaease.easeagent.plugin.utils.jackson.annotation.JsonProperty;\nimport lombok.Builder;\nimport lombok.Data;\n\n@Data\n@Builder\npublic class MD5DictionaryItem {\n    //global field\n    private long timestamp;\n\n    private String category;\n\n    @JsonProperty(\"host_name\")\n    private String hostName;\n\n    @JsonProperty(\"host_ipv4\")\n    private String hostIpv4;\n\n    private String gid;\n\n    private String service;\n\n    private String system;\n\n    private String type;\n\n    private String tags;\n\n    private String id;\n\n    // self field\n    private String md5;\n\n    private String sql;\n}\n"
  },
  {
    "path": "plugins/jdbc/src/main/java/com/megaease/easeagent/plugin/jdbc/common/MD5ReportConsumer.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.jdbc.common;\n\nimport com.megaease.easeagent.plugin.api.Reporter;\nimport com.megaease.easeagent.plugin.api.config.AutoRefreshPluginConfigRegistry;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.logging.Logger;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.tools.config.NameAndSystem;\nimport com.megaease.easeagent.plugin.utils.common.HostAddress;\nimport com.megaease.easeagent.plugin.utils.common.JsonUtil;\n\nimport java.util.Map;\nimport java.util.function.Consumer;\n\npublic class MD5ReportConsumer implements Consumer<Map<String, String>> {\n    public static final Logger LOGGER = EaseAgent.getLogger(MD5ReportConsumer.class);\n    private final IPluginConfig config;\n    private Reporter reporter;\n\n    public MD5ReportConsumer() {\n        this.config = AutoRefreshPluginConfigRegistry.getOrCreate(ConfigConst.OBSERVABILITY,\n            ConfigConst.Namespace.MD5_DICTIONARY, ConfigConst.PluginID.METRIC);\n        this.reporter = EaseAgent.metricReporter(config);\n    }\n\n    @Override\n    public void accept(Map<String, String> map) {\n        if (!this.config.enabled()) {\n            return;\n        }\n        for (Map.Entry<String, String> entry : map.entrySet()) {\n            MD5DictionaryItem item = MD5DictionaryItem.builder()\n                .timestamp(System.currentTimeMillis())\n                .category(\"application\")\n                .hostName(HostAddress.localhost())\n                .hostIpv4(HostAddress.getHostIpv4())\n                .gid(\"\")\n                .system(NameAndSystem.system())\n                .service(NameAndSystem.name())\n                .tags(\"\")\n                .type(\"md5-dictionary\")\n                .id(\"\")\n                .md5(entry.getKey())\n                .sql(entry.getValue())\n                .build();\n            String json = JsonUtil.toJson(item);\n            this.reporter.report(json);\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/jdbc/src/main/java/com/megaease/easeagent/plugin/jdbc/common/MD5SQLCompression.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.jdbc.common;\n\nimport com.google.common.cache.Cache;\nimport com.google.common.cache.CacheBuilder;\nimport com.google.common.cache.RemovalListener;\nimport com.google.common.cache.RemovalNotification;\nimport com.megaease.easeagent.plugin.api.logging.Logger;\nimport com.megaease.easeagent.plugin.async.ScheduleHelper;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.utils.common.DataSize;\nimport com.megaease.easeagent.plugin.utils.common.StringUtils;\nimport org.apache.commons.codec.digest.DigestUtils;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentMap;\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.function.Consumer;\n\npublic class MD5SQLCompression implements SQLCompression, RemovalListener<String, String> {\n    private static final Logger logger = EaseAgent.getLogger(MD5SQLCompression.class);\n\n    public static final DataSize MAX_SQL_SIZE = DataSize.ofKilobytes(32);//32KB\n\n    private static final AtomicReference<MD5SQLCompression> INSTANCE = new AtomicReference<>();\n\n    private final Cache<String, String> dictionary = CacheBuilder.newBuilder().maximumSize(1000)\n        .removalListener(this).build();\n\n    private final Cache<String, String> md5Cache = CacheBuilder.newBuilder().maximumSize(1000).build();\n\n    private final Consumer<Map<String, String>> reportConsumer;\n\n    public MD5SQLCompression(Consumer<Map<String, String>> reportConsumer) {\n        this.reportConsumer = reportConsumer;\n        ScheduleHelper.DEFAULT.nonStopExecute(10, 5, this::pushItems);\n    }\n\n    public static MD5SQLCompression getInstance() {\n        MD5SQLCompression md5SQLCompression = INSTANCE.get();\n        if (md5SQLCompression != null) {\n            return md5SQLCompression;\n        }\n        synchronized (INSTANCE) {\n            md5SQLCompression = INSTANCE.get();\n            if (md5SQLCompression != null) {\n                return md5SQLCompression;\n            }\n            MD5SQLCompression instance = new MD5SQLCompression(new MD5ReportConsumer());\n            INSTANCE.set(instance);\n            return instance;\n        }\n    }\n\n    private String cacheLoad(String str) {\n        return DigestUtils.md5Hex(str);\n    }\n\n    @Override\n    public String compress(String origin) {\n        try {\n            String cutStr = StringUtils.cutStrByDataSize(origin, MAX_SQL_SIZE);\n            String md5 = md5Cache.get(cutStr, () -> cacheLoad(cutStr));\n            String value = dictionary.getIfPresent(md5);\n            if (value == null) {\n                dictionary.put(md5, cutStr);\n            }\n            return md5;\n        } catch (Exception e) {\n            logger.warn(\"compress content[{}] failure\", origin, e);\n            return origin;\n        }\n    }\n\n    private void pushItems() {\n        ConcurrentMap<String, String> map = this.dictionary.asMap();\n        if (map.isEmpty()) {\n            return;\n        }\n        this.reportConsumer.accept(map);\n    }\n\n    @Override\n    public void onRemoval(RemovalNotification<String, String> notification) {\n        logger.info(\"remove md5 dictionary item. cause: {}, md5: {}, content: {}\",\n            notification.getCause().toString(), notification.getKey(), notification.getValue());\n        Map<String, String> map = new HashMap<>();\n        map.put(notification.getKey(), notification.getValue());\n        reportConsumer.accept(map);\n    }\n}\n"
  },
  {
    "path": "plugins/jdbc/src/main/java/com/megaease/easeagent/plugin/jdbc/common/SQLCompression.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.jdbc.common;\n\npublic interface SQLCompression {\n\n    SQLCompression DEFAULT = origin -> origin;\n\n    String compress(String origin);\n}\n"
  },
  {
    "path": "plugins/jdbc/src/main/java/com/megaease/easeagent/plugin/jdbc/common/SQLCompressionFactory.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.jdbc.common;\n\npublic class SQLCompressionFactory {\n\n    public static SQLCompression getSqlCompression() {\n        return SQLCompressionWrapper.INSTANCE;\n    }\n}\n"
  },
  {
    "path": "plugins/jdbc/src/main/java/com/megaease/easeagent/plugin/jdbc/common/SQLCompressionWrapper.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.jdbc.common;\n\nimport com.megaease.easeagent.plugin.api.config.Config;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\n\npublic class SQLCompressionWrapper implements SQLCompression {\n\n    public static final SQLCompressionWrapper INSTANCE = new SQLCompressionWrapper();\n\n    private static final String SQL_COMPRESS_ENABLED = \"plugin.observability.jdbc.sql.compress.enabled\";\n\n    @Override\n    public String compress(String origin) {\n        Config config = EaseAgent.getConfig();\n        Boolean enabled = config.getBoolean(SQL_COMPRESS_ENABLED);\n        if (enabled) {\n            return MD5SQLCompression.getInstance().compress(origin);\n        }\n        return SQLCompression.DEFAULT.compress(origin);\n    }\n}\n"
  },
  {
    "path": "plugins/jdbc/src/main/java/com/megaease/easeagent/plugin/jdbc/common/SqlInfo.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.jdbc.common;\n\nimport java.sql.Connection;\nimport java.util.ArrayList;\nimport java.util.List;\n\n@SuppressWarnings(\"unused\")\npublic class SqlInfo {\n    private final Connection connection;\n\n    public SqlInfo(Connection connection) {\n        this.connection = connection;\n    }\n\n    private final List<String> sqlList = new ArrayList<>();\n\n    public Connection getConnection() {\n        return connection;\n    }\n\n    public void addSql(String sql, boolean forBatch) {\n        if (!forBatch) {\n            this.sqlList.clear();\n        }\n        this.sqlList.add(sql);\n    }\n\n    public void clearSql() {\n        this.sqlList.clear();\n    }\n\n    public String getSql() {\n        if (this.sqlList.isEmpty()) {\n            return null;\n        }\n        return String.join(\"\\n\", this.sqlList);\n    }\n\n    public List<String> getSqlList() {\n        return sqlList;\n    }\n}\n\n"
  },
  {
    "path": "plugins/jdbc/src/main/java/com/megaease/easeagent/plugin/jdbc/interceptor/JdbConPrepareOrCreateStmInterceptor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.jdbc.interceptor;\n\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.logging.Logger;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.field.AgentDynamicFieldAccessor;\nimport com.megaease.easeagent.plugin.field.DynamicFieldAccessor;\nimport com.megaease.easeagent.plugin.interceptor.NonReentrantInterceptor;\nimport com.megaease.easeagent.plugin.jdbc.JdbcTracingPlugin;\nimport com.megaease.easeagent.plugin.jdbc.advice.JdbcConnectionAdvice;\nimport com.megaease.easeagent.plugin.jdbc.common.SqlInfo;\n\nimport java.sql.Connection;\nimport java.sql.Statement;\n\n@AdviceTo(value = JdbcConnectionAdvice.class, plugin = JdbcTracingPlugin.class)\npublic class JdbConPrepareOrCreateStmInterceptor implements NonReentrantInterceptor {\n    private static final Logger logger = EaseAgent.getLogger(JdbConPrepareOrCreateStmInterceptor.class);\n\n    @Override\n    public void doBefore(MethodInfo methodInfo, Context context) {\n    }\n\n    @Override\n    public void doAfter(MethodInfo methodInfo, Context context) {\n        Statement stm = (Statement) methodInfo.getRetValue();\n        SqlInfo sqlInfo = new SqlInfo((Connection) methodInfo.getInvoker());\n        if (methodInfo.getMethod().startsWith(\"prepare\")\n            && methodInfo.getArgs() != null && methodInfo.getArgs().length > 0) {\n            String sql = (String) methodInfo.getArgs()[0];\n            if (sql != null) {\n                sqlInfo.addSql(sql, false);\n            }\n        }\n        if (stm instanceof DynamicFieldAccessor) {\n            AgentDynamicFieldAccessor.setDynamicFieldValue(stm, sqlInfo);\n        } else {\n            logger.warn(\"statement must implements \" + DynamicFieldAccessor.class.getName());\n        }\n    }\n\n    @Override\n    public String getType() {\n        return Order.TRACING.getName();\n    }\n\n    @Override\n    public int order() {\n        return Order.HIGHEST.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/jdbc/src/main/java/com/megaease/easeagent/plugin/jdbc/interceptor/JdbcStmPrepareSqlInterceptor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.jdbc.interceptor;\n\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.logging.Logger;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.field.AgentDynamicFieldAccessor;\nimport com.megaease.easeagent.plugin.field.DynamicFieldAccessor;\nimport com.megaease.easeagent.plugin.interceptor.NonReentrantInterceptor;\nimport com.megaease.easeagent.plugin.jdbc.JdbcTracingPlugin;\nimport com.megaease.easeagent.plugin.jdbc.advice.JdbcStatementAdvice;\nimport com.megaease.easeagent.plugin.jdbc.common.SqlInfo;\n\nimport java.sql.Statement;\n\n@AdviceTo(value = JdbcStatementAdvice.class, plugin = JdbcTracingPlugin.class)\n@AdviceTo(value = JdbcStatementAdvice.class, qualifier = \"batch\", plugin = JdbcTracingPlugin.class)\npublic class JdbcStmPrepareSqlInterceptor implements NonReentrantInterceptor {\n    private static final Logger log = EaseAgent.getLogger(JdbcStmPrepareSqlInterceptor.class);\n\n    @Override\n    public void doBefore(MethodInfo methodInfo, Context context) {\n        Statement stm = (Statement) methodInfo.getInvoker();\n        if (!(stm instanceof DynamicFieldAccessor)) {\n            log.warn(\"statement must implements \" + DynamicFieldAccessor.class.getName());\n            return;\n        }\n\n        SqlInfo sqlInfo = AgentDynamicFieldAccessor.getDynamicFieldValue(stm);\n        if (sqlInfo == null) {\n            /*\n             * This happens:\n             * 1. StatementA contains StatementB.\n             * 2. Intercept: statementA = con.preparedStatement.\n             * 3. Not intercept: statementB = new StatementB(). StatementB can not be set dynamicField value.\n             * 4. StatementB invoke other method, like: clearBatch, so interceptor will find dynamicField value is null.\n             */\n            return;\n        }\n        String sql = null;\n        if (methodInfo.getArgs() != null && methodInfo.getArgs().length > 0) {\n            sql = (String) methodInfo.getArgs()[0];\n        }\n        String method = methodInfo.getMethod();\n        if (method.equals(\"addBatch\")) {\n            /*\n             * user creates PreparedStatement with con.preparedStatement(sql).\n             * User can invokes PreparedStatement.addBatch() multi times.\n             * In this scenario, sqlInfo should has only one sql.\n             */\n            if (sql != null) {\n                sqlInfo.addSql(sql, true);\n            }\n        } else if (method.equals(\"clearBatch\")) {\n            sqlInfo.clearSql();\n        } else if (method.startsWith(\"execute\") && sql != null) {\n            sqlInfo.addSql(sql, false);\n        }\n        context.put(SqlInfo.class, sqlInfo);\n    }\n\n    @Override\n    public String getType() {\n        return Order.TRACING.getName();\n    }\n\n    @Override\n    public int order() {\n        return Order.HIGH.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/jdbc/src/main/java/com/megaease/easeagent/plugin/jdbc/interceptor/metric/JdbcDataSourceMetricInterceptor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.jdbc.interceptor.metric;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.metric.ServiceMetricRegistry;\nimport com.megaease.easeagent.plugin.api.metric.name.Tags;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.interceptor.NonReentrantInterceptor;\nimport com.megaease.easeagent.plugin.jdbc.JdbcConnectionMetricPlugin;\nimport com.megaease.easeagent.plugin.jdbc.advice.JdbcDataSourceAdvice;\nimport com.megaease.easeagent.plugin.jdbc.common.JdbcUtils;\n\nimport java.sql.Connection;\n\n@AdviceTo(value = JdbcDataSourceAdvice.class, plugin = JdbcConnectionMetricPlugin.class)\npublic class JdbcDataSourceMetricInterceptor implements NonReentrantInterceptor {\n    private static JdbcMetric metric;\n    public static final String ERR_CON_METRIC_KEY = \"err-con\";\n\n    @Override\n    public void init(IPluginConfig config, String className, String methodName, String methodDescriptor) {\n        Tags tags = JdbcMetric.newConnectionTags();\n        metric = ServiceMetricRegistry.getOrCreate(config,\n            tags, JdbcMetric.METRIC_SUPPLIER);\n    }\n\n    @Override\n    public void doBefore(MethodInfo methodInfo, Context context) {\n    }\n\n    @Override\n    public void doAfter(MethodInfo methodInfo, Context context) {\n        Connection connection = (Connection) methodInfo.getRetValue();\n        String key;\n        boolean success = true;\n        if (methodInfo.getRetValue() == null || methodInfo.getThrowable() != null) {\n            key = ERR_CON_METRIC_KEY;\n            success = false;\n        } else {\n            key = JdbcUtils.getUrl(connection);\n        }\n        metric.collectMetric(key, success, context);\n    }\n\n\n    @Override\n    public String getType() {\n        return Order.METRIC.getName();\n    }\n\n    @Override\n    public int order() {\n        return Order.METRIC.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/jdbc/src/main/java/com/megaease/easeagent/plugin/jdbc/interceptor/metric/JdbcMetric.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.jdbc.interceptor.metric;\n\nimport com.google.common.cache.RemovalListener;\nimport com.google.common.cache.RemovalNotification;\nimport com.google.common.collect.ImmutableList;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.ContextUtils;\nimport com.megaease.easeagent.plugin.api.logging.Logger;\nimport com.megaease.easeagent.plugin.api.metric.*;\nimport com.megaease.easeagent.plugin.api.metric.name.*;\nimport com.megaease.easeagent.plugin.api.middleware.Redirect;\nimport com.megaease.easeagent.plugin.api.middleware.RedirectProcessor;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.tools.metrics.LastMinutesCounterGauge;\nimport com.megaease.easeagent.plugin.utils.ImmutableMap;\n\nimport javax.annotation.Nonnull;\nimport java.time.Duration;\nimport java.util.HashMap;\nimport java.util.Optional;\n\npublic class JdbcMetric extends ServiceMetric implements RemovalListener<String, String> {\n    private final Logger logger = EaseAgent.getLogger(JdbcMetric.class);\n    public static final ServiceMetricSupplier<JdbcMetric> METRIC_SUPPLIER = new ServiceMetricSupplier<JdbcMetric>() {\n        @Override\n        public NameFactory newNameFactory() {\n            return JdbcMetric.nameFactory();\n        }\n\n        @Override\n        public JdbcMetric newInstance(MetricRegistry metricRegistry, NameFactory nameFactory) {\n            return new JdbcMetric(metricRegistry, nameFactory);\n        }\n    };\n\n    public JdbcMetric(@Nonnull MetricRegistry metricRegistry, @Nonnull NameFactory nameFactory) {\n        super(metricRegistry, nameFactory);\n    }\n\n    public static Tags newConnectionTags() {\n        Tags tags = new Tags(\"application\", \"jdbc-connection\", \"url\");\n        RedirectProcessor.setTagsIfRedirected(Redirect.DATABASE, tags);\n        return tags;\n    }\n\n    public static Tags newStmTags() {\n        Tags tags = new Tags(\"application\", \"jdbc-statement\", \"signature\");\n        RedirectProcessor.setTagsIfRedirected(Redirect.DATABASE, tags);\n        return tags;\n    }\n\n    public static NameFactory nameFactory() {\n        return NameFactory.createBuilder()\n            .timerType(MetricSubType.DEFAULT,\n                ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                    .put(MetricField.MIN_EXECUTION_TIME, MetricValueFetcher.SnapshotMinValue)\n                    .put(MetricField.MAX_EXECUTION_TIME, MetricValueFetcher.SnapshotMaxValue)\n                    .put(MetricField.MEAN_EXECUTION_TIME, MetricValueFetcher.SnapshotMeanValue)\n                    .put(MetricField.P25_EXECUTION_TIME, MetricValueFetcher.Snapshot25Percentile)\n                    .put(MetricField.P50_EXECUTION_TIME, MetricValueFetcher.Snapshot50PercentileValue)\n                    .put(MetricField.P75_EXECUTION_TIME, MetricValueFetcher.Snapshot75PercentileValue)\n                    .put(MetricField.P95_EXECUTION_TIME, MetricValueFetcher.Snapshot95PercentileValue)\n                    .put(MetricField.P98_EXECUTION_TIME, MetricValueFetcher.Snapshot98PercentileValue)\n                    .put(MetricField.P99_EXECUTION_TIME, MetricValueFetcher.Snapshot99PercentileValue)\n                    .put(MetricField.P999_EXECUTION_TIME, MetricValueFetcher.Snapshot999PercentileValue)\n                    .build())\n            .gaugeType(MetricSubType.DEFAULT, new HashMap<>())\n            .meterType(MetricSubType.DEFAULT,\n                ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                    .put(MetricField.M1_RATE, MetricValueFetcher.MeteredM1RateIgnoreZero)\n                    .put(MetricField.M5_RATE, MetricValueFetcher.MeteredM5Rate)\n                    .put(MetricField.M15_RATE, MetricValueFetcher.MeteredM15Rate)\n                    .build())\n            .meterType(MetricSubType.ERROR,\n                ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                    .put(MetricField.M1_ERROR_RATE, MetricValueFetcher.MeteredM1Rate)\n                    .put(MetricField.M5_ERROR_RATE, MetricValueFetcher.MeteredM5Rate)\n                    .put(MetricField.M15_ERROR_RATE, MetricValueFetcher.MeteredM15Rate)\n                    .build())\n            .counterType(MetricSubType.DEFAULT, ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                .put(MetricField.EXECUTION_COUNT, MetricValueFetcher.CountingCount)\n                .build())\n            .counterType(MetricSubType.ERROR, ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                .put(MetricField.EXECUTION_ERROR_COUNT, MetricValueFetcher.CountingCount)\n                .build())\n            .build();\n    }\n\n    public void collectMetric(String key, boolean success, Context ctx) {\n        Timer timer = this.metricRegistry.timer(this.nameFactory.timerName(key, MetricSubType.DEFAULT));\n        timer.update(Duration.ofMillis(ContextUtils.getDuration(ctx)));\n        Counter counter = this.metricRegistry.counter(this.nameFactory.counterName(key, MetricSubType.DEFAULT));\n        Meter meter = this.metricRegistry.meter(this.nameFactory.meterName(key, MetricSubType.DEFAULT));\n        meter.mark();\n        counter.inc();\n        if (!success) {\n            Counter errCounter = this.metricRegistry.counter(this.nameFactory.counterName(key, MetricSubType.ERROR));\n            Meter errMeter = this.metricRegistry.meter(this.nameFactory.meterName(key, MetricSubType.ERROR));\n            errMeter.mark();\n            errCounter.inc();\n        }\n        MetricName gaugeName = this.nameFactory.gaugeNames(key).get(MetricSubType.DEFAULT);\n        metricRegistry.gauge(gaugeName.name(), () -> () -> LastMinutesCounterGauge.builder()\n            .m1Count((long) meter.getOneMinuteRate() * 60)\n            .m5Count((long) meter.getFiveMinuteRate() * 60 * 5)\n            .m15Count((long) meter.getFifteenMinuteRate() * 60 * 15)\n            .build());\n    }\n\n    @SuppressWarnings(\"NullableProblems\")\n    public void onRemoval(RemovalNotification<String, String> notification) {\n        try {\n            String key = notification.getKey();\n            ImmutableList<String> list = ImmutableList.of(\n                Optional.ofNullable(this.nameFactory.counterName(key, MetricSubType.DEFAULT)).orElse(\"\"),\n                Optional.ofNullable(this.nameFactory.counterName(key, MetricSubType.ERROR)).orElse(\"\"),\n                Optional.ofNullable(this.nameFactory.meterName(key, MetricSubType.DEFAULT)).orElse(\"\"),\n                Optional.ofNullable(this.nameFactory.meterName(key, MetricSubType.ERROR)).orElse(\"\"),\n                Optional.ofNullable(this.nameFactory.timerName(key, MetricSubType.DEFAULT)).orElse(\"\"),\n                Optional.ofNullable(this.nameFactory.gaugeName(key, MetricSubType.DEFAULT)).orElse(\"\"));\n\n            list.forEach(metricRegistry::remove);\n        } catch (Exception e) {\n            logger.warn(\"remove lru cache failed: \" + e.getMessage());\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/jdbc/src/main/java/com/megaease/easeagent/plugin/jdbc/interceptor/metric/JdbcStmMetricInterceptor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.jdbc.interceptor.metric;\n\nimport com.google.common.cache.Cache;\nimport com.google.common.cache.CacheBuilder;\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.metric.ServiceMetricRegistry;\nimport com.megaease.easeagent.plugin.api.metric.name.Tags;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.interceptor.NonReentrantInterceptor;\nimport com.megaease.easeagent.plugin.jdbc.JdbcDataSourceMetricPlugin;\nimport com.megaease.easeagent.plugin.jdbc.advice.JdbcStatementAdvice;\nimport com.megaease.easeagent.plugin.jdbc.common.MD5SQLCompression;\nimport com.megaease.easeagent.plugin.jdbc.common.SQLCompression;\nimport com.megaease.easeagent.plugin.jdbc.common.SQLCompressionFactory;\nimport com.megaease.easeagent.plugin.jdbc.common.SqlInfo;\n\n@AdviceTo(value = JdbcStatementAdvice.class, plugin = JdbcDataSourceMetricPlugin.class)\npublic class JdbcStmMetricInterceptor implements NonReentrantInterceptor {\n    private static final int maxCacheSize = 1000;\n    private static volatile JdbcMetric metric;\n    private static SQLCompression sqlCompression;\n    private static Cache<String, String> cache;\n\n    @Override\n    public void init(IPluginConfig config, String className, String methodName, String methodDescriptor) {\n        if (metric == null) {\n            synchronized (JdbcStmMetricInterceptor.class) {\n                if (metric == null) {\n                    Tags tags = JdbcMetric.newStmTags();\n                    metric = ServiceMetricRegistry.getOrCreate(config,\n                        tags,\n                        JdbcMetric.METRIC_SUPPLIER);\n                    sqlCompression = SQLCompressionFactory.getSqlCompression();\n                    cache = CacheBuilder.newBuilder()\n                        .maximumSize(maxCacheSize).removalListener(metric).build();\n\n                }\n            }\n        }\n    }\n\n    @Override\n    public void doBefore(MethodInfo methodInfo, Context context) {\n    }\n\n    @Override\n    public void doAfter(MethodInfo methodInfo, Context context) {\n        SqlInfo sqlInfo = context.get(SqlInfo.class);\n        String sql = sqlInfo.getSql();\n        String key = sqlCompression.compress(sql);\n        metric.collectMetric(key, methodInfo.getThrowable() == null, context);\n        String value = cache.getIfPresent(key);\n        if (value == null) {\n            cache.put(key, \"\");\n        }\n    }\n\n\n    @Override\n    public String getType() {\n        return Order.METRIC.getName();\n    }\n\n    @Override\n    public int order() {\n        return Order.METRIC.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/jdbc/src/main/java/com/megaease/easeagent/plugin/jdbc/interceptor/redirect/HikariSetPropertyInterceptor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.jdbc.interceptor.redirect;\n\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.logging.Logger;\nimport com.megaease.easeagent.plugin.api.middleware.Redirect;\nimport com.megaease.easeagent.plugin.api.middleware.RedirectProcessor;\nimport com.megaease.easeagent.plugin.api.middleware.ResourceConfig;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.jdbc.JdbcRedirectPlugin;\nimport com.megaease.easeagent.plugin.jdbc.advice.HikariDataSourceAdvice;\nimport com.megaease.easeagent.plugin.utils.common.StringUtils;\n\n@AdviceTo(value = HikariDataSourceAdvice.class, plugin = JdbcRedirectPlugin.class)\npublic class HikariSetPropertyInterceptor implements Interceptor {\n    private static final Logger LOGGER = EaseAgent.getLogger(HikariSetPropertyInterceptor.class);\n\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n        ResourceConfig cnf = Redirect.DATABASE.getConfig();\n        if (cnf == null) {\n            return;\n        }\n        if (methodInfo.getMethod().equals(\"setJdbcUrl\")) {\n            String jdbcUrl = cnf.getFirstUri();\n            LOGGER.info(\"Redirect JDBC Url: {} to {}\", methodInfo.getArgs()[0], jdbcUrl);\n            methodInfo.changeArg(0, jdbcUrl);\n            RedirectProcessor.redirected(Redirect.DATABASE, jdbcUrl);\n        } else if (methodInfo.getMethod().equals(\"setUsername\") && StringUtils.isNotEmpty(cnf.getUserName())) {\n            LOGGER.info(\"Redirect JDBC Username: {} to {}\", methodInfo.getArgs()[0], cnf.getUserName());\n            methodInfo.changeArg(0, cnf.getUserName());\n        } else if (methodInfo.getMethod().equals(\"setPassword\") && StringUtils.isNotEmpty(cnf.getPassword())) {\n            LOGGER.info(\"Redirect JDBC Password: *** to ***\");\n            methodInfo.changeArg(0, cnf.getPassword());\n        }\n    }\n\n    @Override\n    public String getType() {\n        return Order.REDIRECT.getName();\n    }\n\n    @Override\n    public int order() {\n        return Order.REDIRECT.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/jdbc/src/main/java/com/megaease/easeagent/plugin/jdbc/interceptor/tracing/JdbcStmTracingInterceptor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.jdbc.interceptor.tracing;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.context.ContextUtils;\nimport com.megaease.easeagent.plugin.api.logging.Logger;\nimport com.megaease.easeagent.plugin.api.middleware.MiddlewareConstants;\nimport com.megaease.easeagent.plugin.api.middleware.Redirect;\nimport com.megaease.easeagent.plugin.api.middleware.RedirectProcessor;\nimport com.megaease.easeagent.plugin.api.middleware.Type;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.interceptor.NonReentrantInterceptor;\nimport com.megaease.easeagent.plugin.jdbc.JdbcTracingPlugin;\nimport com.megaease.easeagent.plugin.jdbc.advice.JdbcStatementAdvice;\nimport com.megaease.easeagent.plugin.jdbc.common.*;\nimport org.apache.commons.codec.digest.DigestUtils;\n\nimport java.sql.Connection;\n\n@AdviceTo(value = JdbcStatementAdvice.class, plugin = JdbcTracingPlugin.class)\npublic class JdbcStmTracingInterceptor implements NonReentrantInterceptor {\n    private final static Logger LOG = EaseAgent.getLogger(JdbcStmTracingInterceptor.class);\n    protected final static String SPAN_KEY = JdbcStmTracingInterceptor.class.getName() + \"-SPAN\";\n\n    public static final String SPAN_SQL_QUERY_TAG_NAME = \"sql\";\n    public static final String SPAN_ERROR_TAG_NAME = \"error\";\n    public static final String SPAN_LOCAL_COMPONENT_TAG_NAME = \"local-component\";\n    public static final String SPAN_URL = \"url\";\n    private static volatile SQLCompression md5SQLCompression;\n\n    @Override\n    public void init(IPluginConfig config, String className, String methodName, String methodDescriptor) {\n        /*\n         * make reference to third-part lib,\n         * make it loaded during init, so these classes can be found\n         * during running\n         */\n        md5SQLCompression = SQLCompressionFactory.getSqlCompression();\n        DigestUtils.md5Hex(\"\");\n    }\n\n    @Override\n    public void doBefore(MethodInfo methodInfo, Context context) {\n        SqlInfo sqlInfo = ContextUtils.getFromContext(context, SqlInfo.class);\n        if (sqlInfo == null) {\n            LOG.warn(\"must get sqlInfo from context\");\n            return;\n        }\n        Span span = context.nextSpan();\n        // Statement stm = (Statement) methodInfo.getInvoker();\n        span.name(methodInfo.getMethod());\n        span.kind(Span.Kind.CLIENT);\n        span.tag(SPAN_SQL_QUERY_TAG_NAME,\n            md5SQLCompression.compress(sqlInfo.getSql()));\n        span.tag(SPAN_LOCAL_COMPONENT_TAG_NAME, \"database\");\n        Connection conn = sqlInfo.getConnection();\n        String url = JdbcUtils.getUrl(conn);\n        if (url != null) {\n            span.tag(SPAN_URL, url);\n        }\n        span.tag(MiddlewareConstants.TYPE_TAG_NAME, Type.DATABASE.getRemoteType());\n        RedirectProcessor.setTagsIfRedirected(Redirect.DATABASE, span, url);\n        DatabaseInfo databaseInfo = DatabaseInfo.getFromConnection(conn);\n        if (databaseInfo != null) {\n            span.remoteServiceName(remoteServiceName(databaseInfo));\n            span.remoteIpAndPort(databaseInfo.getHost(), databaseInfo.getPort());\n        }\n        span.start();\n        context.put(SPAN_KEY, span);\n    }\n\n    public String remoteServiceName(DatabaseInfo info) {\n        return String.format(\"%s-%s\", info.getDatabaseType(), info.getDatabase());\n    }\n\n    @Override\n    public void doAfter(MethodInfo methodInfo, Context context) {\n        Span span = context.get(SPAN_KEY);\n        if (methodInfo.getThrowable() != null) {\n            span.error(methodInfo.getThrowable());\n        }\n        span.finish();\n    }\n\n    @Override\n    public String getType() {\n        return Order.TRACING.getName();\n    }\n\n    @Override\n    public int order() {\n        return Order.TRACING.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/jdbc/src/test/java/com/megaease/easeagent/plugin/jdbc/MockJDBCStatement.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.jdbc;\n\nimport com.megaease.easeagent.plugin.field.DynamicFieldAccessor;\n\nimport java.sql.Statement;\n\npublic abstract class MockJDBCStatement implements Statement, DynamicFieldAccessor {\n    Object data;\n\n    @Override\n    public void setEaseAgent$$DynamicField$$Data(Object data) {\n        this.data = data;\n    }\n\n    @Override\n    public Object getEaseAgent$$DynamicField$$Data() {\n        return this.data;\n    }\n}\n"
  },
  {
    "path": "plugins/jdbc/src/test/java/com/megaease/easeagent/plugin/jdbc/TestUtils.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.jdbc;\n\nimport com.megaease.easeagent.mock.utils.MockSystemEnv;\nimport com.megaease.easeagent.plugin.api.middleware.MiddlewareConstants;\nimport com.megaease.easeagent.plugin.api.middleware.Redirect;\nimport com.megaease.easeagent.plugin.api.middleware.ResourceConfig;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\n\nimport java.sql.Connection;\nimport java.sql.DatabaseMetaData;\nimport java.sql.SQLException;\n\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\npublic class TestUtils {\n    public static final String DATABASE_TYPE = \"mysql\";\n    public static final String DATABASE = \"db_damo\";\n    public static final String HOST = \"192.168.1.14\";\n    public static final int PORT = 1234;\n    public static final String URI = String.format(\"jdbc:%s://%s:%s/%s\", DATABASE_TYPE, HOST, PORT, DATABASE);\n    public static final String FULL_URI = URI + \"?useUnicode=true&characterEncoding=utf-8&autoReconnectForPools=true&autoReconnect=true\";\n    public static final String REDIRECT_USERNAME = \"testUserName\";\n    public static final String REDIRECT_PASSWORD = \"testPassword\";\n\n\n    public static Connection mockConnection() throws SQLException {\n        Connection connection = mock(Connection.class);\n        DatabaseMetaData databaseMetaData = mock(DatabaseMetaData.class);\n        when(connection.getMetaData()).thenReturn(databaseMetaData);\n        when(connection.getCatalog()).thenReturn(DATABASE);\n        when(databaseMetaData.getURL()).thenReturn(FULL_URI);\n        return connection;\n    }\n\n    public static void setRedirect() {\n        MockSystemEnv.set(MiddlewareConstants.ENV_DATABASE, String.format(\"{\\\"uris\\\":\\\"%s\\\", \\\"userName\\\":\\\"%s\\\",\\\"password\\\":\\\"%s\\\"}\", FULL_URI, REDIRECT_USERNAME, REDIRECT_PASSWORD));\n        AgentFieldReflectAccessor.setFieldValue(Redirect.DATABASE, \"config\", ResourceConfig.getResourceConfig(Redirect.DATABASE.getEnv(), Redirect.DATABASE.isNeedParse()));\n    }\n\n//    public static void setSqlInfoToContext() throws SQLException {\n//        SqlInfo sqlInfo = new SqlInfo(TestUtils.mockConnection());\n//        String sql = \"select * from data\";\n//        sqlInfo.addSql(sql, false);\n//        EaseAgent.getContext().put(SqlInfo.class, sqlInfo);\n//    }\n}\n"
  },
  {
    "path": "plugins/jdbc/src/test/java/com/megaease/easeagent/plugin/jdbc/common/DatabaseInfoTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.jdbc.common;\n\nimport com.megaease.easeagent.plugin.jdbc.TestUtils;\nimport org.junit.Test;\n\nimport java.sql.Connection;\nimport java.sql.SQLException;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotNull;\n\npublic class DatabaseInfoTest {\n\n    @Test\n    public void getFromConnection() throws SQLException {\n        Connection connection = TestUtils.mockConnection();\n        DatabaseInfo databaseInfo = DatabaseInfo.getFromConnection(connection);\n        assertNotNull(databaseInfo);\n        assertEquals(TestUtils.DATABASE_TYPE, databaseInfo.getDatabaseType());\n        assertEquals(TestUtils.DATABASE, databaseInfo.getDatabase());\n        assertEquals(TestUtils.HOST, databaseInfo.getHost());\n        assertEquals(TestUtils.PORT, databaseInfo.getPort());\n    }\n}\n"
  },
  {
    "path": "plugins/jdbc/src/test/java/com/megaease/easeagent/plugin/jdbc/common/JdbcUtilsTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.jdbc.common;\n\nimport com.megaease.easeagent.plugin.jdbc.TestUtils;\nimport org.junit.Test;\n\nimport java.sql.Connection;\nimport java.sql.SQLException;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.mockito.Mockito.when;\n\npublic class JdbcUtilsTest {\n\n    @Test\n    public void getUrl() throws SQLException {\n        Connection connection = TestUtils.mockConnection();\n        assertEquals(TestUtils.FULL_URI, connection.getMetaData().getURL());\n        assertEquals(TestUtils.URI, JdbcUtils.getUrl(connection));\n\n        when(connection.getMetaData().getURL()).thenReturn(TestUtils.URI);\n        assertEquals(TestUtils.URI, connection.getMetaData().getURL());\n        assertEquals(TestUtils.URI, JdbcUtils.getUrl(connection));\n    }\n}\n"
  },
  {
    "path": "plugins/jdbc/src/test/java/com/megaease/easeagent/plugin/jdbc/common/MD5DictionaryItemTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.jdbc.common;\n\nimport com.megaease.easeagent.plugin.tools.config.NameAndSystem;\nimport com.megaease.easeagent.plugin.utils.common.JsonUtil;\nimport org.junit.Test;\n\nimport java.util.Map;\n\nimport static org.junit.Assert.assertEquals;\n\npublic class MD5DictionaryItemTest {\n\n    @Test\n    public void getTimestamp() {\n        MD5DictionaryItem item = MD5DictionaryItem.builder().timestamp(System.currentTimeMillis()).build();\n        String json = JsonUtil.toJson(item);\n        Map<String, Object> map = JsonUtil.toMap(json);\n        assertEquals(item.getTimestamp(), map.get(\"timestamp\"));\n    }\n\n    @Test\n    public void getCategory() {\n        MD5DictionaryItem item = MD5DictionaryItem.builder().category(\"getCategory\").build();\n        String json = JsonUtil.toJson(item);\n        Map<String, Object> map = JsonUtil.toMap(json);\n        assertEquals(item.getCategory(), map.get(\"category\"));\n    }\n\n    @Test\n    public void getHostName() {\n        MD5DictionaryItem item = MD5DictionaryItem.builder().hostName(\"testHost\").build();\n        String json = JsonUtil.toJson(item);\n        Map<String, Object> map = JsonUtil.toMap(json);\n        if (map.get(\"hostName\") != null) {\n            assertEquals(item.getHostName(), map.get(\"hostName\"));\n        } else {\n            assertEquals(item.getHostName(), map.get(\"host_name\"));\n        }\n\n    }\n\n    @Test\n    public void getHostIpv4() {\n        MD5DictionaryItem item = MD5DictionaryItem.builder().hostIpv4(\"192.168.0.15\").build();\n        String json = JsonUtil.toJson(item);\n        Map<String, Object> map = JsonUtil.toMap(json);\n        if (map.get(\"hostIpv4\") != null) {\n            assertEquals(item.getHostIpv4(), map.get(\"hostIpv4\"));\n        } else {\n            assertEquals(item.getHostIpv4(), map.get(\"host_ipv4\"));\n        }\n\n    }\n\n    @Test\n    public void getGid() {\n        MD5DictionaryItem item = MD5DictionaryItem.builder().gid(\"testGid\").build();\n        String json = JsonUtil.toJson(item);\n        Map<String, Object> map = JsonUtil.toMap(json);\n        assertEquals(item.getGid(), map.get(\"gid\"));\n    }\n\n    @Test\n    public void getService() {\n        MD5DictionaryItem item = MD5DictionaryItem.builder().service(NameAndSystem.name()).build();\n        String json = JsonUtil.toJson(item);\n        Map<String, Object> map = JsonUtil.toMap(json);\n        assertEquals(NameAndSystem.name(), map.get(\"service\"));\n    }\n\n    @Test\n    public void getSystem() {\n        MD5DictionaryItem item = MD5DictionaryItem.builder().system(NameAndSystem.system()).build();\n        String json = JsonUtil.toJson(item);\n        Map<String, Object> map = JsonUtil.toMap(json);\n        assertEquals(NameAndSystem.system(), map.get(\"system\"));\n    }\n\n    @Test\n    public void getType() {\n        MD5DictionaryItem item = MD5DictionaryItem.builder().type(\"testType\").build();\n        String json = JsonUtil.toJson(item);\n        Map<String, Object> map = JsonUtil.toMap(json);\n        assertEquals(item.getType(), map.get(\"type\"));\n\n    }\n\n    @Test\n    public void getTags() {\n        MD5DictionaryItem item = MD5DictionaryItem.builder().tags(\"testTags\").build();\n        String json = JsonUtil.toJson(item);\n        Map<String, Object> map = JsonUtil.toMap(json);\n        assertEquals(item.getTags(), map.get(\"tags\"));\n    }\n\n    @Test\n    public void getId() {\n        MD5DictionaryItem item = MD5DictionaryItem.builder().id(\"testId\").build();\n        String json = JsonUtil.toJson(item);\n        Map<String, Object> map = JsonUtil.toMap(json);\n        assertEquals(item.getId(), map.get(\"id\"));\n    }\n\n    @Test\n    public void getMd5() {\n        MD5DictionaryItem item = MD5DictionaryItem.builder().md5(\"testMd5\").build();\n        String json = JsonUtil.toJson(item);\n        Map<String, Object> map = JsonUtil.toMap(json);\n        assertEquals(item.getMd5(), map.get(\"md5\"));\n\n    }\n\n    @Test\n    public void getSql() {\n        MD5DictionaryItem item = MD5DictionaryItem.builder().md5(\"sql\").build();\n        String json = JsonUtil.toJson(item);\n        Map<String, Object> map = JsonUtil.toMap(json);\n        assertEquals(item.getSql(), map.get(\"sql\"));\n\n    }\n}\n"
  },
  {
    "path": "plugins/jdbc/src/test/java/com/megaease/easeagent/plugin/jdbc/common/MD5ReportConsumerTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.jdbc.common;\n\nimport com.megaease.easeagent.mock.plugin.api.MockEaseAgent;\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.mock.plugin.api.utils.ConfigTestUtils;\nimport com.megaease.easeagent.mock.plugin.api.utils.TagVerifier;\nimport com.megaease.easeagent.mock.report.impl.LastJsonReporter;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.api.config.Const;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.tools.config.NameAndSystem;\nimport com.megaease.easeagent.plugin.utils.common.HostAddress;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport java.util.Collections;\nimport java.util.Map;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class MD5ReportConsumerTest {\n\n    private static void verifyMD5ReportConsumer(Map<String, Object> reportConsumer, String md5, String sql) {\n        assertNotNull(reportConsumer.get(\"timestamp\"));\n        assertEquals(reportConsumer.get(\"category\"), \"application\");\n        if (reportConsumer.get(\"hostName\") != null) {\n            assertEquals(HostAddress.localhost(), reportConsumer.get(\"hostName\"));\n        } else {\n            assertEquals(HostAddress.localhost(), reportConsumer.get(\"host_name\"));\n        }\n        if (reportConsumer.get(\"hostIpv4\") != null) {\n            assertEquals(HostAddress.getHostIpv4(), reportConsumer.get(\"hostIpv4\"));\n        } else {\n            assertEquals(HostAddress.getHostIpv4(), reportConsumer.get(\"host_ipv4\"));\n        }\n        assertEquals(\"\", reportConsumer.get(\"gid\"));\n        assertEquals(NameAndSystem.system(), reportConsumer.get(\"system\"));\n        assertEquals(NameAndSystem.name(), reportConsumer.get(\"service\"));\n        assertEquals(\"\", reportConsumer.get(\"tags\"));\n        assertEquals(\"md5-dictionary\", reportConsumer.get(\"type\"));\n        assertEquals(md5, reportConsumer.get(\"md5\"));\n        assertEquals(sql, reportConsumer.get(\"sql\"));\n\n\n    }\n\n    @Test\n    public void accept() {\n        MD5ReportConsumer md5ReportConsumer = new MD5ReportConsumer();\n        TagVerifier tagVerifier = new TagVerifier()\n            .add(\"category\", \"application\")\n            .add(\"type\", \"md5-dictionary\");\n        LastJsonReporter lastJsonReporter = MockEaseAgent.lastMetricJsonReporter(tagVerifier::verifyAnd);\n        String testMd5 = \"testMd5\";\n        String testSql = \"testSql\";\n        md5ReportConsumer.accept(Collections.singletonMap(testMd5, testSql));\n        verifyMD5ReportConsumer(lastJsonReporter.getLastOnlyOne(), testMd5, testSql);\n        IPluginConfig iPluginConfig = EaseAgent.getConfig(ConfigConst.OBSERVABILITY,\n            ConfigConst.Namespace.MD5_DICTIONARY, ConfigConst.PluginID.METRIC);\n        try (ConfigTestUtils.Reset ignored = ConfigTestUtils.changeBoolean(iPluginConfig, Const.ENABLED_CONFIG, false)) {\n            lastJsonReporter.clean();\n            md5ReportConsumer.accept(Collections.singletonMap(testMd5, testSql));\n            try {\n                lastJsonReporter.getLast();\n                fail(\"must be throw error\");\n            } catch (Exception e) {\n                //must be error.\n            }\n        }\n\n    }\n}\n"
  },
  {
    "path": "plugins/jdbc/src/test/java/com/megaease/easeagent/plugin/jdbc/common/MD5SQLCompressionTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.jdbc.common;\n\nimport com.google.common.cache.Cache;\nimport com.google.common.cache.RemovalCause;\nimport com.google.common.cache.RemovalNotification;\nimport com.megaease.easeagent.mock.plugin.api.MockEaseAgent;\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.mock.plugin.api.utils.TagVerifier;\nimport com.megaease.easeagent.mock.report.impl.LastJsonReporter;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\nimport org.apache.commons.codec.digest.DigestUtils;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Method;\nimport java.util.Map;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertSame;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class MD5SQLCompressionTest {\n\n    @Test\n    public void getInstance() {\n        MD5SQLCompression md5SQLCompression = MD5SQLCompression.getInstance();\n        assertSame(md5SQLCompression, MD5SQLCompression.getInstance());\n    }\n\n    @Test\n    public void compress() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {\n        MD5SQLCompression md5SQLCompression = MD5SQLCompression.getInstance();\n        Cache<String, String> dictionary = AgentFieldReflectAccessor.getFieldValue(md5SQLCompression, \"dictionary\");\n        Cache<String, String> md5Cache = AgentFieldReflectAccessor.getFieldValue(md5SQLCompression, \"md5Cache\");\n        dictionary.cleanUp();\n        md5Cache.cleanUp();\n        String sql = \"select * from data\";\n        String md5 = DigestUtils.md5Hex(sql);\n        String result = md5SQLCompression.compress(sql);\n        assertEquals(md5, result);\n        assertEquals(result, md5SQLCompression.compress(sql));\n        assertEquals(md5, md5Cache.getIfPresent(sql));\n        assertEquals(sql, dictionary.getIfPresent(md5));\n        TagVerifier tagVerifier = new TagVerifier()\n            .add(\"category\", \"application\")\n            .add(\"type\", \"md5-dictionary\");\n        LastJsonReporter lastJsonReporter = MockEaseAgent.lastMetricJsonReporter(tagVerifier::verifyAnd);\n        pushItems();\n        Map<String, Object> report = lastJsonReporter.getLastOnlyOne();\n        assertEquals(md5, report.get(\"md5\"));\n        assertEquals(sql, report.get(\"sql\"));\n        dictionary.cleanUp();\n        md5Cache.cleanUp();\n    }\n\n\n    public static void pushItems() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {\n        Method method = MD5SQLCompression.class.getDeclaredMethod(\"pushItems\");\n        method.setAccessible(true);\n        method.invoke(MD5SQLCompression.getInstance());\n    }\n\n    @Test\n    public void onRemoval() {\n        String sql = \"select * from data\";\n        String md5 = DigestUtils.md5Hex(sql);\n        RemovalNotification<String, String> removalNotification = RemovalNotification.create(md5, sql, RemovalCause.SIZE);\n        MD5SQLCompression md5SQLCompression = MD5SQLCompression.getInstance();\n        TagVerifier tagVerifier = new TagVerifier()\n            .add(\"category\", \"application\")\n            .add(\"type\", \"md5-dictionary\");\n        LastJsonReporter lastJsonReporter = MockEaseAgent.lastMetricJsonReporter(tagVerifier::verifyAnd);\n        md5SQLCompression.onRemoval(removalNotification);\n        Map<String, Object> result = lastJsonReporter.getLastOnlyOne();\n        assertEquals(md5, result.get(\"md5\"));\n        assertEquals(sql, result.get(\"sql\"));\n    }\n}\n"
  },
  {
    "path": "plugins/jdbc/src/test/java/com/megaease/easeagent/plugin/jdbc/common/SqlInfoTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.jdbc.common;\n\nimport com.megaease.easeagent.plugin.jdbc.TestUtils;\nimport org.junit.Test;\n\nimport java.sql.Connection;\nimport java.sql.SQLException;\n\nimport static org.junit.Assert.*;\n\npublic class SqlInfoTest {\n\n    private static SqlInfo buildSqlInfo() throws SQLException {\n        Connection connection = TestUtils.mockConnection();\n        return new SqlInfo(connection);\n    }\n\n    @Test\n    public void getConnection() throws SQLException {\n        Connection connection = TestUtils.mockConnection();\n        SqlInfo sqlInfo = new SqlInfo(connection);\n        assertSame(connection, sqlInfo.getConnection());\n    }\n\n    @Test\n    public void addSql() throws SQLException {\n        SqlInfo sqlInfo = buildSqlInfo();\n        String sql = \"testSql\";\n        sqlInfo.addSql(sql, true);\n        assertEquals(1, sqlInfo.getSqlList().size());\n        assertEquals(sql, sqlInfo.getSqlList().get(0));\n        sqlInfo.addSql(sql, true);\n        assertEquals(2, sqlInfo.getSqlList().size());\n        assertEquals(sql, sqlInfo.getSqlList().get(1));\n\n        String sql2 = \"testSql2\";\n        sqlInfo.addSql(sql2, false);\n        assertEquals(1, sqlInfo.getSqlList().size());\n        assertEquals(sql2, sqlInfo.getSqlList().get(0));\n\n    }\n\n    @Test\n    public void clearSql() throws SQLException {\n        SqlInfo sqlInfo = buildSqlInfo();\n        sqlInfo.addSql(\"testSql\", true);\n        assertFalse(sqlInfo.getSqlList().isEmpty());\n        sqlInfo.clearSql();\n        assertTrue(sqlInfo.getSqlList().isEmpty());\n    }\n\n    @Test\n    public void getSql() throws SQLException {\n        SqlInfo sqlInfo = buildSqlInfo();\n        String sql = \"testSql\";\n        String sql2 = \"testSql2\";\n        sqlInfo.addSql(sql, true);\n        sqlInfo.addSql(sql2, true);\n        assertEquals(sql + \"\\n\" + sql2, sqlInfo.getSql());\n\n    }\n\n    @Test\n    public void getSqlList() throws SQLException {\n        addSql();\n    }\n}\n"
  },
  {
    "path": "plugins/jdbc/src/test/java/com/megaease/easeagent/plugin/jdbc/interceptor/JdbConPrepareOrCreateStmInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.jdbc.interceptor;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.field.AgentDynamicFieldAccessor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.jdbc.MockJDBCStatement;\nimport com.megaease.easeagent.plugin.jdbc.TestUtils;\nimport com.megaease.easeagent.plugin.jdbc.common.SqlInfo;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport java.sql.Connection;\nimport java.sql.SQLException;\nimport java.sql.Statement;\n\nimport static org.junit.Assert.*;\nimport static org.mockito.Mockito.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class JdbConPrepareOrCreateStmInterceptorTest {\n\n    @Test\n    public void doBefore() {\n        JdbConPrepareOrCreateStmInterceptor interceptor = new JdbConPrepareOrCreateStmInterceptor();\n        interceptor.doBefore(null, null);\n        assertTrue(true);\n    }\n\n    @Test\n    public void doAfter() throws SQLException {\n        JdbConPrepareOrCreateStmInterceptor interceptor = new JdbConPrepareOrCreateStmInterceptor();\n        Statement statement = mock(Statement.class);\n        Connection connection = TestUtils.mockConnection();\n        MethodInfo methodInfo = MethodInfo.builder().invoker(connection).retValue(statement).method(\"\").build();\n        interceptor.doBefore(methodInfo, EaseAgent.getContext());\n\n        MockJDBCStatement mockJDBCStatement = mock(MockJDBCStatement.class);\n        doCallRealMethod().when(mockJDBCStatement).setEaseAgent$$DynamicField$$Data(any());\n        doCallRealMethod().when(mockJDBCStatement).getEaseAgent$$DynamicField$$Data();\n        methodInfo = MethodInfo.builder().invoker(connection).retValue(mockJDBCStatement).method(\"\").build();\n        interceptor.doAfter(methodInfo, EaseAgent.getContext());\n        SqlInfo sqlInfo = AgentDynamicFieldAccessor.getDynamicFieldValue(mockJDBCStatement);\n        assertNotNull(sqlInfo);\n        assertTrue(sqlInfo.getSqlList().isEmpty());\n\n\n        mockJDBCStatement.setEaseAgent$$DynamicField$$Data(null);\n        String sql = \"select * from data\";\n        methodInfo = MethodInfo.builder().invoker(connection).retValue(mockJDBCStatement)\n            .method(\"prepareA\").args(new Object[]{sql}).build();\n        interceptor.doAfter(methodInfo, EaseAgent.getContext());\n        sqlInfo = AgentDynamicFieldAccessor.getDynamicFieldValue(mockJDBCStatement);\n        assertNotNull(sqlInfo);\n        assertEquals(1, sqlInfo.getSqlList().size());\n        assertEquals(sql, sqlInfo.getSqlList().get(0));\n\n        interceptor.doAfter(methodInfo, EaseAgent.getContext());\n        sqlInfo = AgentDynamicFieldAccessor.getDynamicFieldValue(mockJDBCStatement);\n        assertNotNull(sqlInfo);\n        assertEquals(1, sqlInfo.getSqlList().size());\n        assertEquals(sql, sqlInfo.getSqlList().get(0));\n    }\n\n    @Test\n    public void getType() {\n        JdbConPrepareOrCreateStmInterceptor interceptor = new JdbConPrepareOrCreateStmInterceptor();\n        assertEquals(ConfigConst.PluginID.TRACING, interceptor.getType());\n\n    }\n\n    @Test\n    public void order() {\n        JdbConPrepareOrCreateStmInterceptor interceptor = new JdbConPrepareOrCreateStmInterceptor();\n        assertEquals(Order.HIGHEST.getOrder(), interceptor.order());\n    }\n}\n"
  },
  {
    "path": "plugins/jdbc/src/test/java/com/megaease/easeagent/plugin/jdbc/interceptor/JdbcStmPrepareSqlInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.jdbc.interceptor;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.jdbc.MockJDBCStatement;\nimport com.megaease.easeagent.plugin.jdbc.common.SqlInfo;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static org.junit.Assert.*;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class JdbcStmPrepareSqlInterceptorTest {\n\n    @Test\n    public void doBefore() {\n        JdbcStmPrepareSqlInterceptor interceptor = new JdbcStmPrepareSqlInterceptor();\n        Context context = EaseAgent.getContext();\n        MockJDBCStatement mockJDBCStatement = mock(MockJDBCStatement.class);\n        SqlInfo sqlInfo = new SqlInfo(null);\n        when(mockJDBCStatement.getEaseAgent$$DynamicField$$Data()).thenReturn(sqlInfo);\n        String sql = \"select * from data\";\n        MethodInfo methodInfo = MethodInfo.builder().invoker(mockJDBCStatement).method(\"addBatch\").args(new Object[]{sql}).build();\n        interceptor.doBefore(methodInfo, context);\n        assertEquals(1, sqlInfo.getSqlList().size());\n        assertEquals(sql, sqlInfo.getSqlList().get(0));\n        interceptor.doBefore(methodInfo, context);\n        assertEquals(2, sqlInfo.getSqlList().size());\n        assertEquals(sql, sqlInfo.getSqlList().get(0));\n\n        methodInfo = MethodInfo.builder().invoker(mockJDBCStatement).method(\"clearBatch\").build();\n        interceptor.doBefore(methodInfo, context);\n        assertTrue(sqlInfo.getSqlList().isEmpty());\n\n        methodInfo = MethodInfo.builder().invoker(mockJDBCStatement).method(\"execute\").args(new Object[]{sql}).build();\n        interceptor.doBefore(methodInfo, context);\n        assertEquals(1, sqlInfo.getSqlList().size());\n        assertEquals(sql, sqlInfo.getSqlList().get(0));\n        interceptor.doBefore(methodInfo, context);\n        assertEquals(1, sqlInfo.getSqlList().size());\n        assertEquals(sql, sqlInfo.getSqlList().get(0));\n\n        methodInfo = MethodInfo.builder().invoker(mockJDBCStatement).method(\"clearBatch\").args(new Object[]{null}).build();\n        interceptor.doBefore(methodInfo, context);\n        assertTrue(sqlInfo.getSqlList().isEmpty());\n\n        assertSame(sqlInfo, context.get(SqlInfo.class));\n\n    }\n\n    @Test\n    public void getType() {\n        JdbcStmPrepareSqlInterceptor interceptor = new JdbcStmPrepareSqlInterceptor();\n        assertEquals(ConfigConst.PluginID.TRACING, interceptor.getType());\n    }\n\n    @Test\n    public void order() {\n        JdbcStmPrepareSqlInterceptor interceptor = new JdbcStmPrepareSqlInterceptor();\n        assertEquals(Order.HIGH.getOrder(), interceptor.order());\n    }\n}\n"
  },
  {
    "path": "plugins/jdbc/src/test/java/com/megaease/easeagent/plugin/jdbc/interceptor/metric/JdbcDataSourceMetricInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.jdbc.interceptor.metric;\n\nimport com.megaease.easeagent.mock.plugin.api.MockEaseAgent;\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.mock.plugin.api.utils.InterceptorTestUtils;\nimport com.megaease.easeagent.mock.plugin.api.utils.TagVerifier;\nimport com.megaease.easeagent.mock.report.impl.LastJsonReporter;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.api.context.ContextUtils;\nimport com.megaease.easeagent.plugin.api.metric.name.MetricField;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.jdbc.JdbcConnectionMetricPlugin;\nimport com.megaease.easeagent.plugin.jdbc.TestUtils;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport java.sql.Connection;\nimport java.sql.SQLException;\nimport java.util.Map;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class JdbcDataSourceMetricInterceptorTest {\n\n    @Test\n    public void init() {\n        JdbcDataSourceMetricInterceptor interceptor = new JdbcDataSourceMetricInterceptor();\n        InterceptorTestUtils.init(interceptor, new JdbcConnectionMetricPlugin());\n        assertNotNull(AgentFieldReflectAccessor.getStaticFieldValue(JdbcDataSourceMetricInterceptor.class, \"metric\"));\n\n    }\n\n    @Test\n    public void doBefore() {\n        JdbcDataSourceMetricInterceptor interceptor = new JdbcDataSourceMetricInterceptor();\n        interceptor.doBefore(null, null);\n        assertTrue(true);\n    }\n\n    @Test\n    public void doAfter() throws SQLException {\n        JdbcDataSourceMetricInterceptor interceptor = new JdbcDataSourceMetricInterceptor();\n        InterceptorTestUtils.init(interceptor, new JdbcConnectionMetricPlugin());\n        Context context = EaseAgent.getContext();\n        ContextUtils.setBeginTime(context);\n\n        MethodInfo methodInfo = MethodInfo.builder().build();\n        interceptor.doAfter(methodInfo, context);\n\n        TagVerifier errorTagVerifier = TagVerifier.build(JdbcMetric.newConnectionTags(), JdbcDataSourceMetricInterceptor.ERR_CON_METRIC_KEY);\n        LastJsonReporter lastJsonReporter = MockEaseAgent.lastMetricJsonReporter(errorTagVerifier::verifyAnd);\n        Map<String, Object> metrics = lastJsonReporter.flushAndOnlyOne();\n        assertEquals(1, metrics.get(MetricField.EXECUTION_COUNT.getField()));\n        assertEquals(1, metrics.get(MetricField.EXECUTION_ERROR_COUNT.getField()));\n\n        Connection connection = TestUtils.mockConnection();\n        methodInfo = MethodInfo.builder().retValue(connection).throwable(new RuntimeException(\"test error\")).build();\n        interceptor.doAfter(methodInfo, context);\n        metrics = lastJsonReporter.flushAndOnlyOne();\n        assertEquals(2, metrics.get(MetricField.EXECUTION_COUNT.getField()));\n        assertEquals(2, metrics.get(MetricField.EXECUTION_ERROR_COUNT.getField()));\n\n        methodInfo = MethodInfo.builder().retValue(connection).build();\n        interceptor.doAfter(methodInfo, context);\n        metrics = lastJsonReporter.flushAndOnlyOne();\n        assertEquals(2, metrics.get(MetricField.EXECUTION_COUNT.getField()));\n        assertEquals(2, metrics.get(MetricField.EXECUTION_ERROR_COUNT.getField()));\n        TagVerifier urlTagVerifier = TagVerifier.build(JdbcMetric.newConnectionTags(), TestUtils.URI);\n        lastJsonReporter = MockEaseAgent.lastMetricJsonReporter(urlTagVerifier::verifyAnd);\n        metrics = lastJsonReporter.flushAndOnlyOne();\n        assertEquals(1, metrics.get(MetricField.EXECUTION_COUNT.getField()));\n        assertNull(metrics.get(MetricField.EXECUTION_ERROR_COUNT.getField()));\n\n    }\n\n    @Test\n    public void getType() {\n        JdbcDataSourceMetricInterceptor interceptor = new JdbcDataSourceMetricInterceptor();\n        assertEquals(ConfigConst.PluginID.METRIC, interceptor.getType());\n    }\n\n    @Test\n    public void order() {\n        JdbcDataSourceMetricInterceptor interceptor = new JdbcDataSourceMetricInterceptor();\n        assertEquals(Order.METRIC.getOrder(), interceptor.order());\n    }\n}\n"
  },
  {
    "path": "plugins/jdbc/src/test/java/com/megaease/easeagent/plugin/jdbc/interceptor/metric/JdbcMetricTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.jdbc.interceptor.metric;\n\nimport com.google.common.cache.RemovalCause;\nimport com.google.common.cache.RemovalNotification;\nimport com.megaease.easeagent.mock.plugin.api.MockEaseAgent;\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.mock.plugin.api.utils.TagVerifier;\nimport com.megaease.easeagent.mock.report.impl.LastJsonReporter;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.context.ContextUtils;\nimport com.megaease.easeagent.plugin.api.metric.ServiceMetricRegistry;\nimport com.megaease.easeagent.plugin.api.metric.name.MetricField;\nimport com.megaease.easeagent.plugin.api.metric.name.NameFactory;\nimport com.megaease.easeagent.plugin.api.metric.name.Tags;\nimport com.megaease.easeagent.plugin.api.middleware.Redirect;\nimport com.megaease.easeagent.plugin.api.middleware.RedirectProcessor;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\nimport com.megaease.easeagent.plugin.jdbc.JdbcConnectionMetricPlugin;\nimport com.megaease.easeagent.plugin.jdbc.TestUtils;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class JdbcMetricTest {\n\n    public static JdbcMetric get() {\n        JdbcConnectionMetricPlugin jdbcPlugin = new JdbcConnectionMetricPlugin();\n        IPluginConfig iPluginConfig = EaseAgent.getConfig(jdbcPlugin.getDomain(), jdbcPlugin.getNamespace(), ConfigConst.PluginID.METRIC);\n        return ServiceMetricRegistry.getOrCreate(iPluginConfig, JdbcMetric.newConnectionTags(), JdbcMetric.METRIC_SUPPLIER);\n    }\n\n    @Test\n    public void newConnectionTags() {\n        Tags tags = JdbcMetric.newConnectionTags();\n        assertEquals(\"application\", tags.getCategory());\n        assertEquals(\"jdbc-connection\", tags.getType());\n        assertEquals(\"url\", tags.getKeyFieldName());\n\n        TestUtils.setRedirect();\n        RedirectProcessor.redirected(Redirect.DATABASE, TestUtils.URI);\n\n        String testTagKey = \"tagKey\";\n        String testTagValue = \"tagValue\";\n        Map<String, String> oldTags = RedirectProcessor.INSTANCE.getTags();\n        Map<String, String> newTagsMap = new HashMap<>();\n        newTagsMap.put(testTagKey, testTagValue);\n        AgentFieldReflectAccessor.setFieldValue(RedirectProcessor.INSTANCE, \"tags\", newTagsMap);\n        Tags newTags = JdbcMetric.newConnectionTags();\n        assertEquals(testTagValue, newTags.getTags().get(testTagKey));\n\n    }\n\n    @Test\n    public void newStmTags() {\n        Tags tags = JdbcMetric.newStmTags();\n        assertEquals(\"application\", tags.getCategory());\n        assertEquals(\"jdbc-statement\", tags.getType());\n        assertEquals(\"signature\", tags.getKeyFieldName());\n\n        TestUtils.setRedirect();\n        RedirectProcessor.redirected(Redirect.DATABASE, TestUtils.URI);\n        String testTagKey = \"tagKey\";\n        String testTagValue = \"tagValue\";\n        Map<String, String> newTagsMap = new HashMap<>();\n        newTagsMap.put(testTagKey, testTagValue);\n        AgentFieldReflectAccessor.setFieldValue(RedirectProcessor.INSTANCE, \"tags\", newTagsMap);\n        Tags newTags = JdbcMetric.newStmTags();\n        assertEquals(testTagValue, newTags.getTags().get(testTagKey));\n    }\n\n    @Test\n    public void nameFactory() {\n        NameFactory nameFactory = JdbcMetric.nameFactory();\n        assertEquals(4, nameFactory.metricTypes().size());\n    }\n\n    @Test\n    public void collectMetric() {\n        JdbcMetric jdbcMetric = get();\n        Context context = EaseAgent.getContext();\n        ContextUtils.setBeginTime(context);\n        jdbcMetric.collectMetric(TestUtils.URI, true, context);\n        TagVerifier tagVerifier = TagVerifier.build(JdbcMetric.newConnectionTags(), TestUtils.URI);\n        LastJsonReporter lastJsonReporter = MockEaseAgent.lastMetricJsonReporter(tagVerifier::verifyAnd);\n        Map<String, Object> metrics = lastJsonReporter.flushAndOnlyOne();\n        assertEquals(1, metrics.get(MetricField.EXECUTION_COUNT.getField()));\n        Object errorCount = metrics.get(MetricField.EXECUTION_ERROR_COUNT.getField());\n        if (errorCount != null) {\n            assertEquals(0, (int) (double) errorCount);\n        }\n\n        jdbcMetric.collectMetric(TestUtils.URI, false, context);\n        metrics = lastJsonReporter.flushAndOnlyOne();\n        assertEquals(2, metrics.get(MetricField.EXECUTION_COUNT.getField()));\n        assertEquals(1, metrics.get(MetricField.EXECUTION_ERROR_COUNT.getField()));\n\n    }\n\n    @Test\n    public void onRemoval() {\n        JdbcMetric jdbcMetric = get();\n        Context context = EaseAgent.getContext();\n        ContextUtils.setBeginTime(context);\n        jdbcMetric.collectMetric(TestUtils.URI, true, context);\n        TagVerifier tagVerifier = TagVerifier.build(JdbcMetric.newConnectionTags(), TestUtils.URI);\n        LastJsonReporter lastJsonReporter = MockEaseAgent.lastMetricJsonReporter(tagVerifier::verifyAnd);\n        Map<String, Object> metrics = lastJsonReporter.flushAndOnlyOne();\n        assertNotNull(metrics);\n        jdbcMetric.collectMetric(TestUtils.URI, false, context);\n        metrics = lastJsonReporter.flushAndOnlyOne();\n        assertNotNull(metrics);\n\n        RemovalNotification<String, String> removalNotification = RemovalNotification.create(TestUtils.URI, \"\", RemovalCause.SIZE);\n        jdbcMetric.onRemoval(removalNotification);\n        try {\n            lastJsonReporter.flushAndOnlyOne();\n            fail(\"must be throw error\");\n        } catch (Exception e) {\n            //must be error\n        }\n\n    }\n}\n"
  },
  {
    "path": "plugins/jdbc/src/test/java/com/megaease/easeagent/plugin/jdbc/interceptor/metric/JdbcStmMetricInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.jdbc.interceptor.metric;\n\nimport com.megaease.easeagent.mock.plugin.api.MockEaseAgent;\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.mock.plugin.api.utils.InterceptorTestUtils;\nimport com.megaease.easeagent.mock.plugin.api.utils.TagVerifier;\nimport com.megaease.easeagent.mock.report.impl.LastJsonReporter;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.api.context.ContextUtils;\nimport com.megaease.easeagent.plugin.api.metric.name.MetricField;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.jdbc.JdbcDataSourceMetricPlugin;\nimport com.megaease.easeagent.plugin.jdbc.TestUtils;\nimport com.megaease.easeagent.plugin.jdbc.common.SQLCompressionFactory;\nimport com.megaease.easeagent.plugin.jdbc.common.SqlInfo;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport java.sql.SQLException;\nimport java.util.Map;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class JdbcStmMetricInterceptorTest {\n\n    @Test\n    public void init() {\n        JdbcStmMetricInterceptor interceptor = new JdbcStmMetricInterceptor();\n        InterceptorTestUtils.init(interceptor, new JdbcDataSourceMetricPlugin());\n        assertNotNull(AgentFieldReflectAccessor.getStaticFieldValue(JdbcStmMetricInterceptor.class, \"metric\"));\n        assertNotNull(AgentFieldReflectAccessor.getStaticFieldValue(JdbcStmMetricInterceptor.class, \"sqlCompression\"));\n        assertNotNull(AgentFieldReflectAccessor.getStaticFieldValue(JdbcStmMetricInterceptor.class, \"cache\"));\n    }\n\n    @Test\n    public void doBefore() {\n        JdbcStmMetricInterceptor interceptor = new JdbcStmMetricInterceptor();\n        interceptor.doBefore(null, null);\n        assertTrue(true);\n    }\n\n    @Test\n    public void doAfter() throws SQLException {\n        JdbcStmMetricInterceptor interceptor = new JdbcStmMetricInterceptor();\n        InterceptorTestUtils.init(interceptor, new JdbcDataSourceMetricPlugin());\n\n        Context context = EaseAgent.getContext();\n        ContextUtils.setBeginTime(context);\n        SqlInfo sqlInfo = new SqlInfo(TestUtils.mockConnection());\n        String sql = \"select * from data\";\n        sqlInfo.addSql(sql, false);\n        context.put(SqlInfo.class, sqlInfo);\n\n        String key = SQLCompressionFactory.getSqlCompression().compress(sqlInfo.getSql());\n        MethodInfo methodInfo = MethodInfo.builder().build();\n        interceptor.doAfter(methodInfo, context);\n        TagVerifier tagVerifier = TagVerifier.build(JdbcMetric.newStmTags(), key);\n        LastJsonReporter lastJsonReporter = MockEaseAgent.lastMetricJsonReporter(tagVerifier::verifyAnd);\n        Map<String, Object> metrics = lastJsonReporter.flushAndOnlyOne();\n        assertEquals(1, metrics.get(MetricField.EXECUTION_COUNT.getField()));\n        Object errorCount = metrics.get(MetricField.EXECUTION_ERROR_COUNT.getField());\n        if (errorCount != null) {\n            assertEquals(0, (int) (double) errorCount);\n        }\n\n        methodInfo = MethodInfo.builder().throwable(new RuntimeException(\"test error\")).build();\n        interceptor.doAfter(methodInfo, context);\n        metrics = lastJsonReporter.flushAndOnlyOne();\n        assertEquals(2, metrics.get(MetricField.EXECUTION_COUNT.getField()));\n        assertEquals(1, metrics.get(MetricField.EXECUTION_ERROR_COUNT.getField()));\n\n    }\n\n    @Test\n    public void getType() {\n        JdbcStmMetricInterceptor interceptor = new JdbcStmMetricInterceptor();\n        assertEquals(ConfigConst.PluginID.METRIC, interceptor.getType());\n    }\n\n    @Test\n    public void order() {\n        JdbcStmMetricInterceptor interceptor = new JdbcStmMetricInterceptor();\n        assertEquals(Order.METRIC.getOrder(), interceptor.order());\n    }\n}\n"
  },
  {
    "path": "plugins/jdbc/src/test/java/com/megaease/easeagent/plugin/jdbc/interceptor/redirect/HikariSetPropertyInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.jdbc.interceptor.redirect;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.jdbc.TestUtils;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNull;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class HikariSetPropertyInterceptorTest {\n\n    @Test\n    public void before() {\n        HikariSetPropertyInterceptor interceptor = new HikariSetPropertyInterceptor();\n        MethodInfo methodInfo = MethodInfo.builder().method(\"setJdbcUrl\").args(new Object[]{null}).build();\n        interceptor.before(methodInfo, EaseAgent.getContext());\n        assertNull(methodInfo.getArgs()[0]);\n\n        TestUtils.setRedirect();\n        interceptor.before(methodInfo, EaseAgent.getContext());\n        assertEquals(TestUtils.FULL_URI, methodInfo.getArgs()[0]);\n\n        methodInfo = MethodInfo.builder().method(\"setUsername\").args(new Object[]{null}).build();\n        interceptor.before(methodInfo, EaseAgent.getContext());\n        assertEquals(TestUtils.REDIRECT_USERNAME, methodInfo.getArgs()[0]);\n\n        methodInfo = MethodInfo.builder().method(\"setPassword\").args(new Object[]{null}).build();\n        interceptor.before(methodInfo, EaseAgent.getContext());\n        assertEquals(TestUtils.REDIRECT_PASSWORD, methodInfo.getArgs()[0]);\n    }\n\n    @Test\n    public void getType() {\n        HikariSetPropertyInterceptor interceptor = new HikariSetPropertyInterceptor();\n        assertEquals(ConfigConst.PluginID.REDIRECT, interceptor.getType());\n    }\n\n    @Test\n    public void order() {\n        HikariSetPropertyInterceptor interceptor = new HikariSetPropertyInterceptor();\n        assertEquals(Order.REDIRECT.getOrder(), interceptor.order());\n    }\n}\n"
  },
  {
    "path": "plugins/jdbc/src/test/java/com/megaease/easeagent/plugin/jdbc/interceptor/tracing/JdbcStmTracingInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.jdbc.interceptor.tracing;\n\nimport com.megaease.easeagent.mock.plugin.api.MockEaseAgent;\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.mock.plugin.api.utils.InterceptorTestUtils;\nimport com.megaease.easeagent.mock.plugin.api.utils.SpanTestUtils;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.api.middleware.MiddlewareConstants;\nimport com.megaease.easeagent.plugin.api.middleware.Redirect;\nimport com.megaease.easeagent.plugin.api.middleware.RedirectProcessor;\nimport com.megaease.easeagent.plugin.api.middleware.Type;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.jdbc.JdbcTracingPlugin;\nimport com.megaease.easeagent.plugin.jdbc.TestUtils;\nimport com.megaease.easeagent.plugin.jdbc.common.SQLCompressionFactory;\nimport com.megaease.easeagent.plugin.jdbc.common.SqlInfo;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport java.sql.SQLException;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class JdbcStmTracingInterceptorTest {\n\n    @Test\n    public void init() {\n        JdbcStmTracingInterceptor interceptor = new JdbcStmTracingInterceptor();\n        InterceptorTestUtils.init(interceptor, new JdbcTracingPlugin());\n        assertNotNull(AgentFieldReflectAccessor.getStaticFieldValue(JdbcStmTracingInterceptor.class, \"md5SQLCompression\"));\n    }\n\n    @Test\n    public void doBefore() throws SQLException {\n        JdbcStmTracingInterceptor interceptor = new JdbcStmTracingInterceptor();\n        Context context = EaseAgent.getContext();\n        InterceptorTestUtils.init(interceptor, new JdbcTracingPlugin());\n        String method = \"test_method\";\n        MethodInfo methodInfo = MethodInfo.builder().method(method).build();\n        interceptor.doBefore(methodInfo, context);\n        assertNull(context.get(JdbcStmTracingInterceptor.SPAN_KEY));\n\n        SqlInfo sqlInfo = new SqlInfo(TestUtils.mockConnection());\n        String sql = \"select * from data\";\n        sqlInfo.addSql(sql, false);\n        context.put(SqlInfo.class, sqlInfo);\n\n        interceptor.doBefore(methodInfo, context);\n        Span span = context.remove(JdbcStmTracingInterceptor.SPAN_KEY);\n        assertNotNull(span);\n        span.finish();\n        ReportSpan reportSpan = MockEaseAgent.getLastSpan();\n        SpanTestUtils.sameId(span, reportSpan);\n\n        assertEquals(method, reportSpan.name());\n        assertEquals(Span.Kind.CLIENT.name(), reportSpan.kind());\n        assertEquals(SQLCompressionFactory.getSqlCompression().compress(sql), reportSpan.tag(JdbcStmTracingInterceptor.SPAN_SQL_QUERY_TAG_NAME));\n        assertEquals(\"database\", reportSpan.tag(JdbcStmTracingInterceptor.SPAN_LOCAL_COMPONENT_TAG_NAME));\n        assertEquals(TestUtils.URI, reportSpan.tag(JdbcStmTracingInterceptor.SPAN_URL));\n        assertEquals(Type.DATABASE.getRemoteType(), reportSpan.tag(MiddlewareConstants.TYPE_TAG_NAME));\n        assertEquals(TestUtils.DATABASE_TYPE + \"-\" + TestUtils.DATABASE, reportSpan.remoteServiceName());\n        assertEquals(TestUtils.HOST, reportSpan.remoteEndpoint().ipv4());\n        assertEquals(TestUtils.PORT, reportSpan.remoteEndpoint().port());\n\n        TestUtils.setRedirect();\n        RedirectProcessor.redirected(Redirect.DATABASE, TestUtils.URI);\n        interceptor.doBefore(methodInfo, context);\n        span = context.remove(JdbcStmTracingInterceptor.SPAN_KEY);\n        assertNotNull(span);\n        span.finish();\n        reportSpan = MockEaseAgent.getLastSpan();\n        SpanTestUtils.sameId(span, reportSpan);\n        assertEquals(TestUtils.URI, reportSpan.tag(MiddlewareConstants.REDIRECTED_LABEL_REMOTE_TAG_NAME));\n    }\n\n    @Test\n    public void doAfter() throws SQLException {\n        JdbcStmTracingInterceptor interceptor = new JdbcStmTracingInterceptor();\n        InterceptorTestUtils.init(interceptor, new JdbcTracingPlugin());\n        Context context = EaseAgent.getContext();\n        String method = \"test_method\";\n        MethodInfo methodInfo = MethodInfo.builder().method(method).build();\n        SqlInfo sqlInfo = new SqlInfo(TestUtils.mockConnection());\n        String sql = \"select * from data\";\n        sqlInfo.addSql(sql, false);\n        context.put(SqlInfo.class, sqlInfo);\n        interceptor.doBefore(methodInfo, context);\n        Span span = context.get(JdbcStmTracingInterceptor.SPAN_KEY);\n        assertNotNull(span);\n        interceptor.doAfter(methodInfo, context);\n        ReportSpan reportSpan = MockEaseAgent.getLastSpan();\n        SpanTestUtils.sameId(span, reportSpan);\n        assertFalse(reportSpan.hasError());\n\n        String error = \"test error\";\n        methodInfo = MethodInfo.builder().method(method).throwable(new RuntimeException(error)).build();\n        interceptor.doBefore(methodInfo, context);\n        span = context.get(JdbcStmTracingInterceptor.SPAN_KEY);\n        assertNotNull(span);\n        interceptor.doAfter(methodInfo, context);\n        reportSpan = MockEaseAgent.getLastSpan();\n        SpanTestUtils.sameId(span, reportSpan);\n        assertTrue(reportSpan.hasError());\n        assertEquals(error, reportSpan.errorInfo());\n    }\n\n    @Test\n    public void getType() {\n        JdbcStmTracingInterceptor interceptor = new JdbcStmTracingInterceptor();\n        assertEquals(ConfigConst.PluginID.TRACING, interceptor.getType());\n    }\n\n    @Test\n    public void order() {\n        JdbcStmTracingInterceptor interceptor = new JdbcStmTracingInterceptor();\n        assertEquals(Order.TRACING.getOrder(), interceptor.order());\n    }\n}\n"
  },
  {
    "path": "plugins/jdbc/src/test/resources/mock_agent.properties",
    "content": "name=test-jdbc-service\nsystem=test-jdbc-system\n"
  },
  {
    "path": "plugins/kafka/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright (c) 2017, MegaEase\n  ~ All rights reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>plugins</artifactId>\n        <groupId>com.megaease.easeagent</groupId>\n        <version>2.3.0</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>kafka</artifactId>\n    <dependencies>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>plugin-api</artifactId>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.google.guava</groupId>\n            <artifactId>guava</artifactId>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.apache.kafka</groupId>\n            <artifactId>kafka-clients</artifactId>\n            <version>2.1.1</version>\n            <scope>provided</scope>\n        </dependency>\n\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>plugin-api-mock</artifactId>\n            <version>${project.version}</version>\n            <scope>test</scope>\n        </dependency>\n    </dependencies>\n</project>\n\n"
  },
  {
    "path": "plugins/kafka/src/main/java/com/megaease/easeagent/plugin/kafka/KafkaPlugin.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.kafka;\n\nimport com.megaease.easeagent.plugin.AgentPlugin;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\n\npublic class KafkaPlugin implements AgentPlugin {\n    @Override\n    public String getNamespace() {\n        return ConfigConst.Namespace.KAFKA;\n    }\n\n    @Override\n    public String getDomain() {\n        return ConfigConst.OBSERVABILITY;\n    }\n}\n"
  },
  {
    "path": "plugins/kafka/src/main/java/com/megaease/easeagent/plugin/kafka/KafkaRedirectPlugin.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.kafka;\n\nimport com.megaease.easeagent.plugin.AgentPlugin;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\n\npublic class KafkaRedirectPlugin implements AgentPlugin {\n    @Override\n    public String getNamespace() {\n        return ConfigConst.Namespace.KAFKA;\n    }\n\n    @Override\n    public String getDomain() {\n        return ConfigConst.INTEGRABILITY;\n    }\n}\n"
  },
  {
    "path": "plugins/kafka/src/main/java/com/megaease/easeagent/plugin/kafka/advice/KafkaConsumerAdvice.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.kafka.advice;\n\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.Set;\n\npublic class KafkaConsumerAdvice implements Points {\n    //return def.type(named(\"org.apache.kafka.clients.consumer.KafkaConsumer\")\n    //                .or(hasSuperType(named(\"org.apache.kafka.clients.consumer.MockConsumer\")))\n    //        )\n    //                .transform(objConstruct(isConstructor().and(takesArguments(3))\n    //                                .and(takesArgument(0, named(\"org.apache.kafka.clients.consumer.ConsumerConfig\")))\n    //                        , AgentDynamicFieldAccessor.DYNAMIC_FIELD_NAME))\n    //                .transform(doPoll((named(\"poll\")\n    //                                .and(takesArguments(1)))\n    //                                .and(takesArgument(0, named(\"java.time.Duration\")))\n    //                        )\n    //                )\n    //                .end()\n    //                ;\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return ClassMatcher.builder().hasClassName(\"org.apache.kafka.clients.consumer.KafkaConsumer\")\n            .build().or(ClassMatcher.builder().hasSuperClass(\"org.apache.kafka.clients.consumer.MockConsumer\")\n                .build());\n\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.multiBuilder()\n            .match(MethodMatcher.builder().named(\"<init>\")\n                .argsLength(3)\n                .arg(0, \"org.apache.kafka.clients.consumer.ConsumerConfig\")\n                .qualifier(\"constructor\")\n                .build())\n            .match(MethodMatcher.builder().named(\"poll\")\n                .argsLength(1)\n                .arg(0, \"java.time.Duration\")\n                .qualifier(\"poll\")\n                .build())\n            .build();\n    }\n\n    @Override\n    public boolean isAddDynamicField() {\n        return true;\n    }\n}\n"
  },
  {
    "path": "plugins/kafka/src/main/java/com/megaease/easeagent/plugin/kafka/advice/KafkaConsumerConfigAdvice.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.kafka.advice;\n\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.Set;\n\nimport static com.megaease.easeagent.plugin.tools.matcher.ClassMatcherUtils.name;\n\npublic class KafkaConsumerConfigAdvice implements Points {\n    public static final String CONFIG_NAME = \"org.apache.kafka.clients.consumer.ConsumerConfig\";\n    //return def.type(named(\"org.apache.kafka.clients.consumer.ConsumerConfig\")\n    //                        .or(hasSuperType(named(\"org.apache.kafka.clients.consumer.ConsumerConfig\")))\n    //                )\n    //                .transform(objConstruct(isConstructor())\n    //                )\n    //                .end()\n    //                ;\n\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return name(CONFIG_NAME)\n            .or(ClassMatcher.builder().hasSuperClass(CONFIG_NAME).build());\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.multiBuilder()\n            .match(MethodMatcher.builder().named(\"<init>\")\n                .qualifier(\"constructor\")\n                .build())\n            .build();\n    }\n}\n"
  },
  {
    "path": "plugins/kafka/src/main/java/com/megaease/easeagent/plugin/kafka/advice/KafkaConsumerRecordAdvice.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.kafka.advice;\n\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.Set;\n\npublic class KafkaConsumerRecordAdvice implements Points {\n    //return def.type(named(\"org.apache.kafka.clients.consumer.ConsumerRecord\"))\n    //                .transform(objConstruct(none(), AgentDynamicFieldAccessor.DYNAMIC_FIELD_NAME))\n    //                .end()\n    //                ;\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return ClassMatcher.builder().hasClassName(\"org.apache.kafka.clients.consumer.ConsumerRecord\")\n            .build();\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.multiBuilder()\n            .match(MethodMatcher.builder().named(\"<init>\")\n                .qualifier(\"constructor\")\n                .build())\n            .build();\n    }\n\n    @Override\n    public boolean isAddDynamicField() {\n        return true;\n    }\n}\n"
  },
  {
    "path": "plugins/kafka/src/main/java/com/megaease/easeagent/plugin/kafka/advice/KafkaMessageListenerAdvice.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.kafka.advice;\n\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.Set;\n\npublic class KafkaMessageListenerAdvice implements Points {\n    //return def.type(hasSuperType(named(\"org.springframework.kafka.listener.MessageListener\")))\n    //                .transform(onMessage((named(\"onMessage\"))\n    //                ))\n    //                .end()\n    //                ;\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return ClassMatcher.builder().hasSuperClass(\"org.springframework.kafka.listener.MessageListener\")\n            .build();\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.multiBuilder()\n            .match(MethodMatcher.builder().named(\"onMessage\")\n                .build())\n            .build();\n    }\n}\n"
  },
  {
    "path": "plugins/kafka/src/main/java/com/megaease/easeagent/plugin/kafka/advice/KafkaProducerAdvice.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.kafka.advice;\n\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.Set;\n\npublic class KafkaProducerAdvice implements Points {\n    //return def.type(\n    //                named(\"org.apache.kafka.clients.producer.KafkaProducer\")\n    //                .or(hasSuperType(named(\"org.apache.kafka.clients.producer.MockProducer\")))\n    //        )\n    //                .transform(objConstruct(isConstructor().and(takesArguments(7)), AgentDynamicFieldAccessor.DYNAMIC_FIELD_NAME))\n    //                .transform(doSend((named(\"doSend\")\n    //                                .and(isPrivate())\n    //                                .and(takesArguments(2)))\n    //                                .and(takesArgument(0, named(\"org.apache.kafka.clients.producer.ProducerRecord\")))\n    //                                .and(takesArgument(1, named(\"org.apache.kafka.clients.producer.Callback\")))\n    //                                .and(returns(named(\"java.util.concurrent.Future\")))\n    //                        )\n    //\n    //                )\n    //\n    //                .end()\n    //                ;\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return ClassMatcher.builder().hasClassName(\"org.apache.kafka.clients.producer.KafkaProducer\")\n            .build().or(ClassMatcher.builder().hasClassName(\"org.apache.kafka.clients.producer.MockProducer\")\n                .build());\n\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.multiBuilder()\n            .match(MethodMatcher.builder().named(\"<init>\")\n                .argsLength(7)\n                .qualifier(\"constructor\")\n                .build())\n            .match(MethodMatcher.builder().named(\"doSend\")\n                .isPrivate()\n                .argsLength(2)\n                .arg(0, \"org.apache.kafka.clients.producer.ProducerRecord\")\n                .arg(1, \"org.apache.kafka.clients.producer.Callback\")\n                .returnType(\"java.util.concurrent.Future\")\n                .qualifier(\"doSend\")\n                .build())\n            .build();\n    }\n\n    @Override\n    public boolean isAddDynamicField() {\n        return true;\n    }\n}\n"
  },
  {
    "path": "plugins/kafka/src/main/java/com/megaease/easeagent/plugin/kafka/advice/KafkaProducerConfigAdvice.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.kafka.advice;\n\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.Set;\n\nimport static com.megaease.easeagent.plugin.tools.matcher.ClassMatcherUtils.name;\n\npublic class KafkaProducerConfigAdvice implements Points {\n    public static final String CONFIG_NAME = \"org.apache.kafka.clients.producer.ProducerConfig\";\n\n    //        return def.type(named(\"org.apache.kafka.clients.producer.ProducerConfig\")\n    //                .or(hasSuperType(named(\"org.apache.kafka.clients.producer.ProducerConfig\")))\n    //            )\n    //            .transform(objConstruct(isConstructor())\n    //            )\n    //            .end()\n    //            ;\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return name(CONFIG_NAME)\n            .or(ClassMatcher.builder().hasSuperClass(CONFIG_NAME).build());\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.multiBuilder()\n            .match(MethodMatcher.builder().named(\"<init>\")\n                .qualifier(\"constructor\")\n                .build())\n            .build();\n    }\n}\n"
  },
  {
    "path": "plugins/kafka/src/main/java/com/megaease/easeagent/plugin/kafka/interceptor/AsyncCallback.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.kafka.interceptor;\n\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport org.apache.kafka.clients.producer.Callback;\n\npublic abstract class AsyncCallback implements Callback {\n    protected final Callback delegate;\n    private final boolean async;\n\n    public AsyncCallback(Callback delegate) {\n        this.delegate = delegate;\n        this.async = isAsync(delegate);\n    }\n\n    public boolean isAsync() {\n        return async;\n    }\n\n    public static Callback callback(MethodInfo methodInfo) {\n        Object arg1 = methodInfo.getArgs()[1];\n        if (arg1 == null) {\n            return null;\n        }\n        return (Callback) arg1;\n    }\n\n\n    public static boolean isAsync(Callback callback) {\n        if (callback == null) {\n            return false;\n        }\n        if (callback instanceof AsyncCallback) {\n            return ((AsyncCallback) callback).isAsync();\n        }\n        return true;\n    }\n}\n"
  },
  {
    "path": "plugins/kafka/src/main/java/com/megaease/easeagent/plugin/kafka/interceptor/KafkaUtils.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.kafka.interceptor;\n\nimport com.megaease.easeagent.plugin.api.Context;\nimport org.apache.kafka.clients.consumer.ConsumerRecord;\nimport org.apache.kafka.clients.producer.ProducerRecord;\nimport org.apache.kafka.common.header.Header;\nimport org.apache.kafka.common.header.Headers;\n\nimport java.util.HashMap;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.Map;\n\npublic class KafkaUtils {\n    public static String getUri(Object bootstrapServers) {\n        String uri = null;\n        if (bootstrapServers instanceof String) {\n            uri = (String) bootstrapServers;\n        } else if (bootstrapServers instanceof List) {\n            @SuppressWarnings(\"unchecked\")\n            List<String> serverConfig = (List<String>) bootstrapServers;\n            uri = String.join(\",\", serverConfig);\n        }\n        return uri;\n    }\n\n    // We can't just skip clearing headers we use because we might consumerInject B3 single, yet have stale B3\n    // multi, or visa versa.\n    public static Map<String, String> clearHeaders(Context context, ConsumerRecord<?, ?> record) {\n        Map<String, String> result = null;\n        Headers headers = record.headers();\n        // Headers::remove creates and consumes an iterator each time. This does one loop instead.\n        for (Iterator<Header> i = headers.iterator(); i.hasNext(); ) {\n            Header next = i.next();\n            String key = next.key();\n            if (context.isNecessaryKeys(key)) {\n                if (result == null) {\n                    result = new HashMap<>();\n                }\n                result.put(key, new String(next.value()));\n                i.remove();\n            }\n        }\n        return result;\n    }\n\n    public static String getTopic(ProducerRecord producerRecord) {\n        return producerRecord.topic();\n    }\n\n}\n"
  },
  {
    "path": "plugins/kafka/src/main/java/com/megaease/easeagent/plugin/kafka/interceptor/initialize/ConsumerRecordInterceptor.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.kafka.interceptor.initialize;\n\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.kafka.KafkaPlugin;\nimport com.megaease.easeagent.plugin.kafka.advice.KafkaConsumerRecordAdvice;\n\n@AdviceTo(value = KafkaConsumerRecordAdvice.class, plugin = KafkaPlugin.class)\npublic class ConsumerRecordInterceptor implements Interceptor {\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n\n    }\n\n    @Override\n    public int order() {\n        return Order.LOWEST.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/kafka/src/main/java/com/megaease/easeagent/plugin/kafka/interceptor/initialize/KafkaConsumerConstructInterceptor.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.kafka.interceptor.initialize;\n\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.field.AgentDynamicFieldAccessor;\nimport com.megaease.easeagent.plugin.interceptor.NonReentrantInterceptor;\nimport com.megaease.easeagent.plugin.kafka.KafkaPlugin;\nimport com.megaease.easeagent.plugin.kafka.advice.KafkaConsumerAdvice;\nimport com.megaease.easeagent.plugin.kafka.interceptor.KafkaUtils;\nimport org.apache.kafka.clients.consumer.ConsumerConfig;\n\nimport java.util.List;\nimport java.util.Map;\n\n@AdviceTo(value = KafkaConsumerAdvice.class, qualifier = \"constructor\", plugin = KafkaPlugin.class)\npublic class KafkaConsumerConstructInterceptor implements NonReentrantInterceptor {\n\n    @Override\n    public void doAfter(MethodInfo methodInfo, Context context) {\n        Object invoker = methodInfo.getInvoker();\n        Object configObj = methodInfo.getArgs()[0];\n        String uri;\n        if (configObj instanceof Map) {\n            uri = KafkaUtils.getUri(((Map) configObj).get(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG));\n        } else {\n            ConsumerConfig config = (ConsumerConfig) methodInfo.getArgs()[0];\n            List<String> list = config.getList(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG);\n            uri = String.join(\",\", list);\n        }\n        AgentDynamicFieldAccessor.setDynamicFieldValue(invoker, uri);\n    }\n\n    @Override\n    public int order() {\n        return Order.TRACING_INIT.getOrder();\n    }\n\n    @Override\n    public String getType() {\n        return Order.TRACING.getName();\n    }\n}\n"
  },
  {
    "path": "plugins/kafka/src/main/java/com/megaease/easeagent/plugin/kafka/interceptor/initialize/KafkaConsumerPollInterceptor.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.kafka.interceptor.initialize;\n\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.field.AgentDynamicFieldAccessor;\nimport com.megaease.easeagent.plugin.interceptor.NonReentrantInterceptor;\nimport com.megaease.easeagent.plugin.kafka.KafkaPlugin;\nimport com.megaease.easeagent.plugin.kafka.advice.KafkaConsumerAdvice;\nimport org.apache.kafka.clients.consumer.Consumer;\nimport org.apache.kafka.clients.consumer.ConsumerRecord;\nimport org.apache.kafka.clients.consumer.ConsumerRecords;\n\n@AdviceTo(value = KafkaConsumerAdvice.class, qualifier = \"poll\", plugin = KafkaPlugin.class)\npublic class KafkaConsumerPollInterceptor implements NonReentrantInterceptor {\n    @Override\n    public void doAfter(MethodInfo methodInfo, Context context) {\n        ConsumerRecords<?, ?> consumerRecords = (ConsumerRecords<?, ?>) methodInfo.getRetValue();\n        if (consumerRecords == null || consumerRecords.isEmpty()) {\n            return;\n        }\n        Consumer<?, ?> consumer = (Consumer<?, ?>) methodInfo.getInvoker();\n        String uri = AgentDynamicFieldAccessor.getDynamicFieldValue(consumer);\n        for (ConsumerRecord<?, ?> consumerRecord : consumerRecords) {\n            AgentDynamicFieldAccessor.setDynamicFieldValue(consumerRecord, uri);\n        }\n    }\n\n    @Override\n    public int order() {\n        return Order.TRACING_INIT.getOrder();\n    }\n\n    @Override\n    public String getType() {\n        return Order.TRACING.getName();\n    }\n}\n"
  },
  {
    "path": "plugins/kafka/src/main/java/com/megaease/easeagent/plugin/kafka/interceptor/initialize/KafkaProducerConstructInterceptor.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.kafka.interceptor.initialize;\n\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.field.AgentDynamicFieldAccessor;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\nimport com.megaease.easeagent.plugin.interceptor.NonReentrantInterceptor;\nimport com.megaease.easeagent.plugin.kafka.KafkaPlugin;\nimport com.megaease.easeagent.plugin.kafka.advice.KafkaProducerAdvice;\nimport com.megaease.easeagent.plugin.kafka.interceptor.KafkaUtils;\nimport org.apache.kafka.clients.consumer.ConsumerConfig;\nimport org.apache.kafka.clients.producer.ProducerConfig;\n\nimport java.util.Map;\n\n@AdviceTo(value = KafkaProducerAdvice.class, qualifier = \"constructor\", plugin = KafkaPlugin.class)\npublic class KafkaProducerConstructInterceptor implements NonReentrantInterceptor {\n\n    @Override\n    public void doAfter(MethodInfo methodInfo, Context context) {\n        Object invoker = methodInfo.getInvoker();\n        String uri = getUri(methodInfo);\n        AgentDynamicFieldAccessor.setDynamicFieldValue(invoker, uri);\n    }\n\n    private String getUri(MethodInfo methodInfo) {\n        Object arg0 = methodInfo.getArgs()[0];\n        ProducerConfig producerConfig = AgentFieldReflectAccessor.getFieldValue(methodInfo.getInvoker(), \"producerConfig\");\n        if (producerConfig == null && arg0 instanceof ProducerConfig) {\n            producerConfig = (ProducerConfig) arg0;\n        }\n        Object bootstrapServers = null;\n        if (producerConfig != null) {\n            bootstrapServers = producerConfig.getList(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG);\n        } else if (arg0 instanceof Map) {\n            bootstrapServers = ((Map) arg0).get(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG);\n        }\n        if (bootstrapServers != null) {\n            return KafkaUtils.getUri(bootstrapServers);\n        }\n        return null;\n    }\n\n    @Override\n    public int order() {\n        return Order.TRACING_INIT.getOrder();\n    }\n\n    @Override\n    public String getType() {\n        return Order.TRACING.getName();\n    }\n}\n"
  },
  {
    "path": "plugins/kafka/src/main/java/com/megaease/easeagent/plugin/kafka/interceptor/metric/KafkaConsumerMetricInterceptor.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.kafka.interceptor.metric;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.metric.ServiceMetricRegistry;\nimport com.megaease.easeagent.plugin.api.metric.Timer;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.interceptor.NonReentrantInterceptor;\nimport com.megaease.easeagent.plugin.kafka.KafkaPlugin;\nimport com.megaease.easeagent.plugin.kafka.advice.KafkaConsumerAdvice;\nimport org.apache.kafka.clients.consumer.ConsumerRecord;\nimport org.apache.kafka.clients.consumer.ConsumerRecords;\n\n@AdviceTo(value = KafkaConsumerAdvice.class, qualifier = \"poll\", plugin = KafkaPlugin.class)\npublic class KafkaConsumerMetricInterceptor implements NonReentrantInterceptor {\n    private static KafkaMetric kafkaMetric;\n\n    @Override\n    public void init(IPluginConfig config, String className, String methodName, String methodDescriptor) {\n        kafkaMetric = ServiceMetricRegistry.getOrCreate(config, KafkaMetric.newTags(), KafkaMetric.KAFKA_METRIC_SUPPLIER);\n    }\n\n    @Override\n    public void doAfter(MethodInfo methodInfo, Context context) {\n        if (!methodInfo.isSuccess()) {\n            return;\n        }\n        ConsumerRecords<?, ?> consumerRecords = (ConsumerRecords<?, ?>) methodInfo.getRetValue();\n        if (consumerRecords == null || consumerRecords.isEmpty()) {\n            return;\n        }\n        for (ConsumerRecord<?, ?> consumerRecord : consumerRecords) {\n            Timer.Context ctx = this.kafkaMetric.consumeStart(consumerRecord.topic());\n            this.kafkaMetric.consumeStop(ctx, consumerRecord.topic());\n        }\n    }\n\n    @Override\n    public String getType() {\n        return Order.METRIC.getName();\n    }\n\n    @Override\n    public int order() {\n        return Order.METRIC.getOrder();\n    }\n\n    public static KafkaMetric getKafkaMetric() {\n        return kafkaMetric;\n    }\n}\n"
  },
  {
    "path": "plugins/kafka/src/main/java/com/megaease/easeagent/plugin/kafka/interceptor/metric/KafkaMessageListenerMetricInterceptor.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.kafka.interceptor.metric;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.metric.ServiceMetricRegistry;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.interceptor.NonReentrantInterceptor;\nimport com.megaease.easeagent.plugin.kafka.KafkaPlugin;\nimport com.megaease.easeagent.plugin.kafka.advice.KafkaMessageListenerAdvice;\nimport org.apache.kafka.clients.consumer.ConsumerRecord;\n\n@AdviceTo(value = KafkaMessageListenerAdvice.class, plugin = KafkaPlugin.class)\npublic class KafkaMessageListenerMetricInterceptor implements NonReentrantInterceptor {\n    protected static final Object START = new Object();\n    private static KafkaMetric kafkaMetric;\n\n    @Override\n    public void init(IPluginConfig config, String className, String methodName, String methodDescriptor) {\n        kafkaMetric = ServiceMetricRegistry.getOrCreate(config, KafkaMetric.newTags(), KafkaMetric.KAFKA_METRIC_SUPPLIER);\n    }\n\n\n    @Override\n    public void doBefore(MethodInfo methodInfo, Context context) {\n        context.put(START, System.currentTimeMillis());\n    }\n\n    @Override\n    public void doAfter(MethodInfo methodInfo, Context context) {\n        ConsumerRecord<?, ?> consumerRecord = (ConsumerRecord<?, ?>) methodInfo.getArgs()[0];\n        this.kafkaMetric.consume(consumerRecord.topic(), context.remove(START), methodInfo.isSuccess());\n    }\n\n    public static KafkaMetric getKafkaMetric() {\n        return kafkaMetric;\n    }\n\n    @Override\n    public String getType() {\n        return Order.METRIC.getName();\n    }\n\n    @Override\n    public int order() {\n        return Order.METRIC.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/kafka/src/main/java/com/megaease/easeagent/plugin/kafka/interceptor/metric/KafkaMetric.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.kafka.interceptor.metric;\n\nimport com.google.common.collect.ImmutableMap;\nimport com.megaease.easeagent.plugin.api.metric.*;\nimport com.megaease.easeagent.plugin.api.metric.name.*;\nimport com.megaease.easeagent.plugin.api.middleware.Redirect;\nimport com.megaease.easeagent.plugin.api.middleware.RedirectProcessor;\n\nimport javax.annotation.Nonnull;\nimport java.util.concurrent.TimeUnit;\n\npublic class KafkaMetric extends ServiceMetric {\n    public static final ServiceMetricSupplier<KafkaMetric> KAFKA_METRIC_SUPPLIER = new ServiceMetricSupplier<KafkaMetric>() {\n        @Override\n        public NameFactory newNameFactory() {\n            return nameFactory();\n        }\n\n        @Override\n        public KafkaMetric newInstance(MetricRegistry metricRegistry, NameFactory nameFactory) {\n            return new KafkaMetric(metricRegistry, nameFactory);\n        }\n    };\n\n    public KafkaMetric(@Nonnull MetricRegistry metricRegistry, @Nonnull NameFactory nameFactory) {\n        super(metricRegistry, nameFactory);\n    }\n\n\n    private void countMeter(String topic, MetricSubType... meterTypes) {\n        for (MetricSubType meterType : meterTypes) {\n            Meter meter = this.metricRegistry.meter(nameFactory.meterName(topic, meterType));\n            if (meter != null) {\n                meter.mark();\n            }\n        }\n    }\n\n    void producerStop(long beginTime, String topic) {\n        countMeter(topic, MetricSubType.PRODUCER);\n        Timer timer = this.metricRegistry.timer(nameFactory.timerName(topic, MetricSubType.PRODUCER));\n        timer.update(System.currentTimeMillis() - beginTime, TimeUnit.MILLISECONDS);\n        Counter counter = metricRegistry.counter(nameFactory.counterName(topic, MetricSubType.PRODUCER));\n        counter.inc();\n    }\n\n    public void errorProducer(String topic) {\n        countMeter(topic, MetricSubType.PRODUCER_ERROR);\n        Counter counter = metricRegistry.counter(this.nameFactory.counterName(topic, MetricSubType.PRODUCER_ERROR));\n        counter.inc();\n    }\n\n    public Timer.Context consumeStart(String topic) {\n        countMeter(topic, MetricSubType.CONSUMER);// meter\n        Timer timer = this.metricRegistry.timer(nameFactory.timerName(topic, MetricSubType.CONSUMER)); //timer\n        return timer.time();\n    }\n\n    public void consumeStop(Timer.Context context, String topic) {\n        context.stop();\n        Counter counter = metricRegistry.counter(nameFactory.counterName(topic, MetricSubType.CONSUMER));\n        counter.inc();\n    }\n\n    public void consumeError(String topic) {\n        countMeter(topic, MetricSubType.CONSUMER_ERROR);\n        Counter errorCounter = metricRegistry.counter(nameFactory.counterName(topic, MetricSubType.CONSUMER_ERROR));\n        errorCounter.inc();\n    }\n\n    public void consume(String topic, long beginTime, boolean success) {\n        countMeter(topic, MetricSubType.CONSUMER);\n        this.metricRegistry.timer(nameFactory.timerName(topic, MetricSubType.CONSUMER)).update(System.currentTimeMillis() - beginTime, TimeUnit.MILLISECONDS);\n        Counter counter = metricRegistry.counter(nameFactory.counterName(topic, MetricSubType.CONSUMER));\n        counter.inc();\n        if (!success) {\n            countMeter(topic, MetricSubType.CONSUMER_ERROR);\n            Counter errorCounter = metricRegistry.counter(nameFactory.counterName(topic, MetricSubType.CONSUMER_ERROR));\n            errorCounter.inc();\n        }\n    }\n\n    @Nonnull\n    public static Tags newTags() {\n        Tags tags = new Tags(\"application\", \"kafka\", \"resource\");\n        RedirectProcessor.setTagsIfRedirected(Redirect.KAFKA, tags);\n        return tags;\n    }\n\n    @Nonnull\n    public static NameFactory nameFactory() {\n        return NameFactory.createBuilder()\n            .counterType(MetricSubType.PRODUCER, ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                .put(MetricField.EXECUTION_PRODUCER_COUNT, MetricValueFetcher.CountingCount)\n                .build())\n            .counterType(MetricSubType.PRODUCER_ERROR, ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                .put(MetricField.EXECUTION_PRODUCER_ERROR_COUNT, MetricValueFetcher.CountingCount)\n                .build())\n            .meterType(MetricSubType.PRODUCER, ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                .put(MetricField.PRODUCER_M1_RATE, MetricValueFetcher.MeteredM1Rate)\n                .put(MetricField.PRODUCER_M5_RATE, MetricValueFetcher.MeteredM5Rate)\n                .put(MetricField.PRODUCER_M15_RATE, MetricValueFetcher.MeteredM15Rate)\n                .build())\n            .meterType(MetricSubType.PRODUCER_ERROR, ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                .put(MetricField.PRODUCER_M1_ERROR_RATE, MetricValueFetcher.MeteredM1Rate)\n                .put(MetricField.PRODUCER_M5_ERROR_RATE, MetricValueFetcher.MeteredM5Rate)\n                .put(MetricField.PRODUCER_M15_ERROR_RATE, MetricValueFetcher.MeteredM15Rate)\n                .build())\n            .timerType(MetricSubType.PRODUCER,\n                ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                    .put(MetricField.PRODUCER_MIN_EXECUTION_TIME, MetricValueFetcher.SnapshotMinValue)\n                    .put(MetricField.PRODUCER_MAX_EXECUTION_TIME, MetricValueFetcher.SnapshotMaxValue)\n                    .put(MetricField.PRODUCER_MEAN_EXECUTION_TIME, MetricValueFetcher.SnapshotMeanValue)\n                    .put(MetricField.PRODUCER_P25_EXECUTION_TIME, MetricValueFetcher.Snapshot25Percentile)\n                    .put(MetricField.PRODUCER_P50_EXECUTION_TIME, MetricValueFetcher.Snapshot50PercentileValue)\n                    .put(MetricField.PRODUCER_P75_EXECUTION_TIME, MetricValueFetcher.Snapshot75PercentileValue)\n                    .put(MetricField.PRODUCER_P95_EXECUTION_TIME, MetricValueFetcher.Snapshot95PercentileValue)\n                    .put(MetricField.PRODUCER_P98_EXECUTION_TIME, MetricValueFetcher.Snapshot98PercentileValue)\n                    .put(MetricField.PRODUCER_P99_EXECUTION_TIME, MetricValueFetcher.Snapshot99PercentileValue)\n                    .put(MetricField.PRODUCER_P999_EXECUTION_TIME, MetricValueFetcher.Snapshot999PercentileValue)\n                    .build())\n            .counterType(MetricSubType.CONSUMER, ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                .put(MetricField.EXECUTION_CONSUMER_COUNT, MetricValueFetcher.CountingCount)\n                .build())\n            .counterType(MetricSubType.CONSUMER_ERROR, ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                .put(MetricField.EXECUTION_CONSUMER_ERROR_COUNT, MetricValueFetcher.CountingCount)\n                .build())\n            .timerType(MetricSubType.CONSUMER,\n                ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                    .put(MetricField.CONSUMER_MIN_EXECUTION_TIME, MetricValueFetcher.SnapshotMinValue)\n                    .put(MetricField.CONSUMER_MAX_EXECUTION_TIME, MetricValueFetcher.SnapshotMaxValue)\n                    .put(MetricField.CONSUMER_MEAN_EXECUTION_TIME, MetricValueFetcher.SnapshotMeanValue)\n                    .put(MetricField.CONSUMER_P25_EXECUTION_TIME, MetricValueFetcher.Snapshot25Percentile)\n                    .put(MetricField.CONSUMER_P50_EXECUTION_TIME, MetricValueFetcher.Snapshot50PercentileValue)\n                    .put(MetricField.CONSUMER_P75_EXECUTION_TIME, MetricValueFetcher.Snapshot75PercentileValue)\n                    .put(MetricField.CONSUMER_P95_EXECUTION_TIME, MetricValueFetcher.Snapshot95PercentileValue)\n                    .put(MetricField.CONSUMER_P98_EXECUTION_TIME, MetricValueFetcher.Snapshot98PercentileValue)\n                    .put(MetricField.CONSUMER_P99_EXECUTION_TIME, MetricValueFetcher.Snapshot99PercentileValue)\n                    .put(MetricField.CONSUMER_P999_EXECUTION_TIME, MetricValueFetcher.Snapshot999PercentileValue)\n                    .build())\n            .meterType(MetricSubType.CONSUMER, ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                .put(MetricField.CONSUMER_M1_RATE, MetricValueFetcher.MeteredM1Rate)\n                .put(MetricField.CONSUMER_M5_RATE, MetricValueFetcher.MeteredM5Rate)\n                .put(MetricField.CONSUMER_M15_RATE, MetricValueFetcher.MeteredM15Rate)\n                .build())\n            .meterType(MetricSubType.CONSUMER_ERROR, ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                .put(MetricField.CONSUMER_M1_ERROR_RATE, MetricValueFetcher.MeteredM1Rate)\n                .put(MetricField.CONSUMER_M5_ERROR_RATE, MetricValueFetcher.MeteredM5Rate)\n                .put(MetricField.CONSUMER_M15_ERROR_RATE, MetricValueFetcher.MeteredM15Rate)\n                .build())\n\n            .build();\n    }\n}\n"
  },
  {
    "path": "plugins/kafka/src/main/java/com/megaease/easeagent/plugin/kafka/interceptor/metric/KafkaProducerMetricInterceptor.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.kafka.interceptor.metric;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.metric.ServiceMetricRegistry;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.interceptor.NonReentrantInterceptor;\nimport com.megaease.easeagent.plugin.kafka.KafkaPlugin;\nimport com.megaease.easeagent.plugin.kafka.advice.KafkaProducerAdvice;\nimport com.megaease.easeagent.plugin.kafka.interceptor.AsyncCallback;\nimport com.megaease.easeagent.plugin.kafka.interceptor.KafkaUtils;\nimport org.apache.kafka.clients.producer.ProducerRecord;\n\n@AdviceTo(value = KafkaProducerAdvice.class, qualifier = \"doSend\", plugin = KafkaPlugin.class)\npublic class KafkaProducerMetricInterceptor implements NonReentrantInterceptor {\n    private static volatile KafkaMetric kafkaMetric;\n\n\n    @Override\n    public void init(IPluginConfig config, String className, String methodName, String methodDescriptor) {\n        kafkaMetric = ServiceMetricRegistry.getOrCreate(config, KafkaMetric.newTags(), KafkaMetric.KAFKA_METRIC_SUPPLIER);\n    }\n\n    @Override\n    public void doBefore(MethodInfo methodInfo, Context context) {\n        MetricCallback metricCallback = new MetricCallback(AsyncCallback.callback(methodInfo), KafkaUtils.getTopic((ProducerRecord) methodInfo.getArgs()[0]), kafkaMetric);\n        methodInfo.changeArg(1, metricCallback);\n    }\n\n    @Override\n    public void doAfter(MethodInfo methodInfo, Context context) {\n        if (AsyncCallback.isAsync(AsyncCallback.callback(methodInfo))) {\n            return;\n        }\n        processSync(methodInfo);\n\n    }\n\n    private void processSync(MethodInfo methodInfo) {\n        ProducerRecord<?, ?> producerRecord = (ProducerRecord<?, ?>) methodInfo.getArgs()[0];\n        if (!methodInfo.isSuccess()) {\n            this.kafkaMetric.errorProducer(producerRecord.topic());\n        }\n    }\n\n    @Override\n    public String getType() {\n        return Order.METRIC.getName();\n    }\n\n    @Override\n    public int order() {\n        return Order.METRIC.getOrder();\n    }\n\n    public static KafkaMetric getKafkaMetric() {\n        return kafkaMetric;\n    }\n}\n"
  },
  {
    "path": "plugins/kafka/src/main/java/com/megaease/easeagent/plugin/kafka/interceptor/metric/MetricCallback.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.kafka.interceptor.metric;\n\nimport com.megaease.easeagent.plugin.kafka.interceptor.AsyncCallback;\nimport org.apache.kafka.clients.producer.Callback;\nimport org.apache.kafka.clients.producer.RecordMetadata;\n\npublic class MetricCallback extends AsyncCallback {\n    private final long start;\n    private final String topic;\n    private final KafkaMetric kafkaMetric;\n\n    public MetricCallback(Callback delegate, String topic, KafkaMetric kafkaMetric) {\n        super(delegate);\n        this.topic = topic;\n        this.kafkaMetric = kafkaMetric;\n        this.start = System.currentTimeMillis();\n    }\n\n    @Override\n    public void onCompletion(RecordMetadata metadata, Exception exception) {\n        try {\n            this.kafkaMetric.producerStop(start, topic);\n            if (exception != null) {\n                this.kafkaMetric.errorProducer(topic);\n            }\n        } finally {\n            if (this.delegate != null) {\n                this.delegate.onCompletion(metadata, exception);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/kafka/src/main/java/com/megaease/easeagent/plugin/kafka/interceptor/redirect/KafkaAbstractConfigConstructInterceptor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.kafka.interceptor.redirect;\n\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.logging.Logger;\nimport com.megaease.easeagent.plugin.api.middleware.Redirect;\nimport com.megaease.easeagent.plugin.api.middleware.RedirectProcessor;\nimport com.megaease.easeagent.plugin.api.middleware.ResourceConfig;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.NonReentrantInterceptor;\n\nimport java.util.Map;\nimport java.util.Properties;\n\npublic class KafkaAbstractConfigConstructInterceptor implements NonReentrantInterceptor {\n    private static final Logger LOGGER = EaseAgent.getLogger(KafkaAbstractConfigConstructInterceptor.class);\n    private static final String BOOTSRAP_CONFIG = \"bootstrap.servers\";\n\n    @Override\n    public void doBefore(MethodInfo methodInfo, Context context) {\n        ResourceConfig cnf = Redirect.KAFKA.getConfig();\n        if (cnf == null) {\n            return;\n        }\n        if (methodInfo.getArgs()[0] instanceof Properties) {\n            Properties properties = (Properties) methodInfo.getArgs()[0];\n            LOGGER.info(\"Redirect Kafka uris: {} to {}\", properties.getProperty(BOOTSRAP_CONFIG), cnf.getUris());\n            properties.put(BOOTSRAP_CONFIG, cnf.getUris());\n            methodInfo.changeArg(0, properties);\n            RedirectProcessor.redirected(Redirect.KAFKA, cnf.getUris());\n        } else if (methodInfo.getArgs()[0] instanceof Map) {\n            @SuppressWarnings(\"unchecked\")\n            Map<String, Object> map = (Map<String, Object>) methodInfo.getArgs()[0];\n            LOGGER.info(\"Redirect Kafka uris: {} to {}\", map.get(BOOTSRAP_CONFIG), cnf.getUris());\n            map.put(BOOTSRAP_CONFIG, cnf.getUris());\n            methodInfo.changeArg(0, map);\n            RedirectProcessor.redirected(Redirect.KAFKA, cnf.getUris());\n        }\n    }\n\n\n    @Override\n    public String getType() {\n        return Order.REDIRECT.getName();\n    }\n\n    @Override\n    public int order() {\n        return Order.REDIRECT.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/kafka/src/main/java/com/megaease/easeagent/plugin/kafka/interceptor/redirect/KafkaConsumerConfigConstructInterceptor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.kafka.interceptor.redirect;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.kafka.KafkaRedirectPlugin;\nimport com.megaease.easeagent.plugin.kafka.advice.KafkaConsumerConfigAdvice;\n\n@AdviceTo(value = KafkaConsumerConfigAdvice.class, qualifier = \"constructor\", plugin = KafkaRedirectPlugin.class)\npublic class KafkaConsumerConfigConstructInterceptor extends KafkaAbstractConfigConstructInterceptor {\n}\n"
  },
  {
    "path": "plugins/kafka/src/main/java/com/megaease/easeagent/plugin/kafka/interceptor/redirect/KafkaProducerConfigConstructInterceptor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.kafka.interceptor.redirect;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.kafka.KafkaRedirectPlugin;\nimport com.megaease.easeagent.plugin.kafka.advice.KafkaProducerConfigAdvice;\n\n@AdviceTo(value = KafkaProducerConfigAdvice.class, qualifier = \"constructor\", plugin = KafkaRedirectPlugin.class)\npublic class KafkaProducerConfigConstructInterceptor extends KafkaAbstractConfigConstructInterceptor {\n}\n"
  },
  {
    "path": "plugins/kafka/src/main/java/com/megaease/easeagent/plugin/kafka/interceptor/tracing/KafkaConsumerRequest.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.kafka.interceptor.tracing;\n\nimport com.megaease.easeagent.plugin.api.trace.MessagingRequest;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport org.apache.kafka.clients.consumer.ConsumerRecord;\n\nimport java.util.Map;\n\npublic class KafkaConsumerRequest implements MessagingRequest {\n    private final Map<String, String> headers;\n    private final ConsumerRecord<?, ?> delegate;\n\n    public KafkaConsumerRequest(Map<String, String> headers, ConsumerRecord<?, ?> delegate) {\n        this.headers = headers;\n        this.delegate = delegate;\n    }\n\n    @Override\n    public String operation() {\n        return \"receive\";\n    }\n\n    @Override\n    public String channelKind() {\n        return null;\n    }\n\n    @Override\n    public String channelName() {\n        return null;\n    }\n\n    @Override\n    public Object unwrap() {\n        return delegate;\n    }\n\n    @Override\n    public Span.Kind kind() {\n        return Span.Kind.CONSUMER;\n    }\n\n    @Override\n    public String header(String name) {\n        return headers == null ? null : headers.get(name);\n    }\n\n    @Override\n    public String name() {\n        return \"poll\";\n    }\n\n    @Override\n    public boolean cacheScope() {\n        return false;\n    }\n\n    @Override\n    public void setHeader(String name, String value) {\n        KafkaHeaders.replaceHeader(delegate.headers(), name, value);\n    }\n}\n"
  },
  {
    "path": "plugins/kafka/src/main/java/com/megaease/easeagent/plugin/kafka/interceptor/tracing/KafkaConsumerTracingInterceptor.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.kafka.interceptor.tracing;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.middleware.MiddlewareConstants;\nimport com.megaease.easeagent.plugin.api.middleware.Redirect;\nimport com.megaease.easeagent.plugin.api.middleware.RedirectProcessor;\nimport com.megaease.easeagent.plugin.api.middleware.Type;\nimport com.megaease.easeagent.plugin.api.trace.MessagingRequest;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.field.AgentDynamicFieldAccessor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.interceptor.NonReentrantInterceptor;\nimport com.megaease.easeagent.plugin.kafka.KafkaPlugin;\nimport com.megaease.easeagent.plugin.kafka.advice.KafkaConsumerAdvice;\nimport com.megaease.easeagent.plugin.kafka.interceptor.KafkaUtils;\nimport org.apache.kafka.clients.consumer.Consumer;\nimport org.apache.kafka.clients.consumer.ConsumerRecord;\nimport org.apache.kafka.clients.consumer.ConsumerRecords;\n\nimport java.util.Iterator;\nimport java.util.LinkedHashMap;\nimport java.util.Map;\n\n@AdviceTo(value = KafkaConsumerAdvice.class, qualifier = \"poll\", plugin = KafkaPlugin.class)\npublic class KafkaConsumerTracingInterceptor implements NonReentrantInterceptor {\n    protected static final String REMOTE_SERVICE_NAME = \"kafka\";\n    boolean singleRootSpanOnReceiveBatch = true;\n\n    @Override\n    public void doAfter(MethodInfo methodInfo, Context context) {\n        if (!methodInfo.isSuccess() || context.currentTracing().isNoop()) {\n            return;\n        }\n        ConsumerRecords<?, ?> consumerRecords = (ConsumerRecords<?, ?>) methodInfo.getRetValue();\n        if (consumerRecords == null || consumerRecords.isEmpty()) {\n            return;\n        }\n        Consumer<?, ?> consumer = (Consumer<?, ?>) methodInfo.getInvoker();\n        String uri = AgentDynamicFieldAccessor.getDynamicFieldValue(consumer);\n        afterPoll(context, consumerRecords, uri);\n    }\n\n    void afterPoll(Context context, ConsumerRecords<?, ?> records, String uri) {\n        Iterator<? extends ConsumerRecord<?, ?>> iterator = records.iterator();\n        Map<String, Span> consumerSpansForTopic = new LinkedHashMap<>();\n        while (iterator.hasNext()) {\n            ConsumerRecord<?, ?> record = iterator.next();\n            String topic = record.topic();\n            Map<String, String> headers = KafkaUtils.clearHeaders(context, record);\n            MessagingRequest request = new KafkaConsumerRequest(headers, record);\n            if (headers == null && singleRootSpanOnReceiveBatch) {\n                Span span = consumerSpansForTopic.get(topic);\n                if (span == null) {\n                    span = context.consumerSpan(request);\n                    if (!span.isNoop()) {\n                        setConsumerSpan(topic, uri, span);\n                        // incur timestamp overhead only once\n                        span.start();\n                    }\n                    consumerSpansForTopic.put(topic, span);\n                }\n                context.consumerInject(span, request);\n            } else {\n                Span span = context.consumerSpan(request);\n                if (!span.isNoop()) {\n                    setConsumerSpan(topic, uri, span);\n                    // incur timestamp overhead only once\n                    span.start().finish(); // span won't be shared by other records\n                    context.consumerInject(span, request);\n                }\n            }\n        }\n        for (Span span : consumerSpansForTopic.values()) span.finish();\n    }\n\n    void setConsumerSpan(String topic, String uri, Span span) {\n        span.tag(KafkaTags.KAFKA_TOPIC_TAG, topic);\n        span.tag(KafkaTags.KAFKA_BROKER_TAG, uri);\n        span.tag(MiddlewareConstants.TYPE_TAG_NAME, Type.KAFKA.getRemoteType());\n        RedirectProcessor.setTagsIfRedirected(Redirect.KAFKA, span, uri);\n        span.remoteServiceName(REMOTE_SERVICE_NAME);\n    }\n\n    @Override\n    public int order() {\n        return Order.TRACING.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/kafka/src/main/java/com/megaease/easeagent/plugin/kafka/interceptor/tracing/KafkaHeaders.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.kafka.interceptor.tracing;\n\nimport com.megaease.easeagent.plugin.api.logging.Logger;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport org.apache.kafka.common.header.Header;\nimport org.apache.kafka.common.header.Headers;\n\nimport static java.nio.charset.StandardCharsets.UTF_8;\n\n/**\n * copy from zipkin.kafka.brave\n */\nfinal class KafkaHeaders {\n    private static final Logger LOGGER = EaseAgent.getLogger(KafkaHeaders.class);\n\n    static void replaceHeader(Headers headers, String key, String value) {\n        try {\n            headers.remove(key);\n            headers.add(key, value.getBytes(UTF_8));\n        } catch (IllegalStateException e) {\n            LOGGER.warn(\"error setting header {0} in headers {1}\", key, headers, e);\n        }\n    }\n\n    static String lastStringHeader(Headers headers, String key) {\n        Header header = headers.lastHeader(key);\n        if (header == null || header.value() == null) return null;\n        return new String(header.value(), UTF_8);\n    }\n\n    KafkaHeaders() {\n    }\n}\n"
  },
  {
    "path": "plugins/kafka/src/main/java/com/megaease/easeagent/plugin/kafka/interceptor/tracing/KafkaMessageListenerTracingInterceptor.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.kafka.interceptor.tracing;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.middleware.MiddlewareConstants;\nimport com.megaease.easeagent.plugin.api.middleware.Redirect;\nimport com.megaease.easeagent.plugin.api.middleware.RedirectProcessor;\nimport com.megaease.easeagent.plugin.api.middleware.Type;\nimport com.megaease.easeagent.plugin.api.trace.MessagingRequest;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.field.AgentDynamicFieldAccessor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.interceptor.NonReentrantInterceptor;\nimport com.megaease.easeagent.plugin.kafka.KafkaPlugin;\nimport com.megaease.easeagent.plugin.kafka.advice.KafkaMessageListenerAdvice;\nimport com.megaease.easeagent.plugin.kafka.interceptor.KafkaUtils;\nimport org.apache.kafka.clients.consumer.ConsumerRecord;\n\nimport java.util.Map;\n\n@AdviceTo(value = KafkaMessageListenerAdvice.class, plugin = KafkaPlugin.class)\npublic class KafkaMessageListenerTracingInterceptor implements NonReentrantInterceptor {\n    protected static final Object SPAN = new Object();\n\n    @Override\n    public void doBefore(MethodInfo methodInfo, Context context) {\n        ConsumerRecord<?, ?> consumerRecord = (ConsumerRecord<?, ?>) methodInfo.getArgs()[0];\n        String uri = AgentDynamicFieldAccessor.getDynamicFieldValue(consumerRecord);\n        Map<String, String> headers = KafkaUtils.clearHeaders(context, consumerRecord);\n        MessagingRequest request = new KafkaConsumerRequest(headers, consumerRecord);\n        Span span = context.consumerSpan(request).name(\"on-message\")\n            .kind(Span.Kind.CLIENT)\n            .remoteServiceName(\"kafka\")\n            .tag(KafkaTags.KAFKA_BROKER_TAG, uri)\n            .cacheScope()\n            .start();\n        span.tag(MiddlewareConstants.TYPE_TAG_NAME, Type.KAFKA.getRemoteType());\n        RedirectProcessor.setTagsIfRedirected(Redirect.KAFKA, span, uri);\n        context.put(SPAN, span);\n    }\n\n    @Override\n    public void doAfter(MethodInfo methodInfo, Context context) {\n        Span span = context.remove(SPAN);\n        if (span == null) {\n            return;\n        }\n        try {\n            if (!methodInfo.isSuccess()) {\n                span.error(methodInfo.getThrowable());\n            }\n        } finally {\n            span.finish();\n        }\n    }\n\n    @Override\n    public int order() {\n        return Order.TRACING.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/kafka/src/main/java/com/megaease/easeagent/plugin/kafka/interceptor/tracing/KafkaProducerDoSendInterceptor.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.kafka.interceptor.tracing;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.middleware.MiddlewareConstants;\nimport com.megaease.easeagent.plugin.api.middleware.Redirect;\nimport com.megaease.easeagent.plugin.api.middleware.RedirectProcessor;\nimport com.megaease.easeagent.plugin.api.middleware.Type;\nimport com.megaease.easeagent.plugin.api.trace.Scope;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.field.AgentDynamicFieldAccessor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.interceptor.NonReentrantInterceptor;\nimport com.megaease.easeagent.plugin.kafka.KafkaPlugin;\nimport com.megaease.easeagent.plugin.kafka.advice.KafkaProducerAdvice;\nimport com.megaease.easeagent.plugin.kafka.interceptor.AsyncCallback;\nimport org.apache.kafka.clients.producer.Callback;\nimport org.apache.kafka.clients.producer.KafkaProducer;\nimport org.apache.kafka.clients.producer.ProducerRecord;\n\n@AdviceTo(value = KafkaProducerAdvice.class, qualifier = \"doSend\", plugin = KafkaPlugin.class)\npublic class KafkaProducerDoSendInterceptor implements NonReentrantInterceptor {\n    protected static final String REMOTE_SERVICE_NAME = \"kafka\";\n    protected static final Object SCOPE = new Object();\n    protected static final Object SPAN = new Object();\n\n    @Override\n    public void doBefore(MethodInfo methodInfo, Context context) {\n        KafkaProducer<?, ?> producer = (KafkaProducer<?, ?>) methodInfo.getInvoker();\n        String uri = AgentDynamicFieldAccessor.getDynamicFieldValue(producer);\n        ProducerRecord<?, ?> record = (ProducerRecord<?, ?>) methodInfo.getArgs()[0];\n        KafkaProducerRequest request = new KafkaProducerRequest(record);\n        Span span = context.producerSpan(request);\n        if (span.isNoop()) {\n            return;\n        }\n        span.tag(KafkaTags.KAFKA_BROKER_TAG, uri);\n        span.kind(Span.Kind.PRODUCER).name(\"send\");\n        if (REMOTE_SERVICE_NAME != null) span.remoteServiceName(REMOTE_SERVICE_NAME);\n        if (record.key() instanceof String && !\"\".equals(record.key())) {\n            span.tag(KafkaTags.KAFKA_KEY_TAG, record.key().toString());\n        }\n        span.tag(KafkaTags.KAFKA_TOPIC_TAG, record.topic());\n        span.tag(MiddlewareConstants.TYPE_TAG_NAME, Type.KAFKA.getRemoteType());\n        RedirectProcessor.setTagsIfRedirected(Redirect.KAFKA, span, uri);\n        span.start();\n        context.put(SCOPE, span.maybeScope());\n        context.put(SPAN, span);\n        Callback tracingCallback = new TraceCallback(span, AsyncCallback.callback(methodInfo));\n        methodInfo.changeArg(1, tracingCallback);\n    }\n\n    @Override\n    public void doAfter(MethodInfo methodInfo, Context context) {\n        Scope scope = context.remove(SCOPE);\n        Span span = context.remove(SPAN);\n        if (scope == null) {\n            return;\n        }\n        try {\n            if (AsyncCallback.isAsync(AsyncCallback.callback(methodInfo))) {\n                return;\n            }\n            if (!methodInfo.isSuccess()) {\n                span.error(methodInfo.getThrowable()).finish();\n            }\n        } finally {\n            scope.close();\n        }\n\n    }\n\n    @Override\n    public int order() {\n        return Order.TRACING.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/kafka/src/main/java/com/megaease/easeagent/plugin/kafka/interceptor/tracing/KafkaProducerRequest.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.megaease.easeagent.plugin.kafka.interceptor.tracing;\n\nimport com.megaease.easeagent.plugin.api.trace.MessagingRequest;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport org.apache.kafka.clients.producer.ProducerRecord;\n\n\n/**\n * copy from zipkin.kafka.brave\n */\n// intentionally not yet public until we add tag parsing functionality\nfinal class KafkaProducerRequest implements MessagingRequest {\n//    static final RemoteGetter<KafkaProducerRequest> GETTER =\n//            new RemoteGetter<KafkaProducerRequest>() {\n//                @Override\n//                public Kind spanKind() {\n//                    return Kind.PRODUCER;\n//                }\n//\n//                @Override\n//                public String get(KafkaProducerRequest request, String name) {\n//                    return lastStringHeader(request.delegate.headers(), name);\n//                }\n//\n//                @Override\n//                public String toString() {\n//                    return \"Headers::lastHeader\";\n//                }\n//            };\n//\n//    static final RemoteSetter<KafkaProducerRequest> SETTER =\n//            new RemoteSetter<KafkaProducerRequest>() {\n//                @Override\n//                public Kind spanKind() {\n//                    return Kind.PRODUCER;\n//                }\n//\n//                @Override\n//                public void put(KafkaProducerRequest request, String name, String value) {\n//                    KafkaHeaders.replaceHeader(request.delegate.headers(), name, value);\n//                }\n//\n//                @Override\n//                public String toString() {\n//                    return \"Headers::replace\";\n//                }\n//            };\n\n    final ProducerRecord<?, ?> delegate;\n\n    KafkaProducerRequest(ProducerRecord<?, ?> delegate) {\n        if (delegate == null) throw new NullPointerException(\"delegate == null\");\n        this.delegate = delegate;\n    }\n\n\n    @Override\n    public Object unwrap() {\n        return delegate;\n    }\n\n    @Override\n    public String operation() {\n        return null;\n    }\n\n    @Override\n    public String channelKind() {\n        return null;\n    }\n\n    @Override\n    public String channelName() {\n        return null;\n    }\n\n    @Override\n    public Span.Kind kind() {\n        return Span.Kind.PRODUCER;\n    }\n\n    @Override\n    public String header(String name) {\n        return KafkaHeaders.lastStringHeader(delegate.headers(), name);\n    }\n\n    @Override\n    public String name() {\n        return \"send\";\n    }\n\n    @Override\n    public boolean cacheScope() {\n        return false;\n    }\n\n    @Override\n    public void setHeader(String name, String value) {\n        KafkaHeaders.replaceHeader(delegate.headers(), name, value);\n    }\n}\n"
  },
  {
    "path": "plugins/kafka/src/main/java/com/megaease/easeagent/plugin/kafka/interceptor/tracing/KafkaTags.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.kafka.interceptor.tracing;\n\n/**\n * Tagging policy is not yet dynamic. The descriptions below reflect static policy.\n *\n * copy from zipkin.kafka.brave\n */\nfinal class KafkaTags {\n  static final String KAFKA_KEY_TAG = \"kafka.key\";\n  static final String KAFKA_TOPIC_TAG = \"kafka.topic\";\n  static final String KAFKA_BROKER_TAG = \"kafka.broker\";\n}\n"
  },
  {
    "path": "plugins/kafka/src/main/java/com/megaease/easeagent/plugin/kafka/interceptor/tracing/TraceCallback.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.kafka.interceptor.tracing;\n\nimport com.megaease.easeagent.plugin.api.trace.Scope;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.kafka.interceptor.AsyncCallback;\nimport org.apache.kafka.clients.producer.Callback;\nimport org.apache.kafka.clients.producer.RecordMetadata;\n\npublic class TraceCallback extends AsyncCallback {\n    final Span span;\n\n    public TraceCallback(Span span, Callback delegate) {\n        super(delegate);\n        this.span = span;\n    }\n\n    @Override\n    public void onCompletion(RecordMetadata metadata, Exception exception) {\n        if (exception != null) span.error(exception);\n        if (this.delegate == null) {\n            span.finish();\n            return;\n        }\n        try (Scope ignored = span.maybeScope()) {\n            this.delegate.onCompletion(metadata, exception);\n        } finally {\n            span.finish();\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/kafka/src/test/java/com/megaease/easeagent/plugin/kafka/interceptor/AsyncCallbackTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.kafka.interceptor;\n\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport org.apache.kafka.clients.producer.Callback;\nimport org.apache.kafka.clients.producer.RecordMetadata;\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\npublic class AsyncCallbackTest {\n\n    @Test\n    public void isAsync() {\n        AsyncCallback asyncCallback = new MockAsyncCallback((metadata, exception) -> {\n        });\n        assertTrue(asyncCallback.isAsync());\n        asyncCallback = new MockAsyncCallback(null);\n        assertFalse(asyncCallback.isAsync());\n    }\n\n    @Test\n    public void callback() {\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{null, null}).build();\n        assertNull(AsyncCallback.callback(methodInfo));\n        Callback callback = (metadata, exception) -> {\n        };\n        methodInfo = MethodInfo.builder().args(new Object[]{null, callback}).build();\n        assertSame(callback, AsyncCallback.callback(methodInfo));\n    }\n\n    @Test\n    public void isAsync1() {\n        assertFalse(AsyncCallback.isAsync(null));\n        Callback callback = (metadata, exception) -> {\n        };\n        assertTrue(AsyncCallback.isAsync(callback));\n        AsyncCallback asyncCallback = new MockAsyncCallback(callback);\n        assertTrue(AsyncCallback.isAsync(asyncCallback));\n        asyncCallback = new MockAsyncCallback(null);\n        assertFalse(AsyncCallback.isAsync(asyncCallback));\n    }\n\n    class MockAsyncCallback extends AsyncCallback {\n\n        public MockAsyncCallback(Callback delegate) {\n            super(delegate);\n        }\n\n        @Override\n        public void onCompletion(RecordMetadata metadata, Exception exception) {\n\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/kafka/src/test/java/com/megaease/easeagent/plugin/kafka/interceptor/KafkaTestUtils.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.kafka.interceptor;\n\nimport com.megaease.easeagent.mock.utils.MockSystemEnv;\nimport com.megaease.easeagent.plugin.api.middleware.MiddlewareConstants;\nimport com.megaease.easeagent.plugin.api.middleware.Redirect;\nimport com.megaease.easeagent.plugin.api.middleware.ResourceConfig;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\n\npublic class KafkaTestUtils {\n    public static void mockRedirect(Runnable r) {\n        ResourceConfig oldConfig = Redirect.KAFKA.getConfig();\n        try {\n            MockSystemEnv.set(MiddlewareConstants.ENV_KAFKA, String.format(\"{\\\"uris\\\":\\\"%s\\\"}\", TestConst.REDIRECT_URIS));\n            AgentFieldReflectAccessor.setFieldValue(Redirect.KAFKA, \"config\", ResourceConfig.getResourceConfig(Redirect.KAFKA.getEnv(), Redirect.KAFKA.isNeedParse()));\n            r.run();\n        } finally {\n            AgentFieldReflectAccessor.setFieldValue(Redirect.KAFKA, \"config\", oldConfig);\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/kafka/src/test/java/com/megaease/easeagent/plugin/kafka/interceptor/KafkaUtilsTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.kafka.interceptor;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.kafka.interceptor.tracing.KafkaConsumerRequest;\nimport org.apache.kafka.clients.consumer.ConsumerRecord;\nimport org.apache.kafka.clients.producer.ProducerRecord;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Map;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertTrue;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class KafkaUtilsTest {\n\n    @Test\n    public void getUri() {\n        assertEquals(TestConst.URIS, KafkaUtils.getUri(TestConst.URIS));\n        List<String> uris = Arrays.asList(TestConst.URIS.split(\",\"));\n        assertEquals(TestConst.URIS, KafkaUtils.getUri(uris));\n    }\n\n    @Test\n    public void clearHeaders() {\n        ConsumerRecord<?, ?> record = new ConsumerRecord<>(\"\", 0, 0, \"\", \"\");\n        String headerKey = \"testHeaderKeys\";\n        String headerValue = \"testHeaderValue\";\n        record.headers().add(headerKey, headerValue.getBytes());\n        assertEquals(1, record.headers().toArray().length);\n        Context context = EaseAgent.getContext();\n        Span span = context.nextSpan();\n        KafkaConsumerRequest kafkaConsumerRequest = new KafkaConsumerRequest(null, record);\n        context.consumerInject(span, kafkaConsumerRequest);\n        assertTrue(record.headers().toArray().length > 1);\n        int traceHeadersSize = record.headers().toArray().length - 1;\n        Map<String, String> traceHeaders = KafkaUtils.clearHeaders(context, record);\n        assertEquals(traceHeadersSize, traceHeaders.size());\n        assertEquals(1, record.headers().toArray().length);\n        assertEquals(headerValue, new String(record.headers().lastHeader(headerKey).value()));\n        span.abandon();\n    }\n\n    @Test\n    public void getTopic() {\n        String topic = \"testTopic\";\n        ProducerRecord producerRecord = new ProducerRecord(topic, \"\");\n        assertEquals(topic, KafkaUtils.getTopic(producerRecord));\n    }\n}\n"
  },
  {
    "path": "plugins/kafka/src/test/java/com/megaease/easeagent/plugin/kafka/interceptor/MockConsumerRecord.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.kafka.interceptor;\n\nimport com.megaease.easeagent.plugin.field.DynamicFieldAccessor;\nimport org.apache.kafka.clients.consumer.ConsumerRecord;\n\npublic class MockConsumerRecord extends ConsumerRecord<String, String> implements DynamicFieldAccessor {\n    private Object data;\n\n\n    public MockConsumerRecord(String topic, int partition, long offset, String key, String value) {\n        super(topic, partition, offset, key, value);\n    }\n\n    public static MockConsumerRecord buldOne(String topic, long offset) {\n        return new MockConsumerRecord(topic, 1, offset, \"\", \"\");\n    }\n\n    @Override\n    public void setEaseAgent$$DynamicField$$Data(Object data) {\n        this.data = data;\n    }\n\n    @Override\n    public Object getEaseAgent$$DynamicField$$Data() {\n        return data;\n    }\n}\n"
  },
  {
    "path": "plugins/kafka/src/test/java/com/megaease/easeagent/plugin/kafka/interceptor/MockKafkaConsumer.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.kafka.interceptor;\n\nimport com.megaease.easeagent.plugin.field.DynamicFieldAccessor;\nimport org.apache.kafka.clients.consumer.ConsumerConfig;\nimport org.apache.kafka.clients.consumer.KafkaConsumer;\n\nimport java.util.Map;\nimport java.util.Properties;\n\npublic class MockKafkaConsumer extends KafkaConsumer<String, String> implements DynamicFieldAccessor {\n    private Object data;\n\n    public MockKafkaConsumer(Map<String, Object> configs) {\n        super(configs);\n    }\n\n    public MockKafkaConsumer(Properties properties) {\n        super(properties);\n    }\n\n    @Override\n    public void setEaseAgent$$DynamicField$$Data(Object data) {\n        this.data = data;\n    }\n\n    @Override\n    public Object getEaseAgent$$DynamicField$$Data() {\n        return this.data;\n    }\n\n    public static MockKafkaConsumer buildOne() {\n        Properties props = new Properties();\n        props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, TestConst.URIS);\n        props.put(\"key.deserializer\", \"org.apache.kafka.common.serialization.StringDeserializer\");\n        props.put(\"value.deserializer\", \"org.apache.kafka.common.serialization.StringDeserializer\");\n        MockKafkaConsumer kafkaConsumer = new MockKafkaConsumer(props);\n        kafkaConsumer.setEaseAgent$$DynamicField$$Data(props.getProperty(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG));\n        return kafkaConsumer;\n    }\n}\n"
  },
  {
    "path": "plugins/kafka/src/test/java/com/megaease/easeagent/plugin/kafka/interceptor/MockKafkaProducer.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.kafka.interceptor;\n\nimport com.megaease.easeagent.plugin.field.DynamicFieldAccessor;\nimport org.apache.kafka.clients.consumer.ConsumerConfig;\nimport org.apache.kafka.clients.producer.KafkaProducer;\n\nimport java.util.Map;\nimport java.util.Properties;\n\npublic class MockKafkaProducer extends KafkaProducer<String, String> implements DynamicFieldAccessor {\n\n    private Object data;\n\n    public MockKafkaProducer(Properties properties) {\n        super(properties);\n    }\n\n    public MockKafkaProducer(Map<String, Object> configs) {\n        super(configs);\n    }\n\n    @Override\n    public void setEaseAgent$$DynamicField$$Data(Object data) {\n        this.data = data;\n    }\n\n    @Override\n    public Object getEaseAgent$$DynamicField$$Data() {\n        return data;\n    }\n\n    public static MockKafkaProducer buildOne() {\n        Properties props = new Properties();\n        props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, TestConst.URIS);\n        props.put(\"key.serializer\", \"org.apache.kafka.common.serialization.StringSerializer\");\n        props.put(\"value.serializer\", \"org.apache.kafka.common.serialization.StringSerializer\");\n        MockKafkaProducer mockKafkaProducer = new MockKafkaProducer(props);\n        mockKafkaProducer.setEaseAgent$$DynamicField$$Data(TestConst.URIS);\n        return mockKafkaProducer;\n    }\n}\n"
  },
  {
    "path": "plugins/kafka/src/test/java/com/megaease/easeagent/plugin/kafka/interceptor/TestConst.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.kafka.interceptor;\n\npublic class TestConst {\n    public static final String URIS = \"127.0.0.1:9090,127.0.0.2:9090\";\n    public static final String REDIRECT_URIS = \"192.168.0.12:9090,192.168.0.13:9090\";\n\n}\n"
  },
  {
    "path": "plugins/kafka/src/test/java/com/megaease/easeagent/plugin/kafka/interceptor/initialize/ConsumerRecordInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.kafka.interceptor.initialize;\n\nimport com.megaease.easeagent.plugin.enums.Order;\nimport org.junit.Test;\n\nimport static org.junit.Assert.assertEquals;\n\npublic class ConsumerRecordInterceptorTest {\n\n    @Test\n    public void order() {\n        assertEquals(Order.LOWEST.getOrder(), new ConsumerRecordInterceptor().order());\n    }\n}\n"
  },
  {
    "path": "plugins/kafka/src/test/java/com/megaease/easeagent/plugin/kafka/interceptor/initialize/KafkaConsumerConstructInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.kafka.interceptor.initialize;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.kafka.interceptor.TestConst;\nimport org.apache.kafka.clients.consumer.ConsumerConfig;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNull;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class KafkaConsumerConstructInterceptorTest {\n\n    @Test\n    public void doAfter() {\n        KafkaConsumerConstructInterceptor interceptor = new KafkaConsumerConstructInterceptor();\n\n        MockDynamicFieldAccessor mockDynamicFieldAccessor = new MockDynamicFieldAccessor();\n        Map config = new HashMap();\n        config.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, TestConst.URIS);\n        config.put(\"key.deserializer\", \"org.apache.kafka.common.serialization.StringDeserializer\");\n        config.put(\"value.deserializer\", \"org.apache.kafka.common.serialization.StringDeserializer\");\n\n\n        MethodInfo methodInfo = MethodInfo.builder().invoker(mockDynamicFieldAccessor).args(new Object[]{config}).build();\n        interceptor.doAfter(methodInfo, EaseAgent.getContext());\n        assertEquals(TestConst.URIS, mockDynamicFieldAccessor.getEaseAgent$$DynamicField$$Data());\n\n        ConsumerConfig consumerConfig = new ConsumerConfig(config);\n        methodInfo = MethodInfo.builder().invoker(mockDynamicFieldAccessor).args(new Object[]{consumerConfig}).build();\n        interceptor.doAfter(methodInfo, EaseAgent.getContext());\n        assertEquals(TestConst.URIS, mockDynamicFieldAccessor.getEaseAgent$$DynamicField$$Data());\n\n\n        Map emptyConfig = new HashMap();\n        methodInfo = MethodInfo.builder().invoker(mockDynamicFieldAccessor).args(new Object[]{emptyConfig}).build();\n        interceptor.doAfter(methodInfo, EaseAgent.getContext());\n        assertNull(mockDynamicFieldAccessor.getEaseAgent$$DynamicField$$Data());\n\n    }\n\n    @Test\n    public void order() {\n        KafkaConsumerConstructInterceptor interceptor = new KafkaConsumerConstructInterceptor();\n        assertEquals(Order.TRACING_INIT.getOrder(), interceptor.order());\n\n    }\n\n    @Test\n    public void getType() {\n        KafkaConsumerConstructInterceptor interceptor = new KafkaConsumerConstructInterceptor();\n        assertEquals(ConfigConst.PluginID.TRACING, interceptor.getType());\n    }\n}\n"
  },
  {
    "path": "plugins/kafka/src/test/java/com/megaease/easeagent/plugin/kafka/interceptor/initialize/KafkaConsumerPollInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.kafka.interceptor.initialize;\n\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.kafka.interceptor.MockConsumerRecord;\nimport com.megaease.easeagent.plugin.kafka.interceptor.MockKafkaConsumer;\nimport org.apache.kafka.clients.consumer.ConsumerRecords;\nimport org.apache.kafka.common.TopicPartition;\nimport org.junit.Test;\n\nimport java.util.Collections;\n\nimport static org.junit.Assert.*;\n\npublic class KafkaConsumerPollInterceptorTest {\n\n    @Test\n    public void doAfter() {\n        KafkaConsumerPollInterceptor interceptor = new KafkaConsumerPollInterceptor();\n        MockKafkaConsumer kafkaConsumer = MockKafkaConsumer.buildOne();\n        String topic = \"testTopic\";\n        MockConsumerRecord mockConsumerRecord = MockConsumerRecord.buldOne(topic, 0);\n        ConsumerRecords<String, String> consumerRecords = new ConsumerRecords<>(\n            Collections.singletonMap(new TopicPartition(topic, 1),\n                Collections.singletonList(mockConsumerRecord))\n        );\n        assertNull(mockConsumerRecord.getEaseAgent$$DynamicField$$Data());\n\n        MethodInfo methodInfo = MethodInfo.builder().invoker(kafkaConsumer).retValue(consumerRecords).build();\n        interceptor.doAfter(methodInfo, EaseAgent.getContext());\n        assertNotNull(mockConsumerRecord.getEaseAgent$$DynamicField$$Data());\n        assertEquals(kafkaConsumer.getEaseAgent$$DynamicField$$Data(), mockConsumerRecord.getEaseAgent$$DynamicField$$Data());\n\n    }\n\n    @Test\n    public void order() {\n        KafkaConsumerPollInterceptor interceptor = new KafkaConsumerPollInterceptor();\n        assertEquals(Order.TRACING_INIT.getOrder(), interceptor.order());\n\n    }\n\n    @Test\n    public void getType() {\n        KafkaConsumerPollInterceptor interceptor = new KafkaConsumerPollInterceptor();\n        assertEquals(ConfigConst.PluginID.TRACING, interceptor.getType());\n    }\n}\n"
  },
  {
    "path": "plugins/kafka/src/test/java/com/megaease/easeagent/plugin/kafka/interceptor/initialize/KafkaProducerConstructInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.kafka.interceptor.initialize;\n\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.kafka.interceptor.MockKafkaProducer;\nimport com.megaease.easeagent.plugin.kafka.interceptor.TestConst;\nimport org.apache.kafka.clients.consumer.ConsumerConfig;\nimport org.apache.kafka.clients.producer.KafkaProducer;\nimport org.apache.kafka.common.KafkaException;\nimport org.junit.Test;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Properties;\n\nimport static org.junit.Assert.assertEquals;\n\npublic class KafkaProducerConstructInterceptorTest {\n\n    @Test\n    public void doAfter() {\n        KafkaProducerConstructInterceptor interceptor = new KafkaProducerConstructInterceptor();\n\n        Map config = new HashMap();\n        config.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, TestConst.URIS);\n        config.put(\"key.serializer\", \"org.apache.kafka.common.serialization.StringSerializer\");\n        config.put(\"value.serializer\", \"org.apache.kafka.common.serialization.StringSerializer\");\n        MockKafkaProducer mockKafkaProducer = new MockKafkaProducer(config);\n\n\n        MethodInfo methodInfo = MethodInfo.builder().invoker(mockKafkaProducer).args(new Object[]{config}).build();\n        interceptor.doAfter(methodInfo, EaseAgent.getContext());\n        assertEquals(TestConst.URIS, mockKafkaProducer.getEaseAgent$$DynamicField$$Data());\n\n        Properties props = new Properties();\n        props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, TestConst.URIS);\n        props.put(\"key.serializer\", \"org.apache.kafka.common.serialization.StringSerializer\");\n        props.put(\"value.serializer\", \"org.apache.kafka.common.serialization.StringSerializer\");\n        mockKafkaProducer = new MockKafkaProducer(props);\n\n        methodInfo = MethodInfo.builder().invoker(mockKafkaProducer).args(new Object[]{props}).build();\n        interceptor.doAfter(methodInfo, EaseAgent.getContext());\n        assertEquals(TestConst.URIS, mockKafkaProducer.getEaseAgent$$DynamicField$$Data());\n\n    }\n\n    @Test(expected = KafkaException.class)\n    public void buildFail() {\n        Map emptyConfig = new HashMap();\n        emptyConfig.put(\"key.serializer\", \"org.apache.kafka.common.serialization.StringSerializer\");\n        emptyConfig.put(\"value.serializer\", \"org.apache.kafka.common.serialization.StringSerializer\");\n        new KafkaProducer<String, String>(emptyConfig);\n    }\n\n    @Test\n    public void order() {\n        KafkaProducerConstructInterceptor interceptor = new KafkaProducerConstructInterceptor();\n        assertEquals(Order.TRACING_INIT.getOrder(), interceptor.order());\n    }\n\n    @Test\n    public void getType() {\n        KafkaProducerConstructInterceptor interceptor = new KafkaProducerConstructInterceptor();\n        assertEquals(ConfigConst.PluginID.TRACING, interceptor.getType());\n    }\n\n}\n"
  },
  {
    "path": "plugins/kafka/src/test/java/com/megaease/easeagent/plugin/kafka/interceptor/initialize/MockDynamicFieldAccessor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.kafka.interceptor.initialize;\n\nimport com.megaease.easeagent.plugin.field.DynamicFieldAccessor;\n\npublic class MockDynamicFieldAccessor implements DynamicFieldAccessor {\n    private Object data;\n\n    @Override\n    public void setEaseAgent$$DynamicField$$Data(Object data) {\n        this.data = data;\n    }\n\n    @Override\n    public Object getEaseAgent$$DynamicField$$Data() {\n        return data;\n    }\n}\n"
  },
  {
    "path": "plugins/kafka/src/test/java/com/megaease/easeagent/plugin/kafka/interceptor/metric/KafkaConsumerMetricInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.kafka.interceptor.metric;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.mock.report.impl.LastJsonReporter;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.api.metric.name.MetricField;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport org.apache.kafka.clients.consumer.ConsumerRecord;\nimport org.apache.kafka.clients.consumer.ConsumerRecords;\nimport org.apache.kafka.common.TopicPartition;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport java.util.Collections;\nimport java.util.Map;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotNull;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class KafkaConsumerMetricInterceptorTest {\n\n    @Test\n    public void init() {\n        KafkaConsumerMetricInterceptor interceptor = new KafkaConsumerMetricInterceptor();\n        KafkaMetricTest.init(interceptor);\n        assertNotNull(KafkaConsumerMetricInterceptor.getKafkaMetric());\n\n    }\n\n    public static ConsumerRecord<String, String> record(String topic, long offset) {\n        return new ConsumerRecord<>(topic, 1, offset, \"\", \"\");\n    }\n\n    @Test\n    public void doAfter() {\n        KafkaConsumerMetricInterceptor interceptor = new KafkaConsumerMetricInterceptor();\n        KafkaMetricTest.init(interceptor);\n\n        String topic = \"testTopic1\";\n        MethodInfo methodInfo = MethodInfo.builder().throwable(new RuntimeException(\"testError\")).build();\n        interceptor.doAfter(methodInfo, EaseAgent.getContext());\n\n        methodInfo = MethodInfo.builder().build();\n        interceptor.doAfter(methodInfo, EaseAgent.getContext());\n\n        methodInfo = MethodInfo.builder().retValue(new ConsumerRecords<>(Collections.emptyMap())).build();\n        interceptor.doAfter(methodInfo, EaseAgent.getContext());\n\n        ConsumerRecords<String, String> consumerRecords = new ConsumerRecords<>(\n            Collections.singletonMap(new TopicPartition(topic, 1),\n                Collections.singletonList(record(topic, 0)))\n        );\n\n        methodInfo = MethodInfo.builder().retValue(consumerRecords).build();\n        interceptor.doAfter(methodInfo, EaseAgent.getContext());\n\n\n        LastJsonReporter lastJsonReporter = KafkaMetricTest.lastMetricSupplier(topic);\n        lastJsonReporter.clean();\n        Map<String, Object> metric = KafkaMetricTest.waitOne(lastJsonReporter);\n        lastJsonReporter.clean();\n        metric = KafkaMetricTest.waitOne(lastJsonReporter);\n        assertEquals(1, metric.get(MetricField.EXECUTION_CONSUMER_COUNT.getField()));\n\n    }\n\n    @Test\n    public void getType() {\n        KafkaConsumerMetricInterceptor interceptor = new KafkaConsumerMetricInterceptor();\n        assertEquals(ConfigConst.PluginID.METRIC, interceptor.getType());\n    }\n}\n"
  },
  {
    "path": "plugins/kafka/src/test/java/com/megaease/easeagent/plugin/kafka/interceptor/metric/KafkaMessageListenerMetricInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.kafka.interceptor.metric;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.mock.report.impl.LastJsonReporter;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.api.metric.name.MetricField;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport org.apache.kafka.clients.consumer.ConsumerRecord;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport java.util.Map;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotNull;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class KafkaMessageListenerMetricInterceptorTest {\n\n    @Test\n    public void init() {\n        KafkaMessageListenerMetricInterceptor interceptor = new KafkaMessageListenerMetricInterceptor();\n        KafkaMetricTest.init(interceptor);\n        assertNotNull(KafkaMessageListenerMetricInterceptor.getKafkaMetric());\n    }\n\n    @Test\n    public void doBefore() {\n        KafkaMessageListenerMetricInterceptor interceptor = new KafkaMessageListenerMetricInterceptor();\n        Context context = EaseAgent.getContext();\n        context.remove(KafkaMessageListenerMetricInterceptor.START);\n\n        interceptor.doBefore(null, context);\n\n        assertNotNull(context.get(KafkaMessageListenerMetricInterceptor.START));\n\n        context.remove(KafkaMessageListenerMetricInterceptor.START);\n    }\n\n    @Test\n    public void doAfter() {\n\n        KafkaMessageListenerMetricInterceptor interceptor = new KafkaMessageListenerMetricInterceptor();\n        KafkaMetricTest.init(interceptor);\n\n        Context context = EaseAgent.getContext();\n        context.remove(KafkaMessageListenerMetricInterceptor.START);\n\n        interceptor.doBefore(null, context);\n        String topic = KafkaMessageListenerMetricInterceptorTest.class.getName() + \".doAfter\";\n        ConsumerRecord<String, String> record = KafkaConsumerMetricInterceptorTest.record(topic, 0);\n\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{record}).build();\n        interceptor.doAfter(methodInfo, EaseAgent.getContext());\n\n        LastJsonReporter lastJsonReporter = KafkaMetricTest.lastMetricSupplier(topic);\n        lastJsonReporter.clean();\n        Map<String, Object> metric = KafkaMetricTest.waitOne(lastJsonReporter);\n        assertEquals(1, metric.get(MetricField.EXECUTION_CONSUMER_COUNT.getField()));\n\n        interceptor.doBefore(null, context);\n        methodInfo = MethodInfo.builder().args(new Object[]{record}).throwable(new RuntimeException(\"error\")).build();\n        interceptor.doAfter(methodInfo, EaseAgent.getContext());\n        lastJsonReporter.clean();\n        metric = KafkaMetricTest.waitOne(lastJsonReporter);\n        assertEquals(2, metric.get(MetricField.EXECUTION_CONSUMER_COUNT.getField()));\n        assertEquals(1, metric.get(MetricField.EXECUTION_CONSUMER_ERROR_COUNT.getField()));\n    }\n\n\n    @Test\n    public void getType() {\n        KafkaMessageListenerMetricInterceptor interceptor = new KafkaMessageListenerMetricInterceptor();\n        assertEquals(ConfigConst.PluginID.METRIC, interceptor.getType());\n    }\n}\n"
  },
  {
    "path": "plugins/kafka/src/test/java/com/megaease/easeagent/plugin/kafka/interceptor/metric/KafkaMetricTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.kafka.interceptor.metric;\n\nimport com.megaease.easeagent.mock.plugin.api.MockEaseAgent;\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.mock.plugin.api.utils.TagVerifier;\nimport com.megaease.easeagent.mock.report.impl.LastJsonReporter;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.metric.ServiceMetricRegistry;\nimport com.megaease.easeagent.plugin.api.metric.Timer;\nimport com.megaease.easeagent.plugin.api.metric.name.MetricField;\nimport com.megaease.easeagent.plugin.api.metric.name.NameFactory;\nimport com.megaease.easeagent.plugin.api.metric.name.Tags;\nimport com.megaease.easeagent.plugin.api.middleware.RedirectProcessor;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.kafka.KafkaPlugin;\nimport com.megaease.easeagent.plugin.kafka.interceptor.KafkaTestUtils;\nimport com.megaease.easeagent.plugin.kafka.interceptor.TestConst;\nimport com.megaease.easeagent.plugin.kafka.interceptor.redirect.KafkaAbstractConfigConstructInterceptor;\nimport org.apache.kafka.clients.consumer.ConsumerConfig;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotNull;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class KafkaMetricTest {\n    public static final String TOPIC = \"testTopic\";\n\n    public static KafkaMetric get() {\n        KafkaPlugin kafkaPlugin = new KafkaPlugin();\n        IPluginConfig iPluginConfig = EaseAgent.getConfig(kafkaPlugin.getDomain(), kafkaPlugin.getNamespace(), ConfigConst.PluginID.METRIC);\n        return ServiceMetricRegistry.getOrCreate(iPluginConfig, KafkaMetric.newTags(), KafkaMetric.KAFKA_METRIC_SUPPLIER);\n    }\n\n    public static void init(Interceptor interceptor) {\n        KafkaPlugin kafkaPlugin = new KafkaPlugin();\n        IPluginConfig iPluginConfig = EaseAgent.getConfig(kafkaPlugin.getDomain(), kafkaPlugin.getNamespace(), ConfigConst.PluginID.METRIC);\n        interceptor.init(iPluginConfig, \"\", \"\", \"\");\n    }\n\n    public static LastJsonReporter lastMetricSupplier(String topic) {\n        TagVerifier tagVerifier = new TagVerifier()\n            .add(\"category\", \"application\")\n            .add(\"type\", \"kafka\")\n            .add(\"resource\", topic);\n        return MockEaseAgent.lastMetricJsonReporter(tagVerifier::verifyAnd);\n    }\n\n    public static Map<String, Object> waitOne(LastJsonReporter lastJsonReporter) {\n        return lastJsonReporter.flushAndOnlyOne();\n    }\n\n\n    @Test\n    public void producerStop() {\n        KafkaMetric kafkaMetric = get();\n        kafkaMetric.producerStop(System.currentTimeMillis() - 100, TOPIC);\n        LastJsonReporter lastJsonReporter = lastMetricSupplier(TOPIC);\n        Map<String, Object> metric = waitOne(lastJsonReporter);\n        assertEquals(1, metric.get(MetricField.EXECUTION_PRODUCER_COUNT.getField()));\n\n    }\n\n    @Test\n    public void errorProducer() {\n        KafkaMetric kafkaMetric = get();\n        kafkaMetric.errorProducer(TOPIC);\n        LastJsonReporter lastJsonReporter = lastMetricSupplier(TOPIC);\n        Map<String, Object> metric = waitOne(lastJsonReporter);\n        assertEquals(1, metric.get(MetricField.EXECUTION_PRODUCER_ERROR_COUNT.getField()));\n\n    }\n\n    @Test\n    public void consumeStart() {\n        KafkaMetric kafkaMetric = get();\n        Timer.Context context = kafkaMetric.consumeStart(TOPIC);\n        assertNotNull(context);\n    }\n\n    @Test\n    public void consumeStop() {\n        KafkaMetric kafkaMetric = get();\n        Timer.Context context = kafkaMetric.consumeStart(TOPIC);\n        kafkaMetric.consumeStop(context, TOPIC);\n        LastJsonReporter lastJsonReporter = lastMetricSupplier(TOPIC);\n        Map<String, Object> metric = waitOne(lastJsonReporter);\n        assertEquals(1, metric.get(MetricField.EXECUTION_CONSUMER_COUNT.getField()));\n    }\n\n    @Test\n    public void consumeError() {\n        KafkaMetric kafkaMetric = get();\n        kafkaMetric.consumeError(TOPIC);\n        LastJsonReporter lastJsonReporter = lastMetricSupplier(TOPIC);\n        Map<String, Object> metric = waitOne(lastJsonReporter);\n        assertEquals(1, metric.get(MetricField.EXECUTION_CONSUMER_ERROR_COUNT.getField()));\n    }\n\n    @Test\n    public void consume() {\n        KafkaMetric kafkaMetric = get();\n        kafkaMetric.consume(TOPIC, System.currentTimeMillis() - 100, true);\n        LastJsonReporter lastJsonReporter = lastMetricSupplier(TOPIC);\n        Map<String, Object> metric = waitOne(lastJsonReporter);\n        assertEquals(1, metric.get(MetricField.EXECUTION_CONSUMER_COUNT.getField()));\n\n        kafkaMetric.consume(TOPIC, System.currentTimeMillis() - 100, false);\n        lastJsonReporter.clean();\n\n        metric = waitOne(lastJsonReporter);\n        assertEquals(2, metric.get(MetricField.EXECUTION_CONSUMER_COUNT.getField()));\n        assertEquals(1, metric.get(MetricField.EXECUTION_CONSUMER_ERROR_COUNT.getField()));\n\n    }\n\n    @Test\n    public void newTags() {\n        Tags tags = KafkaMetric.newTags();\n        assertEquals(\"application\", tags.getCategory());\n        assertEquals(\"kafka\", tags.getType());\n        assertEquals(\"resource\", tags.getKeyFieldName());\n\n        KafkaAbstractConfigConstructInterceptor interceptor = new KafkaAbstractConfigConstructInterceptor();\n        KafkaTestUtils.mockRedirect(() -> {\n            Map config = new HashMap();\n            config.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, TestConst.URIS);\n\n            MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{config}).build();\n            interceptor.doBefore(methodInfo, EaseAgent.getContext());\n\n            String testTagKey = \"tagKey\";\n            String testTagValue = \"tagValue\";\n            Map<String, String> oldTags = RedirectProcessor.INSTANCE.getTags();\n            Map<String, String> newTagsMap = new HashMap<>();\n            newTagsMap.put(testTagKey, testTagValue);\n            AgentFieldReflectAccessor.setFieldValue(RedirectProcessor.INSTANCE, \"tags\", newTagsMap);\n            try {\n                Tags newTags = KafkaMetric.newTags();\n                assertEquals(testTagValue, newTags.getTags().get(testTagKey));\n            } finally {\n                AgentFieldReflectAccessor.setFieldValue(RedirectProcessor.INSTANCE, \"tags\", oldTags);\n            }\n        });\n\n    }\n\n    @Test\n    public void nameFactory() {\n        NameFactory nameFactory = KafkaMetric.nameFactory();\n        assertNotNull(nameFactory);\n    }\n}\n"
  },
  {
    "path": "plugins/kafka/src/test/java/com/megaease/easeagent/plugin/kafka/interceptor/metric/KafkaProducerMetricInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.kafka.interceptor.metric;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.mock.report.impl.LastJsonReporter;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.api.metric.name.MetricField;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport org.apache.kafka.clients.producer.ProducerRecord;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport java.util.Map;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class KafkaProducerMetricInterceptorTest {\n\n\n    @Test\n    public void init() {\n        KafkaProducerMetricInterceptor interceptor = new KafkaProducerMetricInterceptor();\n        KafkaMetricTest.init(interceptor);\n        assertNotNull(KafkaProducerMetricInterceptor.getKafkaMetric());\n    }\n\n    @Test\n    public void doBefore() {\n        KafkaProducerMetricInterceptor interceptor = new KafkaProducerMetricInterceptor();\n        KafkaMetricTest.init(interceptor);\n\n        String topic = KafkaProducerMetricInterceptorTest.class + \".doBefore\";\n        ProducerRecord record = new ProducerRecord<>(topic, \"\", \"\");\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{record, null}).build();\n        interceptor.doBefore(methodInfo, null);\n        assertNotNull(methodInfo.getArgs()[1]);\n        assertTrue(methodInfo.getArgs()[1] instanceof MetricCallback);\n\n\n    }\n\n    @Test\n    public void doAfter() {\n\n        KafkaProducerMetricInterceptor interceptor = new KafkaProducerMetricInterceptor();\n        KafkaMetricTest.init(interceptor);\n\n        String topic = KafkaProducerMetricInterceptorTest.class + \".doAfter\";\n        ProducerRecord record = new ProducerRecord<>(topic, \"\", \"\");\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{record, null}).build();\n        interceptor.doBefore(methodInfo, null);\n        interceptor.doAfter(methodInfo, null);\n\n        methodInfo.throwable(new RuntimeException(\"test error\"));\n        interceptor.doAfter(methodInfo, null);\n\n        LastJsonReporter lastJsonReporter = KafkaMetricTest.lastMetricSupplier(topic);\n        Map<String, Object> metric = KafkaMetricTest.waitOne(lastJsonReporter);\n        assertEquals(1, metric.get(MetricField.EXECUTION_PRODUCER_ERROR_COUNT.getField()));\n    }\n\n    @Test\n    public void getType() {\n        KafkaProducerMetricInterceptor interceptor = new KafkaProducerMetricInterceptor();\n        assertEquals(ConfigConst.PluginID.METRIC, interceptor.getType());\n    }\n}\n"
  },
  {
    "path": "plugins/kafka/src/test/java/com/megaease/easeagent/plugin/kafka/interceptor/metric/MetricCallbackTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.kafka.interceptor.metric;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.mock.report.impl.LastJsonReporter;\nimport com.megaease.easeagent.plugin.api.metric.name.MetricField;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport java.util.Map;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertTrue;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class MetricCallbackTest {\n\n    @Test\n    public void onCompletion() {\n\n        KafkaMetric kafkaMetric = KafkaMetricTest.get();\n\n        String topic = \"testTopic\";\n        MetricCallback metricCallback = new MetricCallback(null, topic, kafkaMetric);\n        metricCallback.onCompletion(null, null);\n\n        LastJsonReporter lastJsonReporter = KafkaMetricTest.lastMetricSupplier(topic);\n        Map<String, Object> metric = KafkaMetricTest.waitOne(lastJsonReporter);\n        assertEquals(1, metric.get(MetricField.EXECUTION_PRODUCER_COUNT.getField()));\n\n        metricCallback.onCompletion(null, new RuntimeException(\"test\"));\n\n        lastJsonReporter.clean();\n        metric = KafkaMetricTest.waitOne(lastJsonReporter);\n        assertEquals(2, metric.get(MetricField.EXECUTION_PRODUCER_COUNT.getField()));\n        assertEquals(1, metric.get(MetricField.EXECUTION_PRODUCER_ERROR_COUNT.getField()));\n\n        AtomicBoolean ran = new AtomicBoolean(false);\n        metricCallback = new MetricCallback((metadata, exception) -> ran.set(true), topic, kafkaMetric);\n        metricCallback.onCompletion(null, null);\n        assertTrue(ran.get());\n    }\n}\n"
  },
  {
    "path": "plugins/kafka/src/test/java/com/megaease/easeagent/plugin/kafka/interceptor/redirect/KafkaAbstractConfigConstructInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.kafka.interceptor.redirect;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.kafka.interceptor.KafkaTestUtils;\nimport com.megaease.easeagent.plugin.kafka.interceptor.TestConst;\nimport org.apache.kafka.clients.consumer.ConsumerConfig;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Properties;\n\nimport static org.junit.Assert.assertEquals;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class KafkaAbstractConfigConstructInterceptorTest {\n\n    @Test\n    public void doBefore() {\n        KafkaAbstractConfigConstructInterceptor interceptor = new KafkaAbstractConfigConstructInterceptor();\n        KafkaTestUtils.mockRedirect(() -> {\n            Map config = new HashMap();\n            config.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, TestConst.URIS);\n\n            MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{config}).build();\n            interceptor.doBefore(methodInfo, EaseAgent.getContext());\n            assertEquals(TestConst.REDIRECT_URIS, config.get(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG));\n\n            Properties props = new Properties();\n            props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, TestConst.URIS);\n            methodInfo = MethodInfo.builder().args(new Object[]{config}).build();\n            interceptor.doBefore(methodInfo, EaseAgent.getContext());\n            assertEquals(TestConst.REDIRECT_URIS, config.get(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG));\n        });\n    }\n\n    @Test\n    public void getType() {\n        KafkaAbstractConfigConstructInterceptor interceptor = new KafkaAbstractConfigConstructInterceptor();\n        assertEquals(ConfigConst.PluginID.REDIRECT, interceptor.getType());\n    }\n}\n"
  },
  {
    "path": "plugins/kafka/src/test/java/com/megaease/easeagent/plugin/kafka/interceptor/tracing/KafkaConsumerRequestTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.kafka.interceptor.tracing;\n\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport org.apache.kafka.clients.consumer.ConsumerRecord;\nimport org.junit.Test;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static org.junit.Assert.*;\n\npublic class KafkaConsumerRequestTest {\n    private KafkaConsumerRequest createOne() {\n        return new KafkaConsumerRequest(null, new ConsumerRecord<>(\"\", 0, 0, \"\", \"\"));\n    }\n\n    @Test\n    public void operation() {\n        assertEquals(\"receive\", createOne().operation());\n    }\n\n    @Test\n    public void channelKind() {\n        assertNull(createOne().channelKind());\n    }\n\n    @Test\n    public void channelName() {\n        assertNull(createOne().channelName());\n    }\n\n    @Test\n    public void unwrap() {\n        assertTrue(createOne().unwrap() instanceof ConsumerRecord);\n    }\n\n    @Test\n    public void kind() {\n        assertEquals(Span.Kind.CONSUMER, createOne().kind());\n    }\n\n    @Test\n    public void header() {\n        Map<String, String> map = new HashMap<>();\n        String headerKey = \"testHeaderKeys\";\n        String headerValue = \"testHeaderValue\";\n        map.put(headerKey, headerValue);\n        KafkaConsumerRequest kafkaConsumerRequest = new KafkaConsumerRequest(map, new ConsumerRecord<>(\"\", 0, 0, \"\", \"\"));\n        assertEquals(headerValue, kafkaConsumerRequest.header(headerKey));\n    }\n\n    @Test\n    public void name() {\n        assertEquals(\"poll\", createOne().name());\n    }\n\n    @Test\n    public void cacheScope() {\n        assertFalse(createOne().cacheScope());\n    }\n\n    @Test\n    public void setHeader() {\n        ConsumerRecord consumerRecord = new ConsumerRecord<>(\"\", 0, 0, \"\", \"\");\n        String headerKey = \"testHeaderKeys\";\n        String headerValue = \"testHeaderValue\";\n        KafkaConsumerRequest kafkaConsumerRequest = new KafkaConsumerRequest(null, consumerRecord);\n        kafkaConsumerRequest.setHeader(headerKey, headerValue);\n\n        assertEquals(headerValue, KafkaHeaders.lastStringHeader(consumerRecord.headers(), headerKey));\n    }\n}\n"
  },
  {
    "path": "plugins/kafka/src/test/java/com/megaease/easeagent/plugin/kafka/interceptor/tracing/KafkaConsumerTracingInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.kafka.interceptor.tracing;\n\nimport com.megaease.easeagent.mock.plugin.api.MockEaseAgent;\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.middleware.MiddlewareConstants;\nimport com.megaease.easeagent.plugin.api.middleware.Type;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.kafka.interceptor.KafkaTestUtils;\nimport com.megaease.easeagent.plugin.kafka.interceptor.MockKafkaConsumer;\nimport com.megaease.easeagent.plugin.kafka.interceptor.TestConst;\nimport com.megaease.easeagent.plugin.kafka.interceptor.redirect.KafkaAbstractConfigConstructInterceptor;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport org.apache.kafka.clients.consumer.ConsumerConfig;\nimport org.apache.kafka.clients.consumer.ConsumerRecord;\nimport org.apache.kafka.clients.consumer.ConsumerRecords;\nimport org.apache.kafka.common.TopicPartition;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport java.util.*;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class KafkaConsumerTracingInterceptorTest {\n    String topic1 = \"testTopic1\";\n    String topic2 = \"testTopic2\";\n\n\n    @Test\n    public void doAfter() {\n        KafkaConsumerTracingInterceptor interceptor = new KafkaConsumerTracingInterceptor();\n        MockKafkaConsumer kafkaConsumer = MockKafkaConsumer.buildOne();\n        String topic = \"testTopic1\";\n\n        MethodInfo methodInfo = MethodInfo.builder().invoker(kafkaConsumer).throwable(new RuntimeException(\"testError\")).build();\n        MockEaseAgent.cleanLastSpan();\n        interceptor.doAfter(methodInfo, EaseAgent.getContext());\n        assertNull(MockEaseAgent.getLastSpan());\n\n        methodInfo = MethodInfo.builder().invoker(kafkaConsumer).build();\n        interceptor.doAfter(methodInfo, EaseAgent.getContext());\n        assertNull(MockEaseAgent.getLastSpan());\n\n        methodInfo = MethodInfo.builder().invoker(kafkaConsumer).retValue(new ConsumerRecords<>(Collections.emptyMap())).build();\n        interceptor.doAfter(methodInfo, EaseAgent.getContext());\n        assertNull(MockEaseAgent.getLastSpan());\n\n\n        ConsumerRecords<String, String> consumerRecords = new ConsumerRecords<>(\n            Collections.singletonMap(new TopicPartition(topic, 1),\n                Collections.singletonList(record(topic, 0)))\n        );\n\n        methodInfo = MethodInfo.builder().invoker(kafkaConsumer).retValue(consumerRecords).build();\n        interceptor.doAfter(methodInfo, EaseAgent.getContext());\n        ReportSpan mockSpan = MockEaseAgent.getLastSpan();\n        checkBaseInfo(mockSpan, topic, (String) kafkaConsumer.getEaseAgent$$DynamicField$$Data());\n\n    }\n\n    private ConsumerRecord<String, String> record(String topic, long offset) {\n        return new ConsumerRecord<>(topic, 1, offset, \"\", \"\");\n    }\n\n    private List<ConsumerRecord<String, String>> tenRecords(String topic) {\n        return records(topic, 10);\n    }\n\n\n    private List<ConsumerRecord<String, String>> records(String topic, int count) {\n        List<ConsumerRecord<String, String>> records = new ArrayList<>(count);\n        for (int i = 0; i < count; i++) {\n            records.add(record(topic, i));\n        }\n        return records;\n    }\n\n\n    @Test\n    public void afterPoll() {\n        KafkaConsumerTracingInterceptor interceptor = new KafkaConsumerTracingInterceptor();\n        MockKafkaConsumer kafkaConsumer = MockKafkaConsumer.buildOne();\n\n        String topic = \"testTopic1\";\n        String uri = (String) kafkaConsumer.getEaseAgent$$DynamicField$$Data();\n        ConsumerRecords<String, String> consumerRecords = new ConsumerRecords<>(\n            Collections.singletonMap(new TopicPartition(topic, 1),\n                Collections.singletonList(record(topic, 0)))\n        );\n        interceptor.afterPoll(EaseAgent.getContext(), consumerRecords, uri);\n        checkBaseInfo(Objects.requireNonNull(MockEaseAgent.getLastSpan()), topic, uri);\n\n        List<ReportSpan> mockSpans = new ArrayList<>();\n        MockEaseAgent.setMockSpanReport(mockSpans::add);\n        consumerRecords = new ConsumerRecords<>(\n            Collections.singletonMap(new TopicPartition(topic, 1),\n                tenRecords(topic)\n            ));\n        interceptor.afterPoll(EaseAgent.getContext(), consumerRecords, uri);\n        assertEquals(1, mockSpans.size());\n        checkBaseInfo(mockSpans.get(0), topic, uri);\n        mockSpans.clear();\n\n        interceptor.afterPoll(EaseAgent.getContext(), consumerRecords, uri);\n        assertEquals(10, mockSpans.size());\n        for (ReportSpan mockSpan : mockSpans) {\n            checkBaseInfo(mockSpan, topic, uri);\n        }\n        mockSpans.clear();\n\n\n        MockEaseAgent.setMockSpanReport(mockSpans::add);\n        consumerRecords = new ConsumerRecords<>(\n            Collections.singletonMap(new TopicPartition(topic, 1),\n                tenRecords(topic)\n            ));\n        interceptor.singleRootSpanOnReceiveBatch = false;\n        interceptor.afterPoll(EaseAgent.getContext(), consumerRecords, uri);\n        assertEquals(10, mockSpans.size());\n        for (ReportSpan mockSpan : mockSpans) {\n            checkBaseInfo(mockSpan, topic, uri);\n        }\n        mockSpans.clear();\n    }\n\n    private void checkTowTopicMockSpans(List<ReportSpan> mockSpans, String uri) {\n        Set<String> topics = new HashSet<>();\n        for (ReportSpan mockSpan : mockSpans) {\n            topics.add(mockSpan.tag(KafkaTags.KAFKA_TOPIC_TAG));\n        }\n        assertTrue(topics.contains(topic1));\n        assertTrue(topics.contains(topic2));\n        for (ReportSpan mockSpan : mockSpans) {\n            String topic = mockSpan.tag(KafkaTags.KAFKA_TOPIC_TAG);\n            checkBaseInfo(mockSpan, topic, uri);\n        }\n    }\n\n    public Map<TopicPartition, List<ConsumerRecord<String, String>>> towTopicAndTenRecords() {\n        Map<TopicPartition, List<ConsumerRecord<String, String>>> result = new HashMap<>();\n        result.put(new TopicPartition(topic1, 1), records(topic1, 5));\n        result.put(new TopicPartition(topic2, 1), records(topic2, 5));\n        return result;\n    }\n\n    @Test\n    public void afterPoll2() {\n        KafkaConsumerTracingInterceptor interceptor = new KafkaConsumerTracingInterceptor();\n        MockKafkaConsumer kafkaConsumer = MockKafkaConsumer.buildOne();\n\n        Map<TopicPartition, List<ConsumerRecord<String, String>>> records = new HashMap<>();\n        records.put(new TopicPartition(topic1, 1), Collections.singletonList(record(topic1, 0)));\n        records.put(new TopicPartition(topic2, 1), Collections.singletonList(record(topic2, 0)));\n\n        List<ReportSpan> mockSpans = new ArrayList<>();\n        MockEaseAgent.setMockSpanReport(mockSpans::add);\n\n        String uri = (String) kafkaConsumer.getEaseAgent$$DynamicField$$Data();\n        ConsumerRecords<String, String> consumerRecords = new ConsumerRecords<>(records);\n        interceptor.afterPoll(EaseAgent.getContext(), consumerRecords, uri);\n        assertEquals(2, mockSpans.size());\n        checkTowTopicMockSpans(mockSpans, uri);\n        mockSpans.clear();\n\n\n        consumerRecords = new ConsumerRecords<>(towTopicAndTenRecords());\n        interceptor.afterPoll(EaseAgent.getContext(), consumerRecords, uri);\n        assertEquals(2, mockSpans.size());\n        checkTowTopicMockSpans(mockSpans, uri);\n        mockSpans.clear();\n\n        interceptor.afterPoll(EaseAgent.getContext(), consumerRecords, uri);\n        assertEquals(10, mockSpans.size());\n        checkTowTopicMockSpans(mockSpans, uri);\n        mockSpans.clear();\n\n\n        MockEaseAgent.setMockSpanReport(mockSpans::add);\n        consumerRecords = new ConsumerRecords<>(towTopicAndTenRecords());\n        interceptor.singleRootSpanOnReceiveBatch = false;\n        interceptor.afterPoll(EaseAgent.getContext(), consumerRecords, uri);\n        assertEquals(10, mockSpans.size());\n        checkTowTopicMockSpans(mockSpans, uri);\n        mockSpans.clear();\n    }\n\n    private void checkBaseInfo(ReportSpan mockSpan, String topic, String uri) {\n        assertEquals(topic, mockSpan.tag(KafkaTags.KAFKA_TOPIC_TAG));\n        assertEquals(uri, mockSpan.tag(KafkaTags.KAFKA_BROKER_TAG));\n        assertEquals(Type.KAFKA.getRemoteType(), mockSpan.tag(MiddlewareConstants.TYPE_TAG_NAME));\n        assertEquals(KafkaConsumerTracingInterceptor.REMOTE_SERVICE_NAME, mockSpan.remoteServiceName());\n        assertNull(mockSpan.tag(\"label.remote\"));\n    }\n\n    @Test\n    public void setConsumerSpan() {\n        KafkaConsumerTracingInterceptor interceptor = new KafkaConsumerTracingInterceptor();\n        Context context = EaseAgent.getContext();\n        Span span = context.nextSpan().start();\n        String topic = \"testTopic\";\n        String uri = \"testUri\";\n        interceptor.setConsumerSpan(topic, uri, span);\n        span.finish();\n        ReportSpan mockSpan = MockEaseAgent.getLastSpan();\n        checkBaseInfo(mockSpan, topic, uri);\n\n        KafkaAbstractConfigConstructInterceptor kafkaAbstractConfigConstructInterceptor = new KafkaAbstractConfigConstructInterceptor();\n        KafkaTestUtils.mockRedirect(() -> {\n            Properties props = new Properties();\n            props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, TestConst.URIS);\n            props.put(\"key.deserializer\", \"org.apache.kafka.common.serialization.StringDeserializer\");\n            props.put(\"value.deserializer\", \"org.apache.kafka.common.serialization.StringDeserializer\");\n\n            MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{props}).build();\n            kafkaAbstractConfigConstructInterceptor.doBefore(methodInfo, EaseAgent.getContext());\n\n            MockKafkaConsumer kafkaConsumer = new MockKafkaConsumer(props);\n            kafkaConsumer.setEaseAgent$$DynamicField$$Data(props.getProperty(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG));\n\n            Span span1 = context.nextSpan().start();\n            interceptor.setConsumerSpan(topic, (String) kafkaConsumer.getEaseAgent$$DynamicField$$Data(), span1);\n            span1.finish();\n            ReportSpan mockSpan1 = MockEaseAgent.getLastSpan();\n            assertEquals(TestConst.REDIRECT_URIS, mockSpan1.tag(\"label.remote\"));\n        });\n    }\n}\n"
  },
  {
    "path": "plugins/kafka/src/test/java/com/megaease/easeagent/plugin/kafka/interceptor/tracing/KafkaHeadersTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.kafka.interceptor.tracing;\n\nimport org.apache.kafka.clients.consumer.ConsumerRecord;\nimport org.junit.Test;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNull;\n\npublic class KafkaHeadersTest {\n\n    @Test\n    public void replaceHeader() {\n        ConsumerRecord<?, ?> record = new ConsumerRecord<>(\"\", 0, 0, \"\", \"\");\n        String headerKey = \"testHeaderKeys\";\n        String headerValue = \"testHeaderValue\";\n        record.headers().add(headerKey, headerValue.getBytes());\n        String newHeaderValue = \"testNewHeaderValue\";\n        assertEquals(headerValue, KafkaHeaders.lastStringHeader(record.headers(), headerKey));\n        KafkaHeaders.replaceHeader(record.headers(), headerKey, newHeaderValue);\n        assertEquals(newHeaderValue, KafkaHeaders.lastStringHeader(record.headers(), headerKey));\n    }\n\n    @Test\n    public void lastStringHeader() {\n        ConsumerRecord<?, ?> record = new ConsumerRecord<>(\"\", 0, 0, \"\", \"\");\n        String headerKey = \"testHeaderKeys\";\n        String headerValue = \"testHeaderValue\";\n        record.headers().add(headerKey, headerValue.getBytes());\n        assertEquals(headerValue, KafkaHeaders.lastStringHeader(record.headers(), headerKey));\n        assertNull(KafkaHeaders.lastStringHeader(record.headers(), \"aaaa\"));\n    }\n}\n"
  },
  {
    "path": "plugins/kafka/src/test/java/com/megaease/easeagent/plugin/kafka/interceptor/tracing/KafkaMessageListenerTracingInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.kafka.interceptor.tracing;\n\nimport com.megaease.easeagent.mock.plugin.api.MockEaseAgent;\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.mock.plugin.api.utils.SpanTestUtils;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.middleware.MiddlewareConstants;\nimport com.megaease.easeagent.plugin.api.middleware.Type;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.kafka.interceptor.KafkaTestUtils;\nimport com.megaease.easeagent.plugin.kafka.interceptor.MockConsumerRecord;\nimport com.megaease.easeagent.plugin.kafka.interceptor.MockKafkaConsumer;\nimport com.megaease.easeagent.plugin.kafka.interceptor.TestConst;\nimport com.megaease.easeagent.plugin.kafka.interceptor.redirect.KafkaAbstractConfigConstructInterceptor;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport org.apache.kafka.clients.consumer.ConsumerConfig;\nimport org.apache.kafka.clients.consumer.ConsumerRecord;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport java.util.Properties;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class KafkaMessageListenerTracingInterceptorTest {\n    String topic = \"testTopic\";\n\n    private ConsumerRecord<String, String> createConsumerRecord(MockKafkaConsumer kafkaConsumer) {\n        MockConsumerRecord mockConsumerRecord = MockConsumerRecord.buldOne(topic, 0);\n        mockConsumerRecord.setEaseAgent$$DynamicField$$Data(kafkaConsumer.getEaseAgent$$DynamicField$$Data());\n        return mockConsumerRecord;\n    }\n\n    private void check(ReportSpan mockSpan, String broker) {\n        assertEquals(\"on-message\", mockSpan.name());\n        assertEquals(Span.Kind.CLIENT.name(), mockSpan.kind());\n        assertEquals(\"kafka\", mockSpan.remoteServiceName());\n        assertEquals(broker, mockSpan.tag(KafkaTags.KAFKA_BROKER_TAG));\n        assertEquals(Type.KAFKA.getRemoteType(), mockSpan.tag(MiddlewareConstants.TYPE_TAG_NAME));\n    }\n\n    @Test\n    public void doBefore() {\n        KafkaMessageListenerTracingInterceptor interceptor = new KafkaMessageListenerTracingInterceptor();\n        Context context = EaseAgent.getContext();\n        MockKafkaConsumer kafkaConsumer = MockKafkaConsumer.buildOne();\n\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{createConsumerRecord(kafkaConsumer)}).build();\n        interceptor.doBefore(methodInfo, context);\n        context.<Span>remove(KafkaMessageListenerTracingInterceptor.SPAN).finish();\n        check(MockEaseAgent.getLastSpan(), (String) kafkaConsumer.getEaseAgent$$DynamicField$$Data());\n    }\n\n    @Test\n    public void doBeforeRedirected() {\n        KafkaAbstractConfigConstructInterceptor kafkaAbstractConfigConstructInterceptor = new KafkaAbstractConfigConstructInterceptor();\n        Context context = EaseAgent.getContext();\n        KafkaTestUtils.mockRedirect(() -> {\n            Properties props = new Properties();\n            props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, TestConst.URIS);\n            props.put(\"key.deserializer\", \"org.apache.kafka.common.serialization.StringDeserializer\");\n            props.put(\"value.deserializer\", \"org.apache.kafka.common.serialization.StringDeserializer\");\n\n            MethodInfo constructMethodInfo = MethodInfo.builder().args(new Object[]{props}).build();\n            kafkaAbstractConfigConstructInterceptor.doBefore(constructMethodInfo, EaseAgent.getContext());\n\n            MockKafkaConsumer kafkaConsumer = new MockKafkaConsumer(props);\n            kafkaConsumer.setEaseAgent$$DynamicField$$Data(props.getProperty(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG));\n\n\n            KafkaMessageListenerTracingInterceptor interceptor = new KafkaMessageListenerTracingInterceptor();\n            MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{createConsumerRecord(kafkaConsumer)}).build();\n            interceptor.doBefore(methodInfo, context);\n            context.<Span>remove(KafkaMessageListenerTracingInterceptor.SPAN).finish();\n            check(MockEaseAgent.getLastSpan(), (String) kafkaConsumer.getEaseAgent$$DynamicField$$Data());\n            assertEquals(TestConst.REDIRECT_URIS, MockEaseAgent.getLastSpan().tag(\"label.remote\"));\n        });\n\n    }\n\n    @Test\n    public void doAfter() {\n        KafkaMessageListenerTracingInterceptor interceptor = new KafkaMessageListenerTracingInterceptor();\n        Context context = EaseAgent.getContext();\n        MockEaseAgent.cleanLastSpan();\n        interceptor.doAfter(null, context);\n        assertNull(MockEaseAgent.getLastSpan());\n        Span span = context.nextSpan().start();\n        context.put(KafkaMessageListenerTracingInterceptor.SPAN, span);\n        MethodInfo methodInfo = MethodInfo.builder().build();\n        interceptor.doAfter(methodInfo, context);\n        ReportSpan mockSpan = MockEaseAgent.getLastSpan();\n        SpanTestUtils.sameId(span, mockSpan);\n\n        String errorInfo = \"test error\";\n        methodInfo = MethodInfo.builder().throwable(new RuntimeException(errorInfo)).build();\n        span = context.nextSpan().start();\n        context.put(KafkaMessageListenerTracingInterceptor.SPAN, span);\n        interceptor.doAfter(methodInfo, context);\n        mockSpan = MockEaseAgent.getLastSpan();\n        SpanTestUtils.sameId(span, mockSpan);\n        assertTrue(mockSpan.tags().containsKey(\"error\"));\n        assertEquals(errorInfo, mockSpan.tags().get(\"error\"));\n\n    }\n}\n"
  },
  {
    "path": "plugins/kafka/src/test/java/com/megaease/easeagent/plugin/kafka/interceptor/tracing/KafkaProducerDoSendInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.kafka.interceptor.tracing;\n\nimport com.megaease.easeagent.mock.plugin.api.MockEaseAgent;\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.mock.plugin.api.utils.SpanTestUtils;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.middleware.MiddlewareConstants;\nimport com.megaease.easeagent.plugin.api.middleware.Type;\nimport com.megaease.easeagent.plugin.api.trace.Scope;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.kafka.interceptor.KafkaTestUtils;\nimport com.megaease.easeagent.plugin.kafka.interceptor.MockKafkaProducer;\nimport com.megaease.easeagent.plugin.kafka.interceptor.TestConst;\nimport com.megaease.easeagent.plugin.kafka.interceptor.redirect.KafkaAbstractConfigConstructInterceptor;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport org.apache.kafka.clients.consumer.ConsumerConfig;\nimport org.apache.kafka.clients.producer.Callback;\nimport org.apache.kafka.clients.producer.ProducerRecord;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport java.util.Properties;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class KafkaProducerDoSendInterceptorTest {\n    String topic = \"testTopic\";\n    String key = \"testKey\";\n    String value = \"testValue\";\n\n\n    Span finishSpan() {\n        Context context = EaseAgent.getContext();\n        Span span = context.remove(KafkaProducerDoSendInterceptor.SPAN);\n        span.finish();\n        context.<Scope>remove(KafkaProducerDoSendInterceptor.SCOPE).close();\n        return span;\n    }\n\n    private void checkBaseInfo(ReportSpan mockSpan) {\n        assertEquals(TestConst.URIS, mockSpan.tag(KafkaTags.KAFKA_BROKER_TAG));\n        assertEquals(Span.Kind.PRODUCER.name(), mockSpan.kind());\n        assertEquals(\"send\", mockSpan.name());\n        assertEquals(KafkaProducerDoSendInterceptor.REMOTE_SERVICE_NAME, mockSpan.remoteServiceName());\n        assertEquals(topic, mockSpan.tag(KafkaTags.KAFKA_TOPIC_TAG));\n        assertEquals(Type.KAFKA.getRemoteType(), mockSpan.tag(MiddlewareConstants.TYPE_TAG_NAME));\n        assertNull(mockSpan.tag(\"label.remote\"));\n    }\n\n    @Test\n    public void doBefore() {\n        KafkaProducerDoSendInterceptor interceptor = new KafkaProducerDoSendInterceptor();\n        MockKafkaProducer kafkaProducer = MockKafkaProducer.buildOne();\n        Context context = EaseAgent.getContext();\n\n        ProducerRecord record = new ProducerRecord<>(topic, key, value);\n        MethodInfo methodInfo = MethodInfo.builder().invoker(kafkaProducer).args(new Object[]{record, null}).build();\n        interceptor.doBefore(methodInfo, context);\n        assertTrue(context.currentTracing().hasCurrentSpan());\n        Span span = finishSpan();\n        assertFalse(context.currentTracing().hasCurrentSpan());\n        ReportSpan mockSpan = MockEaseAgent.getLastSpan();\n        SpanTestUtils.sameId(span, mockSpan);\n        checkBaseInfo(mockSpan);\n        assertEquals(key, mockSpan.tag(KafkaTags.KAFKA_KEY_TAG));\n\n\n        record = new ProducerRecord<>(topic, value);\n        methodInfo = MethodInfo.builder().invoker(kafkaProducer).args(new Object[]{record, null}).build();\n        context = EaseAgent.getContext();\n        interceptor.doBefore(methodInfo, context);\n\n        assertTrue(context.currentTracing().hasCurrentSpan());\n        span = finishSpan();\n        assertFalse(context.currentTracing().hasCurrentSpan());\n        mockSpan = MockEaseAgent.getLastSpan();\n        SpanTestUtils.sameId(span, mockSpan);\n        checkBaseInfo(mockSpan);\n        assertNull(mockSpan.tag(KafkaTags.KAFKA_KEY_TAG));\n\n\n        record = new ProducerRecord<>(topic, value);\n        methodInfo = MethodInfo.builder().invoker(kafkaProducer).args(new Object[]{record, null}).build();\n        context = EaseAgent.getContext();\n        interceptor.doBefore(methodInfo, context);\n        assertTrue(context.currentTracing().hasCurrentSpan());\n        finishSpan();\n        assertFalse(context.currentTracing().hasCurrentSpan());\n        assertNotNull(methodInfo.getArgs()[1]);\n        assertTrue(methodInfo.getArgs()[1] instanceof TraceCallback);\n    }\n\n    @Test\n    public void testRedirectedTag() {\n        KafkaAbstractConfigConstructInterceptor kafkaAbstractConfigConstructInterceptor = new KafkaAbstractConfigConstructInterceptor();\n        KafkaTestUtils.mockRedirect(() -> {\n            Properties props = new Properties();\n            props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, TestConst.URIS);\n            props.put(\"key.serializer\", \"org.apache.kafka.common.serialization.StringSerializer\");\n            props.put(\"value.serializer\", \"org.apache.kafka.common.serialization.StringSerializer\");\n            MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{props}).build();\n            kafkaAbstractConfigConstructInterceptor.doBefore(methodInfo, EaseAgent.getContext());\n\n            MockKafkaProducer kafkaProducer = new MockKafkaProducer(props);\n            kafkaProducer.setEaseAgent$$DynamicField$$Data(props.getProperty(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG));\n\n            KafkaProducerDoSendInterceptor interceptor = new KafkaProducerDoSendInterceptor();\n            Context context = EaseAgent.getContext();\n\n            ProducerRecord record = new ProducerRecord<>(topic, key, value);\n            methodInfo = MethodInfo.builder().invoker(kafkaProducer).args(new Object[]{record, null}).build();\n            interceptor.doBefore(methodInfo, context);\n            assertTrue(context.currentTracing().hasCurrentSpan());\n            finishSpan();\n            assertFalse(context.currentTracing().hasCurrentSpan());\n            ReportSpan mockSpan = MockEaseAgent.getLastSpan();\n            assertEquals(TestConst.REDIRECT_URIS, mockSpan.tag(\"label.remote\"));\n        });\n    }\n\n    @Test\n    public void doAfter() {\n        KafkaProducerDoSendInterceptor interceptor = new KafkaProducerDoSendInterceptor();\n        Context context = EaseAgent.getContext();\n        MockKafkaProducer kafkaProducer = MockKafkaProducer.buildOne();\n\n        interceptor.doAfter(null, context);\n\n\n        ProducerRecord record = new ProducerRecord<>(topic, key, value);\n        MethodInfo methodInfo = MethodInfo.builder().invoker(kafkaProducer).args(new Object[]{record, null}).build();\n        interceptor.doBefore(methodInfo, context);\n        assertTrue(context.currentTracing().hasCurrentSpan());\n        interceptor.doAfter(methodInfo, context);\n        assertFalse(context.currentTracing().hasCurrentSpan());\n        Callback callback = (Callback) methodInfo.getArgs()[1];\n        callback.onCompletion(null, null);\n        ReportSpan mockSpan = MockEaseAgent.getLastSpan();\n        assertFalse(mockSpan.tags().containsKey(\"error\"));\n\n        record = new ProducerRecord<>(topic, key, value);\n        String errorInfo = \"test error\";\n        methodInfo = MethodInfo.builder().invoker(kafkaProducer).args(new Object[]{record, null}).throwable(new RuntimeException(errorInfo)).build();\n        interceptor.doBefore(methodInfo, context);\n        assertTrue(context.currentTracing().hasCurrentSpan());\n        interceptor.doAfter(methodInfo, context);\n        assertFalse(context.currentTracing().hasCurrentSpan());\n        callback = (Callback) methodInfo.getArgs()[1];\n        callback.onCompletion(null, null);\n\n        mockSpan = MockEaseAgent.getLastSpan();\n        assertTrue(mockSpan.tags().containsKey(\"error\"));\n        assertEquals(errorInfo, mockSpan.tags().get(\"error\"));\n\n    }\n}\n"
  },
  {
    "path": "plugins/kafka/src/test/java/com/megaease/easeagent/plugin/kafka/interceptor/tracing/KafkaProducerRequestTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.kafka.interceptor.tracing;\n\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport org.apache.kafka.clients.producer.ProducerRecord;\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\npublic class KafkaProducerRequestTest {\n\n    private KafkaProducerRequest createOne() {\n        return new KafkaProducerRequest(new ProducerRecord<>(\"\", \"\"));\n    }\n\n    @Test\n    public void unwrap() {\n        assertTrue(createOne().unwrap() instanceof ProducerRecord);\n    }\n\n    @Test\n    public void operation() {\n        assertNull(createOne().operation());\n    }\n\n    @Test\n    public void channelKind() {\n        assertNull(createOne().channelKind());\n    }\n\n    @Test\n    public void channelName() {\n        assertNull(createOne().channelName());\n    }\n\n    @Test\n    public void kind() {\n        assertEquals(Span.Kind.PRODUCER, createOne().kind());\n    }\n\n    @Test\n    public void header() {\n        KafkaProducerRequest kafkaProducerRequest = createOne();\n        String headerKey = \"testHeaderKeys\";\n        String headerValue = \"testHeaderValue\";\n        kafkaProducerRequest.setHeader(headerKey, headerValue);\n        assertEquals(headerValue, kafkaProducerRequest.header(headerKey));\n    }\n\n    @Test\n    public void name() {\n        assertEquals(\"send\", createOne().name());\n    }\n\n    @Test\n    public void cacheScope() {\n        assertFalse(createOne().cacheScope());\n    }\n\n    @Test\n    public void setHeader() {\n        header();\n    }\n}\n"
  },
  {
    "path": "plugins/kafka/src/test/java/com/megaease/easeagent/plugin/kafka/interceptor/tracing/TraceCallbackTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.kafka.interceptor.tracing;\n\nimport com.megaease.easeagent.mock.plugin.api.MockEaseAgent;\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.mock.plugin.api.utils.SpanTestUtils;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport java.util.concurrent.atomic.AtomicBoolean;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class TraceCallbackTest {\n\n\n    @Test\n    public void onCompletion() throws InterruptedException {\n        Context context = EaseAgent.getContext();\n        Span span = context.nextSpan().start();\n        TraceCallback traceCallback = new TraceCallback(span, null);\n        traceCallback.onCompletion(null, null);\n        ReportSpan mockSpan = MockEaseAgent.getLastSpan();\n        SpanTestUtils.sameId(span, mockSpan);\n        assertFalse(mockSpan.hasError());\n\n\n        span = context.nextSpan().start();\n        String errorInfo = \"test error\";\n        traceCallback = new TraceCallback(span, null);\n        traceCallback.onCompletion(null, new RuntimeException(errorInfo));\n        mockSpan = MockEaseAgent.getLastSpan();\n        SpanTestUtils.sameId(span, mockSpan);\n        assertTrue(mockSpan.hasError());\n        assertEquals(errorInfo, mockSpan.errorInfo());\n\n        AtomicBoolean ran = new AtomicBoolean(false);\n        span = context.nextSpan().start();\n        final TraceCallback traceCallbackAsync = new TraceCallback(span, (metadata, exception) -> {\n            assertTrue(EaseAgent.getContext().currentTracing().hasCurrentSpan());\n            ran.set(true);\n        });\n        Thread thread = new Thread(() -> traceCallbackAsync.onCompletion(null, null));\n        thread.start();\n        thread.join();\n\n        mockSpan = MockEaseAgent.getLastSpan();\n        SpanTestUtils.sameId(span, mockSpan);\n        assertFalse(mockSpan.hasError());\n        assertTrue(ran.get());\n\n\n    }\n}\n"
  },
  {
    "path": "plugins/kafka/src/test/resources/mock_agent.properties",
    "content": "plugin.observability.kafka.metric.interval=100\nplugin.observability.kafka.metric.intervalUnit=MILLISECONDS\n"
  },
  {
    "path": "plugins/log4j2-log-plugin/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright (c) 2021, MegaEase\n  ~ All rights reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~   http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <parent>\n        <groupId>com.megaease.easeagent</groupId>\n        <artifactId>plugins</artifactId>\n        <version>2.3.0</version>\n    </parent>\n\n    <!--\n    <groupId>com.megaease.easeagent.plugin</groupId>\n    -->\n    <artifactId>log4j2-log-plugin</artifactId>\n\n    <packaging>jar</packaging>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>plugin-api</artifactId>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>junit</groupId>\n            <artifactId>junit</artifactId>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>plugin-api-mock</artifactId>\n            <version>${project.version}</version>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.apache.logging.log4j</groupId>\n            <artifactId>log4j-core</artifactId>\n            <version>2.17.1</version>\n            <scope>provided</scope>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-compiler-plugin</artifactId>\n            </plugin>\n\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-resources-plugin</artifactId>\n                <version>3.0.1</version>\n            </plugin>\n\n            <!--\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-jar-plugin</artifactId>\n                <configuration>\n                    <archive>\n                        <manifest>\n                            <addDefaultImplementationEntries>true</addDefaultImplementationEntries>\n                            <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>\n                        </manifest>\n                    </archive>\n                </configuration>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-release-plugin</artifactId>\n                <version>2.5.3</version>\n            </plugin>\n            -->\n        </plugins>\n    </build>\n</project>\n"
  },
  {
    "path": "plugins/log4j2-log-plugin/src/main/java/com/megaease/easeagent/log4j2/Log4j2Plugin.java",
    "content": "/*\n * Copyright (c) 2022, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.log4j2;\n\nimport com.megaease.easeagent.plugin.AgentPlugin;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.enums.Order;\n\npublic class Log4j2Plugin implements AgentPlugin {\n    @Override\n    public String getNamespace() {\n        return \"log4j2\";\n    }\n\n    @Override\n    public String getDomain() {\n        return ConfigConst.OBSERVABILITY;\n    }\n\n    @Override\n    public int order() {\n        return Order.LOG.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/log4j2-log-plugin/src/main/java/com/megaease/easeagent/log4j2/interceptor/Log4j2AppenderInterceptor.java",
    "content": "/*\n * Copyright (c) 2022, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.log4j2.interceptor;\n\nimport com.megaease.easeagent.log4j2.points.AbstractLoggerPoints;\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.config.PluginConfigChangeListener;\nimport com.megaease.easeagent.plugin.api.otlp.common.AgentLogData;\nimport com.megaease.easeagent.plugin.api.otlp.common.LogMapper;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.interceptor.NonReentrantInterceptor;\nimport com.megaease.easeagent.plugin.tools.loader.AgentHelperClassLoader;\nimport com.megaease.easeagent.plugin.utils.common.StringUtils;\nimport com.megaease.easeagent.plugin.utils.common.WeakConcurrentMap;\nimport org.apache.logging.log4j.Level;\n\nimport java.lang.reflect.InvocationTargetException;\n\n@AdviceTo(AbstractLoggerPoints.class)\npublic class Log4j2AppenderInterceptor implements NonReentrantInterceptor, PluginConfigChangeListener {\n    static WeakConcurrentMap<ClassLoader, LogMapper> logMappers = new WeakConcurrentMap<>();\n    int collectLevel = Level.INFO.intLevel();\n    @Override\n    public void init(IPluginConfig config, int uniqueIndex) {\n        String lv = config.getString(\"level\");\n        if (StringUtils.isNotEmpty(lv)) {\n            collectLevel = Level.toLevel(lv, Level.OFF).intLevel();\n        }\n        config.addChangeListener(this);\n        AgentHelperClassLoader.registryUrls(this.getClass());\n    }\n\n    @Override\n    public void doBefore(MethodInfo methodInfo, Context context) {\n        ClassLoader appLoader = methodInfo.getInvoker().getClass().getClassLoader();\n        LogMapper mapper = logMappers.getIfPresent(appLoader);\n\n        if (mapper == null) {\n            ClassLoader help = AgentHelperClassLoader.getClassLoader(appLoader, EaseAgent.getAgentClassLoader());\n            try {\n                Class<?> cls = help.loadClass(\"com.megaease.easeagent.log4j2.log.Log4jLogMapper\");\n                mapper = (LogMapper) cls.getConstructor().newInstance();\n                logMappers.putIfProbablyAbsent(appLoader, mapper);\n            } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException\n                | InvocationTargetException | InstantiationException e) {\n                return;\n            }\n        }\n\n        AgentLogData log = mapper.mapLoggingEvent(methodInfo, this.collectLevel, context.getConfig());\n        if (log != null) {\n            EaseAgent.getAgentReport().report(log);\n        }\n    }\n\n    @Override\n    public String getType() {\n        return Order.LOG.getName();\n    }\n\n    @Override\n    public int order() {\n        return Order.LOG.getOrder();\n    }\n\n    @Override\n    public void onChange(IPluginConfig oldConfig, IPluginConfig newConfig) {\n        String lv = newConfig.getString(\"level\");\n\n        if (!StringUtils.isEmpty(lv)) {\n            this.collectLevel = Level.toLevel(lv, Level.OFF).intLevel();\n        } else {\n            this.collectLevel = Level.OFF.intLevel();\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/log4j2-log-plugin/src/main/java/com/megaease/easeagent/log4j2/log/Log4jLogMapper.java",
    "content": "/*\n * Copyright (c) 2022, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.megaease.easeagent.log4j2.log;\n\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.otlp.common.AgentLogData;\nimport com.megaease.easeagent.plugin.api.otlp.common.AgentLogDataImpl;\nimport com.megaease.easeagent.plugin.api.otlp.common.LogMapper;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.utils.SystemClock;\nimport io.opentelemetry.sdk.logs.data.Severity;\nimport org.apache.logging.log4j.Level;\nimport org.apache.logging.log4j.Logger;\nimport org.apache.logging.log4j.ThreadContext;\nimport org.apache.logging.log4j.message.MapMessage;\nimport org.apache.logging.log4j.message.Message;\n\nimport java.util.Map;\n\n/**\n * reference to Opentelemetry instrumentation\n */\npublic class Log4jLogMapper implements LogMapper {\n    private static final String SPECIAL_MAP_MESSAGE_ATTRIBUTE = \"message\";\n\n    public AgentLogData mapLoggingEvent(MethodInfo logInfo, int levelInt, IPluginConfig config) {\n        Object[] args = logInfo.getArgs();\n\n        if (args == null) {\n            return null;\n        }\n\n        AgentLogDataImpl.Builder builder = AgentLogDataImpl.builder();\n\n        for (int i = 0; i < args.length; i++) {\n            switch (i) {\n                case 0:\n                    // level\n                    Level level = (Level)args[i];\n                    if (level.intLevel() > levelInt) {\n                        return null;\n                    }\n                    builder.severity(levelToSeverity(level));\n                    builder.severityText(level.name());\n                    break;\n                case 4:\n                    // message\n                    Message message = (Message)args[i];\n                    if (!(message instanceof MapMessage)) {\n                        builder.body(message.getFormattedMessage());\n                    } else {\n                        MapMessage<?, ?> mapMessage = (MapMessage<?, ?>) message;\n\n                        String body = mapMessage.getFormat();\n                        boolean checkSpecialMapMessageAttribute = (body == null || body.isEmpty());\n                        if (checkSpecialMapMessageAttribute) {\n                            body = mapMessage.get(SPECIAL_MAP_MESSAGE_ATTRIBUTE);\n                        }\n                        if (body != null && !body.isEmpty()) {\n                            builder.body(body);\n                        }\n                    }\n                    break;\n                case 5:\n                    // throwable\n                    Throwable throwable = (Throwable) args[5];\n                    if (throwable != null) {\n                        builder.throwable(throwable);\n                    }\n                    break;\n                default:\n                    break;\n            }\n        }\n\n        // logger\n        Logger logger = (Logger)logInfo.getInvoker();\n        if (logger.getName() == null || logger.getName().isEmpty()) {\n            builder.logger(\"ROOT\");\n        } else {\n            builder.logger(logger.getName());\n        }\n\n        // thread\n        builder.thread(Thread.currentThread());\n        builder.epochMills(SystemClock.now());\n\n        // MDC\n        Map<String, String> contextData = ThreadContext.getImmutableContext();\n        builder.contextData(config.getStringList(LogMapper.MDC_KEYS), contextData);\n\n        // span context\n        builder.spanContext();\n\n        return builder.build();\n    }\n\n    private static Severity levelToSeverity(Level level) {\n        switch (level.getStandardLevel()) {\n            case ALL:\n            case TRACE:\n                return Severity.TRACE;\n            case DEBUG:\n                return Severity.DEBUG;\n            case INFO:\n                return Severity.INFO;\n            case WARN:\n                return Severity.WARN;\n            case ERROR:\n                return Severity.ERROR;\n            case FATAL:\n                return Severity.FATAL;\n            case OFF:\n                return Severity.UNDEFINED_SEVERITY_NUMBER;\n        }\n        return Severity.UNDEFINED_SEVERITY_NUMBER;\n    }\n}\n"
  },
  {
    "path": "plugins/log4j2-log-plugin/src/main/java/com/megaease/easeagent/log4j2/points/AbstractLoggerPoints.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.log4j2.points;\n\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.loader.ClassLoaderMatcher;\nimport com.megaease.easeagent.plugin.matcher.loader.IClassLoaderMatcher;\n\nimport java.util.Set;\n\npublic class AbstractLoggerPoints implements Points {\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return ClassMatcher.builder()\n            .hasSuperClass(\"org.apache.logging.log4j.spi.AbstractLogger\")\n            .build();\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n    return MethodMatcher.builder()\n        .named(\"log\")\n        .argsLength(6)\n        .arg(0, \"org.apache.logging.log4j.Level\")\n        .arg(1, \"org.apache.logging.log4j.Marker\")\n        .arg(2, \"java.lang.String\")\n        .arg(3, \"java.lang.StackTraceElement\")\n        .arg(4, \"org.apache.logging.log4j.message.Message\")\n        .arg(5, \"java.lang.Throwable\")\n        .build()\n        .toSet();\n    }\n\n    /**\n     * Do not match classes loaded by Agent classloader\n     */\n    @Override\n    public IClassLoaderMatcher getClassLoaderMatcher() {\n        return ClassLoaderMatcher.AGENT.negate();\n    }\n}\n"
  },
  {
    "path": "plugins/logback/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright (c) 2021, MegaEase\n  ~ All rights reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~   http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <parent>\n        <groupId>com.megaease.easeagent</groupId>\n        <artifactId>plugins</artifactId>\n        <version>2.3.0</version>\n    </parent>\n\n    <!--\n    <groupId>com.megaease.easeagent.plugin</groupId>\n    -->\n    <artifactId>logback</artifactId>\n\n    <packaging>jar</packaging>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>plugin-api</artifactId>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>junit</groupId>\n            <artifactId>junit</artifactId>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>plugin-api-mock</artifactId>\n            <version>${project.version}</version>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>ch.qos.logback</groupId>\n            <artifactId>logback-classic</artifactId>\n            <version>1.2.11</version>\n            <scope>provided</scope>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-compiler-plugin</artifactId>\n            </plugin>\n\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-resources-plugin</artifactId>\n                <version>3.0.1</version>\n            </plugin>\n\n            <!--\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-jar-plugin</artifactId>\n                <configuration>\n                    <archive>\n                        <manifest>\n                            <addDefaultImplementationEntries>true</addDefaultImplementationEntries>\n                            <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>\n                        </manifest>\n                    </archive>\n                </configuration>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-release-plugin</artifactId>\n                <version>2.5.3</version>\n            </plugin>\n            -->\n        </plugins>\n    </build>\n</project>\n"
  },
  {
    "path": "plugins/logback/src/main/java/com/megaease/easeagent/logback/LogbackPlugin.java",
    "content": "/*\n * Copyright (c) 2022, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.logback;\n\nimport com.megaease.easeagent.plugin.AgentPlugin;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\n\npublic class LogbackPlugin implements AgentPlugin {\n    @Override\n    public String getNamespace() {\n        return \"logback\";\n    }\n\n    @Override\n    public String getDomain() {\n        return ConfigConst.OBSERVABILITY;\n    }\n}\n"
  },
  {
    "path": "plugins/logback/src/main/java/com/megaease/easeagent/logback/interceptor/LogbackAppenderInterceptor.java",
    "content": "/*\n * Copyright (c) 2022, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.logback.interceptor;\n\nimport ch.qos.logback.classic.Level;\nimport com.megaease.easeagent.logback.log.LogbackLogMapper;\nimport com.megaease.easeagent.logback.points.LoggerPoints;\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.config.PluginConfigChangeListener;\nimport com.megaease.easeagent.plugin.api.otlp.common.AgentLogData;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.interceptor.NonReentrantInterceptor;\nimport com.megaease.easeagent.plugin.utils.common.StringUtils;\n\n@AdviceTo(LoggerPoints.class)\npublic class LogbackAppenderInterceptor implements NonReentrantInterceptor, PluginConfigChangeListener {\n    int collectLevel = Level.INFO.levelInt;\n\n    @Override\n    public void init(IPluginConfig config, int uniqueIndex) {\n        String lv = config.getString(\"level\");\n        if (StringUtils.isNotEmpty(lv)) {\n            collectLevel = Level.toLevel(lv, Level.OFF).levelInt;\n        }\n        config.addChangeListener(this);\n    }\n\n    @Override\n    public void doBefore(MethodInfo methodInfo, Context context) {\n        AgentLogData log = LogbackLogMapper.INSTANCE.mapLoggingEvent(methodInfo, collectLevel, context.getConfig());\n        if (log == null) {\n            return;\n        }\n        EaseAgent.getAgentReport().report(log);\n    }\n\n    @Override\n    public String getType() {\n        return Order.LOG.getName();\n    }\n\n    @Override\n    public int order() {\n        return Order.LOG.getOrder();\n    }\n\n    @Override\n    public void onChange(IPluginConfig oldConfig, IPluginConfig newConfig) {\n        String lv = newConfig.getString(\"level\");\n        if (StringUtils.isNotEmpty(lv)) {\n            collectLevel = Level.toLevel(lv, Level.OFF).levelInt;\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/logback/src/main/java/com/megaease/easeagent/logback/log/LogbackLogMapper.java",
    "content": "/*\n * Copyright (c) 2022, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.logback.log;\n\nimport ch.qos.logback.classic.Level;\nimport ch.qos.logback.classic.spi.ILoggingEvent;\nimport ch.qos.logback.classic.spi.ThrowableProxy;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.otlp.common.AgentLogData;\nimport com.megaease.easeagent.plugin.api.otlp.common.AgentLogDataImpl;\nimport com.megaease.easeagent.plugin.api.otlp.common.LogMapper;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport io.opentelemetry.sdk.logs.data.Severity;\n\nimport java.util.Map;\n\n/**\n * reference to opentelemetry logback instrumentation lib\n */\npublic class LogbackLogMapper implements LogMapper {\n    public static final LogbackLogMapper INSTANCE = new LogbackLogMapper();\n\n    @Override\n    public AgentLogData mapLoggingEvent(MethodInfo methodInfo, int levelInt, IPluginConfig config) {\n        if (methodInfo.getArgs() == null || methodInfo.getArgs().length < 1) {\n            return null;\n        }\n\n        ILoggingEvent loggingEvent = (ILoggingEvent)methodInfo.getArgs()[0];\n        Level level = loggingEvent.getLevel();\n        if (level == null || level.levelInt < levelInt) {\n            return null;\n        }\n\n        AgentLogDataImpl.Builder builder = AgentLogDataImpl.builder();\n        // logger\n        String logger = loggingEvent.getLoggerName();\n        if (logger == null || logger.isEmpty()) {\n            logger = \"ROOT\";\n        }\n        builder.logger(logger);\n        // message\n        String message = loggingEvent.getFormattedMessage();\n        if (message != null) {\n            builder.body(message);\n        }\n\n        // time\n        long timestamp = loggingEvent.getTimeStamp();\n        builder.epochMills(timestamp);\n\n        // level\n        builder.severity(levelToSeverity(level));\n        builder.severityText(level.levelStr);\n\n        // throwable\n        Object throwableProxy = loggingEvent.getThrowableProxy();\n        Throwable throwable = null;\n        if (throwableProxy instanceof ThrowableProxy) {\n            // there is only one other subclass of ch.qos.logback.classic.spi.\n            // IThrowableProxy and it is only used for logging exceptions over the wire\n            throwable = ((ThrowableProxy) throwableProxy).getThrowable();\n        }\n        if (throwable != null) {\n            builder.throwable(throwable);\n        }\n\n        Thread currentThread = Thread.currentThread();\n        builder.thread(currentThread);\n\n        // MDC\n        Map<String, String> contextData = loggingEvent.getMDCPropertyMap();\n        builder.contextData(config.getStringList(LogMapper.MDC_KEYS), contextData);\n\n        // span context\n        builder.spanContext();\n\n        return builder.build();\n    }\n\n    private static Severity levelToSeverity(Level level) {\n        switch (level.levelInt) {\n            case Level.ALL_INT:\n            case Level.TRACE_INT:\n                return Severity.TRACE;\n            case Level.DEBUG_INT:\n                return Severity.DEBUG;\n            case Level.INFO_INT:\n                return Severity.INFO;\n            case Level.WARN_INT:\n                return Severity.WARN;\n            case Level.ERROR_INT:\n                return Severity.ERROR;\n            case Level.OFF_INT:\n            default:\n                return Severity.UNDEFINED_SEVERITY_NUMBER;\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/logback/src/main/java/com/megaease/easeagent/logback/points/LoggerPoints.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.logback.points;\n\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.loader.ClassLoaderMatcher;\nimport com.megaease.easeagent.plugin.matcher.loader.IClassLoaderMatcher;\n\nimport java.util.Set;\n\npublic class LoggerPoints implements Points {\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return ClassMatcher.builder()\n            .hasClassName(\"ch.qos.logback.classic.Logger\")\n            .build();\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.builder()\n            .named(\"callAppenders\")\n            .argsLength(1)\n            .arg(0, \"ch.qos.logback.classic.spi.ILoggingEvent\")\n            .build().toSet();\n    }\n\n    /**\n     * Do not match classes loaded by Agent classloader\n     */\n    @Override\n    public IClassLoaderMatcher getClassLoaderMatcher() {\n        return ClassLoaderMatcher.AGENT.negate();\n    }\n}\n"
  },
  {
    "path": "plugins/mongodb/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>plugins</artifactId>\n        <groupId>com.megaease.easeagent</groupId>\n        <version>2.3.0</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>mongodb</artifactId>\n\n    <properties>\n        <maven.compiler.source>8</maven.compiler.source>\n        <maven.compiler.target>8</maven.compiler.target>\n    </properties>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>plugin-api</artifactId>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.mongodb</groupId>\n            <artifactId>mongodb-driver-sync</artifactId>\n            <version>4.0.5</version>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>plugin-api-mock</artifactId>\n            <version>${project.version}</version>\n            <scope>test</scope>\n        </dependency>\n    </dependencies>\n</project>\n"
  },
  {
    "path": "plugins/mongodb/src/main/java/com/megaease/easeagent/plugin/mongodb/MongoPlugin.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.mongodb;\n\nimport com.megaease.easeagent.plugin.AgentPlugin;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\n\npublic class MongoPlugin implements AgentPlugin {\n    @Override\n    public String getNamespace() {\n        return \"mongodb\";\n    }\n\n    @Override\n    public String getDomain() {\n        return ConfigConst.OBSERVABILITY;\n    }\n}\n"
  },
  {
    "path": "plugins/mongodb/src/main/java/com/megaease/easeagent/plugin/mongodb/MongoRedirectPlugin.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.mongodb;\n\nimport com.megaease.easeagent.plugin.AgentPlugin;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\n\npublic class MongoRedirectPlugin implements AgentPlugin {\n    @Override\n    public String getNamespace() {\n        return \"mongodb\";\n    }\n\n    @Override\n    public String getDomain() {\n        return ConfigConst.INTEGRABILITY;\n    }\n}\n"
  },
  {
    "path": "plugins/mongodb/src/main/java/com/megaease/easeagent/plugin/mongodb/MongoUtils.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.mongodb;\n\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.mongodb.MongoClientSettings;\nimport lombok.SneakyThrows;\n\nimport java.lang.reflect.Field;\n\npublic class MongoUtils {\n\n    public static final String CONFIG = MongoUtils.class.getName() + \".Config\";\n    public static final String METRIC = MongoUtils.class.getName() + \".Metric\";\n    public static final String EVENT_KEY = MongoUtils.class.getName() + \"-Event\";\n\n\n    public static MongoClientSettings mongoClientSettings(MethodInfo methodInfo) {\n        if (methodInfo.getArgs() != null) {\n            for (Object arg : methodInfo.getArgs()) {\n                if (arg instanceof MongoClientSettings) {\n                    return (MongoClientSettings) arg;\n                }\n            }\n        }\n        return null;\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    @SneakyThrows\n    public static <T> T getFieldValue(Object obj, String fieldName) {\n        Class<?> clazz = obj.getClass();\n        Field field = clazz.getDeclaredField(fieldName);\n        field.setAccessible(true);\n        return (T) field.get(obj);\n    }\n}\n"
  },
  {
    "path": "plugins/mongodb/src/main/java/com/megaease/easeagent/plugin/mongodb/interceptor/InterceptorHelper.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.mongodb.interceptor;\n\npublic class InterceptorHelper {\n}\n"
  },
  {
    "path": "plugins/mongodb/src/main/java/com/megaease/easeagent/plugin/mongodb/interceptor/MetricHelper.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.mongodb.interceptor;\n\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.AutoRefreshPluginConfigImpl;\nimport com.mongodb.event.CommandFailedEvent;\nimport com.mongodb.event.CommandStartedEvent;\nimport com.mongodb.event.CommandSucceededEvent;\nimport org.bson.BsonDocument;\nimport org.bson.BsonValue;\n\npublic class MetricHelper {\n\n    private static final String START_TIME = MetricHelper.class.getName() + \"-StartTime\";\n\n    public static void commandStarted(Context context, CommandStartedEvent event) {\n        context.put(START_TIME, System.currentTimeMillis());\n    }\n\n    private static void processAfter(Context context, AutoRefreshPluginConfigImpl config,\n                                     MongoMetric mongoMetric, String key, boolean success) {\n        if (!config.getConfig().enabled()) {\n            return;\n        }\n        long startTime = context.get(START_TIME);\n        long duration = System.currentTimeMillis() - startTime;\n        mongoMetric.collectMetric(key, duration, success);\n    }\n\n    public static void commandSucceeded(Context context, AutoRefreshPluginConfigImpl config,\n                                        MongoMetric mongoMetric,\n                                        CommandSucceededEvent commandSucceededEvent\n    ) {\n        BsonDocument bsonDocument = commandSucceededEvent.getResponse();\n        BsonValue writeErrors = bsonDocument.get(\"writeErrors\");\n        boolean success = writeErrors == null;\n        MetricHelper.processAfter(context, config, mongoMetric, commandSucceededEvent.getCommandName(), success);\n    }\n\n    public static void commandFailed(Context context, AutoRefreshPluginConfigImpl config,\n                                     MongoMetric mongoMetric,\n                                     CommandFailedEvent commandFailedEvent\n    ) {\n        MetricHelper.processAfter(context, config, mongoMetric, commandFailedEvent.getCommandName(), false);\n    }\n}\n"
  },
  {
    "path": "plugins/mongodb/src/main/java/com/megaease/easeagent/plugin/mongodb/interceptor/MongoBaseInterceptor.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.mongodb.interceptor;\n\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.AutoRefreshPluginConfigImpl;\nimport com.megaease.easeagent.plugin.api.config.AutoRefreshPluginConfigRegistry;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.interceptor.NonReentrantInterceptor;\nimport com.megaease.easeagent.plugin.mongodb.MongoUtils;\nimport com.mongodb.MongoClientSettings;\nimport com.mongodb.event.CommandListener;\n\nimport java.util.List;\n\n\npublic abstract class MongoBaseInterceptor implements NonReentrantInterceptor {\n\n    protected AutoRefreshPluginConfigImpl config;\n\n    @Override\n    public void init(IPluginConfig config, String className, String methodName, String methodDescriptor) {\n        this.config = AutoRefreshPluginConfigRegistry.getOrCreate(ConfigConst.OBSERVABILITY, \"mongodb\", this.getType());\n    }\n\n    @Override\n    public void doBefore(MethodInfo methodInfo, Context context) {\n        MongoClientSettings mongoClientSettings = MongoUtils.mongoClientSettings(methodInfo);\n        if (mongoClientSettings == null) {\n            return;\n        }\n        List<CommandListener> commandListeners = MongoUtils.getFieldValue(mongoClientSettings, \"commandListeners\");\n        commandListeners.add(this.commandListener());\n    }\n\n    protected abstract CommandListener commandListener();\n}\n"
  },
  {
    "path": "plugins/mongodb/src/main/java/com/megaease/easeagent/plugin/mongodb/interceptor/MongoBaseMetricInterceptor.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.mongodb.interceptor;\n\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.metric.MetricRegistry;\nimport com.megaease.easeagent.plugin.api.metric.ServiceMetricRegistry;\nimport com.megaease.easeagent.plugin.api.metric.ServiceMetricSupplier;\nimport com.megaease.easeagent.plugin.api.metric.name.NameFactory;\nimport com.megaease.easeagent.plugin.api.metric.name.Tags;\nimport com.megaease.easeagent.plugin.api.middleware.Redirect;\nimport com.megaease.easeagent.plugin.api.middleware.RedirectProcessor;\nimport com.megaease.easeagent.plugin.enums.Order;\n\n\npublic abstract class MongoBaseMetricInterceptor extends MongoBaseInterceptor {\n    protected MongoMetric mongoMetric = null;\n\n    @Override\n    public void init(IPluginConfig config, String className, String methodName, String methodDescriptor) {\n        super.init(config, className, methodName, methodDescriptor);\n        Tags tags = new Tags(\"application\", \"mongodbclient\", \"operation\");\n        RedirectProcessor.setTagsIfRedirected(Redirect.MONGODB, tags);\n        mongoMetric = ServiceMetricRegistry.getOrCreate(config, tags, new ServiceMetricSupplier<MongoMetric>() {\n            @Override\n            public NameFactory newNameFactory() {\n                return MongoMetric.nameFactory();\n            }\n\n            @Override\n            public MongoMetric newInstance(MetricRegistry metricRegistry, NameFactory nameFactory) {\n                return new MongoMetric(metricRegistry, nameFactory);\n            }\n        });\n    }\n\n    public MongoMetric getMongoMetric() {\n        return mongoMetric;\n    }\n\n    @Override\n    public String getType() {\n        return Order.METRIC.getName();\n    }\n\n    @Override\n    public int order() {\n        return Order.METRIC.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/mongodb/src/main/java/com/megaease/easeagent/plugin/mongodb/interceptor/MongoBaseTraceInterceptor.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.mongodb.interceptor;\n\nimport com.megaease.easeagent.plugin.enums.Order;\n\npublic abstract class MongoBaseTraceInterceptor extends MongoBaseInterceptor {\n\n    @Override\n    public String getType() {\n        return Order.TRACING.getName();\n    }\n\n    @Override\n    public int order() {\n        return Order.TRACING.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/mongodb/src/main/java/com/megaease/easeagent/plugin/mongodb/interceptor/MongoClientConstruct4MetricInterceptor.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.mongodb.interceptor;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.mongodb.MongoPlugin;\nimport com.megaease.easeagent.plugin.mongodb.interceptor.listener.MongoMetricCommandListener;\nimport com.megaease.easeagent.plugin.mongodb.points.MongoClientImplPoints;\nimport com.mongodb.event.CommandListener;\n\n\n@AdviceTo(value = MongoClientImplPoints.class, plugin = MongoPlugin.class)\npublic class MongoClientConstruct4MetricInterceptor extends MongoBaseMetricInterceptor {\n\n    @Override\n    protected CommandListener commandListener() {\n        return new MongoMetricCommandListener(config, mongoMetric);\n    }\n\n}\n"
  },
  {
    "path": "plugins/mongodb/src/main/java/com/megaease/easeagent/plugin/mongodb/interceptor/MongoClientConstruct4TraceInterceptor.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.mongodb.interceptor;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.mongodb.MongoPlugin;\nimport com.megaease.easeagent.plugin.mongodb.interceptor.listener.MongoTraceCommandListener;\nimport com.megaease.easeagent.plugin.mongodb.points.MongoClientImplPoints;\nimport com.mongodb.event.CommandListener;\n\n@AdviceTo(value = MongoClientImplPoints.class, plugin = MongoPlugin.class)\npublic class MongoClientConstruct4TraceInterceptor extends MongoBaseTraceInterceptor {\n\n    protected CommandListener commandListener() {\n        return new MongoTraceCommandListener(this.config);\n    }\n\n}\n"
  },
  {
    "path": "plugins/mongodb/src/main/java/com/megaease/easeagent/plugin/mongodb/interceptor/MongoCtx.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.mongodb.interceptor;\n\nimport com.megaease.easeagent.plugin.api.Context;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\npublic class MongoCtx {\n\n    private static final String MONGO_CTX_KEY = MongoCtx.class.getName();\n\n    private final Map<String, Object> map = new HashMap<>();\n\n    private MongoCtx() {\n    }\n\n    public static MongoCtx create() {\n        return new MongoCtx();\n    }\n\n    public static MongoCtx getOrCreate(Context context) {\n        MongoCtx o = context.get(MONGO_CTX_KEY);\n        if (o == null) {\n            o = MongoCtx.create();\n            o.addToContext(context);\n        }\n        return o;\n    }\n\n    public void addToContext(Context context) {\n        context.put(MONGO_CTX_KEY, this);\n    }\n\n    public void put(String key, Object value) {\n        map.put(key, value);\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    public <T> T get(String key) {\n        return (T) map.get(key);\n    }\n}\n"
  },
  {
    "path": "plugins/mongodb/src/main/java/com/megaease/easeagent/plugin/mongodb/interceptor/MongoDbRedirectInterceptor.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.mongodb.interceptor;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.middleware.Redirect;\nimport com.megaease.easeagent.plugin.api.middleware.ResourceConfig;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.mongodb.MongoRedirectPlugin;\nimport com.megaease.easeagent.plugin.mongodb.points.MongoRedirectPoints;\n\n@AdviceTo(value = MongoRedirectPoints.class, plugin = MongoRedirectPlugin.class)\npublic class MongoDbRedirectInterceptor implements Interceptor {\n\n    @Override\n    public void init(IPluginConfig config, String className, String methodName, String methodDescriptor) {\n        Interceptor.super.init(config, className, methodName, methodDescriptor);\n    }\n\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n        ResourceConfig cnf = Redirect.MONGODB.getConfig();\n        if (cnf == null) {\n            return;\n        }\n        methodInfo.changeArg(0, cnf.getFirstUri());\n    }\n\n    @Override\n    public String getType() {\n        return Order.REDIRECT.getName();\n    }\n\n    @Override\n    public int order() {\n        return Order.REDIRECT.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/mongodb/src/main/java/com/megaease/easeagent/plugin/mongodb/interceptor/MongoInternalConnectionSendAndReceiveAsync4MetricInterceptor.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.mongodb.interceptor;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.AutoRefreshPluginConfigImpl;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.interceptor.NonReentrantInterceptor;\nimport com.megaease.easeagent.plugin.mongodb.MongoPlugin;\nimport com.megaease.easeagent.plugin.mongodb.MongoUtils;\nimport com.megaease.easeagent.plugin.mongodb.points.MongoDBInternalConnectionPoints;\nimport com.mongodb.event.CommandEvent;\nimport com.mongodb.event.CommandFailedEvent;\nimport com.mongodb.event.CommandSucceededEvent;\nimport com.mongodb.internal.async.SingleResultCallback;\n\n@AdviceTo(value = MongoDBInternalConnectionPoints.class, plugin = MongoPlugin.class)\npublic class MongoInternalConnectionSendAndReceiveAsync4MetricInterceptor implements NonReentrantInterceptor {\n\n//    private static final Logger LOGGER = LoggerFactory.getLogger(MongoInternalConnectionSendAndReceiveAsync4MetricInterceptor.class);\n\n    @Override\n    public void doBefore(MethodInfo methodInfo, Context context) {\n//        LOGGER.warn(\"agent: metric - \" + methodInfo.getMethod());\n        SingleResultCallback<?> callback = (SingleResultCallback<?>) methodInfo.getArgs()[3];\n        MongoCtx mongoCtx = MongoCtx.getOrCreate(context);\n        SingleResultCallbackProxy<?> proxy = new SingleResultCallbackProxy<>(callback, mongoCtx);\n        methodInfo.changeArg(3, proxy);\n    }\n\n    @Override\n    public String getType() {\n        return Order.METRIC.getName();\n    }\n\n    @Override\n    public int order() {\n        return Order.METRIC.getOrder();\n    }\n\n    public static class SingleResultCallbackProxy<T> implements SingleResultCallback<T> {\n\n//        private static final Logger LOGGER = LoggerFactory.getLogger(SingleResultCallbackProxy.class);\n\n        private final SingleResultCallback<T> delegate;\n        private final MongoCtx mongoCtx;\n\n        public SingleResultCallbackProxy(SingleResultCallback<T> delegate, MongoCtx mongoCtx) {\n            this.delegate = delegate;\n            this.mongoCtx = mongoCtx;\n        }\n\n        @Override\n        public void onResult(T result, Throwable t) {\n//            LOGGER.info(\"SingleResultCallbackProxy onResult metric\");\n            this.delegate.onResult(result, t);\n            Context context = EaseAgent.getContext();\n            CommandEvent event = context.get(MongoUtils.EVENT_KEY);\n            if (event == null) {\n                return;\n            }\n            MongoMetric mongoMetric = this.mongoCtx.get(MongoUtils.METRIC);\n            AutoRefreshPluginConfigImpl config = this.mongoCtx.get(MongoUtils.CONFIG);\n            if (event instanceof CommandSucceededEvent) {\n                MetricHelper.commandSucceeded(context, config, mongoMetric, (CommandSucceededEvent) event);\n            } else if (event instanceof CommandFailedEvent) {\n                MetricHelper.commandFailed(context, config, mongoMetric, (CommandFailedEvent) event);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/mongodb/src/main/java/com/megaease/easeagent/plugin/mongodb/interceptor/MongoInternalConnectionSendAndReceiveAsync4TraceInterceptor.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.mongodb.interceptor;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.interceptor.NonReentrantInterceptor;\nimport com.megaease.easeagent.plugin.mongodb.MongoPlugin;\nimport com.megaease.easeagent.plugin.mongodb.MongoUtils;\nimport com.megaease.easeagent.plugin.mongodb.points.MongoDBInternalConnectionPoints;\nimport com.mongodb.event.CommandEvent;\nimport com.mongodb.event.CommandFailedEvent;\nimport com.mongodb.event.CommandSucceededEvent;\nimport com.mongodb.internal.async.SingleResultCallback;\n\n@AdviceTo(value = MongoDBInternalConnectionPoints.class, plugin = MongoPlugin.class)\npublic class MongoInternalConnectionSendAndReceiveAsync4TraceInterceptor implements NonReentrantInterceptor {\n\n//    private static final Logger LOGGER = LoggerFactory.getLogger(MongoInternalConnectionSendAndReceiveAsync4TraceInterceptor.class);\n\n    @Override\n    public void doBefore(MethodInfo methodInfo, Context context) {\n//        LOGGER.warn(\"agent: trace - \" + methodInfo.getMethod());\n        SingleResultCallback<?> callback = (SingleResultCallback<?>) methodInfo.getArgs()[3];\n        MongoCtx mongoCtx = MongoCtx.getOrCreate(context);\n        SingleResultCallbackProxy<?> proxy = new SingleResultCallbackProxy<>(callback, mongoCtx);\n        methodInfo.changeArg(3, proxy);\n    }\n\n    @Override\n    public String getType() {\n        return Order.TRACING.getName();\n    }\n\n    @Override\n    public int order() {\n        return Order.TRACING.getOrder();\n    }\n\n    public static class SingleResultCallbackProxy<T> implements SingleResultCallback<T> {\n\n        //        private static final Logger LOGGER = LoggerFactory.getLogger(SingleResultCallbackProxy.class);\n        private final SingleResultCallback<T> delegate;\n        private final MongoCtx mongoCtx;\n\n        public SingleResultCallbackProxy(SingleResultCallback<T> delegate, MongoCtx mongoCtx) {\n            this.delegate = delegate;\n            this.mongoCtx = mongoCtx;\n        }\n\n        @Override\n        public void onResult(T result, Throwable t) {\n//            LOGGER.info(\"SingleResultCallbackProxy onResult trace\");\n            this.delegate.onResult(result, t);\n            Context context = EaseAgent.getContext();\n            CommandEvent event = context.get(MongoUtils.EVENT_KEY);\n            if (event == null) {\n                return;\n            }\n            Object span = this.mongoCtx.get(TraceHelper.SPAN_KEY);\n            if (span == null) {\n                return;\n            }\n            context.put(TraceHelper.SPAN_KEY, span);\n            if (event instanceof CommandSucceededEvent) {\n                TraceHelper.commandSucceeded(context, (CommandSucceededEvent) event);\n            } else if (event instanceof CommandFailedEvent) {\n                TraceHelper.commandFailed(context, (CommandFailedEvent) event);\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "plugins/mongodb/src/main/java/com/megaease/easeagent/plugin/mongodb/interceptor/MongoMetric.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.mongodb.interceptor;\n\nimport com.megaease.easeagent.plugin.api.metric.Counter;\nimport com.megaease.easeagent.plugin.api.metric.Meter;\nimport com.megaease.easeagent.plugin.api.metric.MetricRegistry;\nimport com.megaease.easeagent.plugin.api.metric.ServiceMetric;\nimport com.megaease.easeagent.plugin.api.metric.name.*;\nimport com.megaease.easeagent.plugin.tools.metrics.LastMinutesCounterGauge;\nimport com.megaease.easeagent.plugin.utils.ImmutableMap;\n\nimport javax.annotation.Nonnull;\nimport java.util.HashMap;\nimport java.util.concurrent.TimeUnit;\n\npublic class MongoMetric extends ServiceMetric {\n    public MongoMetric(@Nonnull MetricRegistry metricRegistry, @Nonnull NameFactory nameFactory) {\n        super(metricRegistry, nameFactory);\n    }\n\n    public static NameFactory nameFactory() {\n        return NameFactory.createBuilder()\n            .timerType(MetricSubType.DEFAULT,\n                ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                    .put(MetricField.MIN_EXECUTION_TIME, MetricValueFetcher.SnapshotMinValue)\n                    .put(MetricField.MAX_EXECUTION_TIME, MetricValueFetcher.SnapshotMaxValue)\n                    .put(MetricField.MEAN_EXECUTION_TIME, MetricValueFetcher.SnapshotMeanValue)\n                    .put(MetricField.P25_EXECUTION_TIME, MetricValueFetcher.Snapshot25Percentile)\n                    .put(MetricField.P50_EXECUTION_TIME, MetricValueFetcher.Snapshot50PercentileValue)\n                    .put(MetricField.P75_EXECUTION_TIME, MetricValueFetcher.Snapshot75PercentileValue)\n                    .put(MetricField.P95_EXECUTION_TIME, MetricValueFetcher.Snapshot95PercentileValue)\n                    .put(MetricField.P98_EXECUTION_TIME, MetricValueFetcher.Snapshot98PercentileValue)\n                    .put(MetricField.P99_EXECUTION_TIME, MetricValueFetcher.Snapshot99PercentileValue)\n                    .put(MetricField.P999_EXECUTION_TIME, MetricValueFetcher.Snapshot999PercentileValue)\n                    .build())\n            .gaugeType(MetricSubType.DEFAULT, new HashMap<>())\n            .meterType(MetricSubType.DEFAULT,\n                ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                    .put(MetricField.M1_RATE, MetricValueFetcher.MeteredM1Rate)\n                    .put(MetricField.M5_RATE, MetricValueFetcher.MeteredM5Rate)\n                    .put(MetricField.M15_RATE, MetricValueFetcher.MeteredM15Rate)\n                    .put(MetricField.MEAN_RATE, MetricValueFetcher.MeteredMeanRate)\n                    .build())\n            .meterType(MetricSubType.ERROR,\n                ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                    .put(MetricField.M1_ERROR_RATE, MetricValueFetcher.MeteredM1Rate)\n                    .put(MetricField.M5_ERROR_RATE, MetricValueFetcher.MeteredM5Rate)\n                    .put(MetricField.M15_ERROR_RATE, MetricValueFetcher.MeteredM15Rate)\n                    .build())\n            .counterType(MetricSubType.ERROR, ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                .put(MetricField.EXECUTION_ERROR_COUNT, MetricValueFetcher.CountingCount)\n                .build())\n            .counterType(MetricSubType.DEFAULT, ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                .put(MetricField.EXECUTION_COUNT, MetricValueFetcher.CountingCount)\n                .build())\n            .build();\n    }\n\n    public void collectMetric(String key, long duration, boolean success) {\n        metricRegistry.timer(this.nameFactory.timerName(key, MetricSubType.DEFAULT)).update(duration, TimeUnit.MILLISECONDS);\n        final Meter defaultMeter = metricRegistry.meter(nameFactory.meterName(key, MetricSubType.DEFAULT));\n        final Counter defaultCounter = metricRegistry.counter(nameFactory.counterName(key, MetricSubType.DEFAULT));\n        if (!success) {\n            final Meter errorMeter = metricRegistry.meter(nameFactory.meterName(key, MetricSubType.ERROR));\n            final Counter errorCounter = metricRegistry.counter(nameFactory.counterName(key, MetricSubType.ERROR));\n            errorMeter.mark();\n            errorCounter.inc();\n        }\n        defaultMeter.mark();\n        defaultCounter.inc();\n\n        MetricName gaugeName = nameFactory.gaugeNames(key).get(MetricSubType.DEFAULT);\n        metricRegistry.gauge(gaugeName.name(), () -> () ->\n            LastMinutesCounterGauge.builder()\n                .m1Count((long) (defaultMeter.getOneMinuteRate() * 60))\n                .m5Count((long) (defaultMeter.getFiveMinuteRate() * 60 * 5))\n                .m15Count((long) (defaultMeter.getFifteenMinuteRate() * 60 * 15))\n                .build());\n    }\n}\n"
  },
  {
    "path": "plugins/mongodb/src/main/java/com/megaease/easeagent/plugin/mongodb/interceptor/MongoReactiveInitMetricInterceptor.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.mongodb.interceptor;\n\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.mongodb.MongoPlugin;\nimport com.megaease.easeagent.plugin.mongodb.interceptor.listener.MongoReactiveMetricCommandListener;\nimport com.megaease.easeagent.plugin.mongodb.points.MongoAsyncMongoClientsPoints;\nimport com.mongodb.event.CommandListener;\n\n@AdviceTo(value = MongoAsyncMongoClientsPoints.class, plugin = MongoPlugin.class)\npublic class MongoReactiveInitMetricInterceptor extends MongoBaseMetricInterceptor {\n\n    @Override\n    protected CommandListener commandListener() {\n        return new MongoReactiveMetricCommandListener(this.config, this.mongoMetric);\n    }\n}\n"
  },
  {
    "path": "plugins/mongodb/src/main/java/com/megaease/easeagent/plugin/mongodb/interceptor/MongoReactiveInitTraceInterceptor.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.mongodb.interceptor;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.mongodb.MongoPlugin;\nimport com.megaease.easeagent.plugin.mongodb.interceptor.listener.MongoReactiveTraceCommandListener;\nimport com.megaease.easeagent.plugin.mongodb.points.MongoAsyncMongoClientsPoints;\nimport com.mongodb.event.CommandListener;\n\n@AdviceTo(value = MongoAsyncMongoClientsPoints.class, plugin = MongoPlugin.class)\npublic class MongoReactiveInitTraceInterceptor extends MongoBaseTraceInterceptor {\n\n    @Override\n    protected CommandListener commandListener() {\n        return new MongoReactiveTraceCommandListener(this.config);\n    }\n}\n"
  },
  {
    "path": "plugins/mongodb/src/main/java/com/megaease/easeagent/plugin/mongodb/interceptor/TraceHelper.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.mongodb.interceptor;\n\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.AutoRefreshPluginConfigImpl;\nimport com.megaease.easeagent.plugin.api.middleware.MiddlewareConstants;\nimport com.megaease.easeagent.plugin.api.middleware.Type;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.mongodb.MongoSocketException;\nimport com.mongodb.connection.ConnectionDescription;\nimport com.mongodb.connection.ConnectionId;\nimport com.mongodb.event.CommandFailedEvent;\nimport com.mongodb.event.CommandStartedEvent;\nimport com.mongodb.event.CommandSucceededEvent;\nimport org.bson.BsonDocument;\nimport org.bson.BsonValue;\n\nimport java.net.InetSocketAddress;\nimport java.util.Arrays;\nimport java.util.LinkedHashSet;\nimport java.util.Set;\n\npublic class TraceHelper {\n    public static final String SPAN_KEY = TraceHelper.class.getName() + \"-Span\";\n\n    // See https://docs.mongodb.com/manual/reference/command for the command reference\n    static final Set<String> COMMANDS_WITH_COLLECTION_NAME = new LinkedHashSet<>(Arrays.asList(\n        \"aggregate\", \"count\", \"distinct\", \"mapReduce\", \"geoSearch\", \"delete\", \"find\", \"findAndModify\",\n        \"insert\", \"update\", \"collMod\", \"compact\", \"convertToCapped\", \"create\", \"createIndexes\", \"drop\",\n        \"dropIndexes\", \"killCursors\", \"listIndexes\", \"reIndex\"));\n\n    public static void commandStarted(Context context, AutoRefreshPluginConfigImpl config, CommandStartedEvent event) {\n        if (!config.getConfig().enabled()) {\n            return;\n        }\n        Span span = context.nextSpan();\n        context.put(SPAN_KEY, span);\n\n        String databaseName = event.getDatabaseName();\n        if (\"admin\".equals(databaseName)) return; // don't trace commands like \"endSessions\"\n\n        if (span == null || span.isNoop()) return;\n\n        String commandName = event.getCommandName();\n        BsonDocument command = event.getCommand();\n        String collectionName = getCollectionName(command, commandName);\n\n        span.name(getSpanName(commandName, collectionName))\n            .kind(Span.Kind.CLIENT)\n            .remoteServiceName(\"mongodb-\" + databaseName)\n            .tag(\"mongodb.command\", commandName)\n            .tag(MiddlewareConstants.TYPE_TAG_NAME, Type.MONGODB.getRemoteType())\n        ;\n\n        if (collectionName != null) {\n            span.tag(\"mongodb.collection\", collectionName);\n        }\n\n        ConnectionDescription connectionDescription = event.getConnectionDescription();\n        if (connectionDescription != null) {\n            ConnectionId connectionId = connectionDescription.getConnectionId();\n            if (connectionId != null) {\n                span.tag(\"mongodb.cluster_id\", connectionId.getServerId().getClusterId().getValue());\n            }\n\n            try {\n                InetSocketAddress socketAddress =\n                    connectionDescription.getServerAddress().getSocketAddress();\n                span.remoteIpAndPort(socketAddress.getAddress().getHostAddress(), socketAddress.getPort());\n            } catch (MongoSocketException ignored) {\n\n            }\n        }\n\n        span.start();\n    }\n\n    public static void commandSucceeded(Context context, CommandSucceededEvent event) {\n        Span span = context.get(SPAN_KEY);\n        if (span == null) {\n            return;\n        }\n        BsonDocument bsonDocument = event.getResponse();\n        BsonValue writeErrors = bsonDocument.get(\"writeErrors\");\n        boolean success = writeErrors == null;\n        String msg = null;\n        if (!success) {\n            if (writeErrors.isArray()) {\n                for (BsonValue bsonValue : writeErrors.asArray()) {\n                    if (bsonValue.isDocument()) {\n                        BsonDocument document = bsonValue.asDocument();\n                        BsonValue errmsgBsonValue = document.get(\"errmsg\");\n                        msg = errmsgBsonValue.asString().getValue();\n                        break;\n                    }\n                }\n            } else {\n                msg = \"unknown\";\n            }\n            span.tag(\"error\", msg);\n        }\n        span.finish();\n    }\n\n    public static void commandFailed(Context context, CommandFailedEvent event) {\n        Span span = context.get(SPAN_KEY);\n        if (span == null) {\n            return;\n        }\n        span.error(event.getThrowable());\n        span.finish();\n    }\n\n    public static String getCollectionName(BsonDocument command, String commandName) {\n        if (COMMANDS_WITH_COLLECTION_NAME.contains(commandName)) {\n            String collectionName = getNonEmptyBsonString(command.get(commandName));\n            if (collectionName != null) {\n                return collectionName;\n            }\n        }\n        // Some other commands, like getMore, have a field like {\"collection\": collectionName}.\n        return getNonEmptyBsonString(command.get(\"collection\"));\n    }\n\n    /**\n     * @return trimmed string from {@code bsonValue} or null if the trimmed string was empty or the\n     * value wasn't a string\n     */\n    protected static String getNonEmptyBsonString(BsonValue bsonValue) {\n        if (bsonValue == null || !bsonValue.isString()) return null;\n        String stringValue = bsonValue.asString().getValue().trim();\n        return stringValue.isEmpty() ? null : stringValue;\n    }\n\n    protected static String getSpanName(String commandName, String collectionName) {\n        if (collectionName == null) return commandName;\n        return commandName + \" \" + collectionName;\n    }\n\n}\n"
  },
  {
    "path": "plugins/mongodb/src/main/java/com/megaease/easeagent/plugin/mongodb/interceptor/listener/MongoBaseCommandListener.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.mongodb.interceptor.listener;\n\nimport com.megaease.easeagent.plugin.api.config.AutoRefreshPluginConfigImpl;\nimport com.mongodb.event.CommandListener;\n\npublic abstract class MongoBaseCommandListener implements CommandListener {\n\n    protected final AutoRefreshPluginConfigImpl config;\n\n    public MongoBaseCommandListener(AutoRefreshPluginConfigImpl config) {\n        this.config = config;\n    }\n\n}\n"
  },
  {
    "path": "plugins/mongodb/src/main/java/com/megaease/easeagent/plugin/mongodb/interceptor/listener/MongoBaseMetricCommandListener.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.mongodb.interceptor.listener;\n\nimport com.megaease.easeagent.plugin.api.config.AutoRefreshPluginConfigImpl;\nimport com.megaease.easeagent.plugin.mongodb.interceptor.MongoMetric;\n\npublic class MongoBaseMetricCommandListener extends MongoBaseCommandListener {\n\n    protected final MongoMetric mongoMetric;\n\n    public MongoBaseMetricCommandListener(AutoRefreshPluginConfigImpl config, MongoMetric mongoMetric) {\n        super(config);\n        this.mongoMetric = mongoMetric;\n    }\n\n}\n"
  },
  {
    "path": "plugins/mongodb/src/main/java/com/megaease/easeagent/plugin/mongodb/interceptor/listener/MongoBaseTraceCommandListener.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.mongodb.interceptor.listener;\n\nimport com.megaease.easeagent.plugin.api.config.AutoRefreshPluginConfigImpl;\n\npublic abstract class MongoBaseTraceCommandListener extends MongoBaseCommandListener {\n\n    public MongoBaseTraceCommandListener(AutoRefreshPluginConfigImpl config) {\n        super(config);\n    }\n\n}\n"
  },
  {
    "path": "plugins/mongodb/src/main/java/com/megaease/easeagent/plugin/mongodb/interceptor/listener/MongoMetricCommandListener.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.mongodb.interceptor.listener;\n\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.AutoRefreshPluginConfigImpl;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.mongodb.interceptor.MetricHelper;\nimport com.megaease.easeagent.plugin.mongodb.interceptor.MongoMetric;\nimport com.mongodb.event.CommandFailedEvent;\nimport com.mongodb.event.CommandStartedEvent;\nimport com.mongodb.event.CommandSucceededEvent;\n\npublic class MongoMetricCommandListener extends MongoBaseMetricCommandListener {\n\n//    private static final Logger LOGGER = LoggerFactory.getLogger(MongoMetricCommandListener.class);\n\n    public MongoMetricCommandListener(AutoRefreshPluginConfigImpl config, MongoMetric mongoMetric) {\n        super(config, mongoMetric);\n    }\n\n    @Override\n    public void commandStarted(CommandStartedEvent event) {\n//        LOGGER.warn(\"commandStarted metric\");\n        Context context = EaseAgent.getContext();\n        MetricHelper.commandStarted(context, event);\n    }\n\n    @Override\n    public void commandSucceeded(CommandSucceededEvent event) {\n//        LOGGER.warn(\"commandSucceeded metric\");\n        Context context = EaseAgent.getContext();\n        MetricHelper.commandSucceeded(context, config, this.mongoMetric, event);\n    }\n\n    @Override\n    public void commandFailed(CommandFailedEvent event) {\n//        LOGGER.warn(\"commandFailed metric\");\n        Context context = EaseAgent.getContext();\n        MetricHelper.commandFailed(context, config, this.mongoMetric, event);\n    }\n\n}\n"
  },
  {
    "path": "plugins/mongodb/src/main/java/com/megaease/easeagent/plugin/mongodb/interceptor/listener/MongoReactiveMetricCommandListener.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.mongodb.interceptor.listener;\n\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.AutoRefreshPluginConfigImpl;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.mongodb.MongoUtils;\nimport com.megaease.easeagent.plugin.mongodb.interceptor.MetricHelper;\nimport com.megaease.easeagent.plugin.mongodb.interceptor.MongoCtx;\nimport com.megaease.easeagent.plugin.mongodb.interceptor.MongoMetric;\nimport com.mongodb.event.CommandFailedEvent;\nimport com.mongodb.event.CommandStartedEvent;\nimport com.mongodb.event.CommandSucceededEvent;\n\npublic class MongoReactiveMetricCommandListener extends MongoBaseMetricCommandListener {\n\n//    private static final Logger LOGGER = LoggerFactory.getLogger(MongoReactiveMetricCommandListener.class);\n\n    public MongoReactiveMetricCommandListener(AutoRefreshPluginConfigImpl config, MongoMetric mongoMetric) {\n        super(config, mongoMetric);\n    }\n\n    @Override\n    public void commandStarted(CommandStartedEvent event) {\n//        LOGGER.warn(\"reactive commandStarted metric\");\n        Context context = EaseAgent.getContext();\n        MongoCtx mongoCtx = MongoCtx.getOrCreate(context);\n        MetricHelper.commandStarted(context, event);\n        mongoCtx.put(MongoUtils.METRIC, this.mongoMetric);\n        mongoCtx.put(MongoUtils.CONFIG, this.config);\n    }\n\n    @Override\n    public void commandSucceeded(CommandSucceededEvent event) {\n//        LOGGER.warn(\"reactive commandSucceeded metric\");\n        Context context = EaseAgent.getContext();\n        context.put(MongoUtils.EVENT_KEY, event);\n    }\n\n    @Override\n    public void commandFailed(CommandFailedEvent event) {\n//        LOGGER.warn(\"reactive commandFailed metric\");\n        Context context = EaseAgent.getContext();\n        context.put(MongoUtils.EVENT_KEY, event);\n    }\n\n}\n"
  },
  {
    "path": "plugins/mongodb/src/main/java/com/megaease/easeagent/plugin/mongodb/interceptor/listener/MongoReactiveTraceCommandListener.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.mongodb.interceptor.listener;\n\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.AutoRefreshPluginConfigImpl;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.mongodb.MongoUtils;\nimport com.megaease.easeagent.plugin.mongodb.interceptor.MongoCtx;\nimport com.megaease.easeagent.plugin.mongodb.interceptor.TraceHelper;\nimport com.mongodb.event.CommandFailedEvent;\nimport com.mongodb.event.CommandStartedEvent;\nimport com.mongodb.event.CommandSucceededEvent;\n\npublic class MongoReactiveTraceCommandListener extends MongoBaseTraceCommandListener {\n\n//    private static final Logger LOGGER = LoggerFactory.getLogger(MongoReactiveTraceCommandListener.class);\n\n    public MongoReactiveTraceCommandListener(AutoRefreshPluginConfigImpl config) {\n        super(config);\n    }\n\n    @Override\n    public void commandStarted(CommandStartedEvent event) {\n//        LOGGER.warn(\"reactive commandStarted trace\");\n        Context context = EaseAgent.getContext();\n        TraceHelper.commandStarted(context, this.config, event);\n        MongoCtx mongoCtx = MongoCtx.getOrCreate(context);\n        mongoCtx.put(MongoUtils.CONFIG, this.config);\n        mongoCtx.put(TraceHelper.SPAN_KEY, context.get(TraceHelper.SPAN_KEY));\n    }\n\n\n    @Override\n    public void commandSucceeded(CommandSucceededEvent event) {\n//        LOGGER.warn(\"reactive commandSucceeded trace\");\n        Context context = EaseAgent.getContext();\n        context.put(MongoUtils.EVENT_KEY, event);\n    }\n\n    @Override\n    public void commandFailed(CommandFailedEvent event) {\n//        LOGGER.warn(\"reactive commandFailed trace\");\n        Context context = EaseAgent.getContext();\n        context.put(MongoUtils.EVENT_KEY, event);\n    }\n\n}\n"
  },
  {
    "path": "plugins/mongodb/src/main/java/com/megaease/easeagent/plugin/mongodb/interceptor/listener/MongoTraceCommandListener.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.mongodb.interceptor.listener;\n\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.AutoRefreshPluginConfigImpl;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.mongodb.interceptor.TraceHelper;\nimport com.mongodb.event.CommandFailedEvent;\nimport com.mongodb.event.CommandStartedEvent;\nimport com.mongodb.event.CommandSucceededEvent;\n\npublic class MongoTraceCommandListener extends MongoBaseTraceCommandListener {\n\n//    private static final Logger LOGGER = LoggerFactory.getLogger(MongoTraceCommandListener.class);\n\n    public MongoTraceCommandListener(AutoRefreshPluginConfigImpl config) {\n        super(config);\n    }\n\n    @Override\n    public void commandStarted(CommandStartedEvent event) {\n//        LOGGER.warn(\"commandStarted trace\");\n        Context context = EaseAgent.getContext();\n        TraceHelper.commandStarted(context, this.config, event);\n    }\n\n\n    @Override\n    public void commandSucceeded(CommandSucceededEvent event) {\n//        LOGGER.warn(\"commandSucceeded trace\");\n        Context context = EaseAgent.getContext();\n        TraceHelper.commandSucceeded(context, event);\n    }\n\n    @Override\n    public void commandFailed(CommandFailedEvent event) {\n//        LOGGER.warn(\"commandFailed trace\");\n        Context context = EaseAgent.getContext();\n        TraceHelper.commandFailed(context, event);\n    }\n\n}\n"
  },
  {
    "path": "plugins/mongodb/src/main/java/com/megaease/easeagent/plugin/mongodb/points/MongoAsyncMongoClientsPoints.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.mongodb.points;\n\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.Set;\n\npublic class MongoAsyncMongoClientsPoints implements Points {\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return ClassMatcher.builder()\n            .hasClassName(\"com.mongodb.internal.async.client.AsyncMongoClients\")\n            .build();\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.builder()\n            .named(\"createCluster\")\n            .isStatic()\n            .isPrivate()\n            .build().toSet();\n    }\n}\n"
  },
  {
    "path": "plugins/mongodb/src/main/java/com/megaease/easeagent/plugin/mongodb/points/MongoClientImplPoints.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.mongodb.points;\n\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.Set;\n\npublic class MongoClientImplPoints implements Points {\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return ClassMatcher.builder()\n            .hasClassName(\"com.mongodb.client.internal.MongoClientImpl\")\n            .build();\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.builder().named(\"createCluster\")\n            .isStatic()\n            .isPrivate()\n            .build().toSet();\n    }\n}\n"
  },
  {
    "path": "plugins/mongodb/src/main/java/com/megaease/easeagent/plugin/mongodb/points/MongoDBInternalConnectionPoints.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.mongodb.points;\n\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.Set;\n\npublic class MongoDBInternalConnectionPoints implements Points {\n\n    private final String clsName = \"com.mongodb.internal.connection.InternalConnection\";\n\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return ClassMatcher.builder()\n            .hasInterface(clsName)\n            .notInterface()\n            .build();\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        IClassMatcher overriddenFrom = ClassMatcher.builder()\n            .hasClassName(clsName)\n            .build();\n        return MethodMatcher.builder()\n            .named(\"sendAndReceiveAsync\").isOverriddenFrom(overriddenFrom)\n            .build()\n            .toSet();\n    }\n}\n"
  },
  {
    "path": "plugins/mongodb/src/main/java/com/megaease/easeagent/plugin/mongodb/points/MongoRedirectPoints.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.mongodb.points;\n\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.Set;\n\npublic class MongoRedirectPoints implements Points {\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return ClassMatcher.builder()\n            .hasClassName(\"com.mongodb.ConnectionString\")\n            .build();\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.builder().isConstruct()\n            .argsLength(1)\n            .build().toSet();\n    }\n}\n"
  },
  {
    "path": "plugins/mongodb/src/test/java/com/megaease/easeagent/plugin/mongodb/MongoBaseTest.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.mongodb;\n\nimport com.megaease.easeagent.mock.plugin.api.MockEaseAgent;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.AutoRefreshPluginConfigImpl;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.context.ContextUtils;\nimport com.megaease.easeagent.plugin.api.metric.Metric;\nimport com.megaease.easeagent.plugin.api.metric.MetricRegistry;\nimport com.megaease.easeagent.plugin.api.metric.name.MetricSubType;\nimport com.megaease.easeagent.plugin.api.metric.name.NameFactory;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport com.megaease.easeagent.plugin.utils.common.JsonUtil;\nimport com.mongodb.ServerAddress;\nimport com.mongodb.connection.ClusterId;\nimport com.mongodb.connection.ConnectionDescription;\nimport com.mongodb.connection.ServerId;\nimport com.mongodb.event.CommandStartedEvent;\nimport org.bson.BsonDocument;\nimport org.junit.Assert;\nimport org.junit.Before;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static org.junit.Assert.*;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\npublic class MongoBaseTest {\n\n    protected int requestId = 10000;\n    protected String dbName = \"db_demo\";\n    protected String cmdName = \"insert\";\n    protected String collection = \"mock-collection\";\n    protected ServerAddress serverAddress;\n    protected ClusterId clusterId;\n    protected ServerId serverId;\n    protected ConnectionDescription connectionDescription;\n    protected CommandStartedEvent startedEvent;\n    protected AutoRefreshPluginConfigImpl config;\n    protected String errMsg = \"mock-err\";\n\n\n    @Before\n    public void before() {\n        EaseAgent.initializeContextSupplier.getContext().clear();\n        config = new AutoRefreshPluginConfigImpl();\n        IPluginConfig iPluginConfig = mock(IPluginConfig.class);\n        when(iPluginConfig.enabled()).thenReturn(true);\n        when(iPluginConfig.namespace()).thenReturn(\"mongodb\");\n        when(iPluginConfig.domain()).thenReturn(\"observability\");\n        config.onChange(null, iPluginConfig);\n        Context context = EaseAgent.getContext();\n        ContextUtils.setBeginTime(context);\n        MockEaseAgent.cleanLastSpan();\n        clusterId = new ClusterId(\"local-cluster\");\n        serverAddress = new ServerAddress(\"127.0.0.1\", 2020);\n        serverId = new ServerId(clusterId, serverAddress);\n        this.connectionDescription = new ConnectionDescription(serverId);\n        Map<String, Object> map = new HashMap<>();\n        map.put(\"collection\", collection);\n        BsonDocument bsonDocument = BsonDocument.parse(JsonUtil.toJson(map));\n        this.startedEvent = new CommandStartedEvent(this.requestId, this.connectionDescription, this.dbName, this.cmdName, bsonDocument);\n    }\n\n    protected void assertTrace(boolean success, String error) {\n        ReportSpan mockSpan = MockEaseAgent.getLastSpan();\n        assertNotNull(mockSpan);\n        assertEquals(Span.Kind.CLIENT.name(), mockSpan.kind());\n        assertEquals(\"mongodb-\" + this.dbName, mockSpan.remoteServiceName());\n        assertEquals(this.cmdName, mockSpan.tag(\"mongodb.command\"));\n        assertEquals(\"mongodb\", mockSpan.tag(\"component.type\"));\n        assertEquals(this.collection, mockSpan.tag(\"mongodb.collection\"));\n        assertEquals(this.clusterId.getValue(), mockSpan.tag(\"mongodb.cluster_id\"));\n        assertEquals(this.serverAddress.getHost(), mockSpan.remoteEndpoint().ipv4());\n        assertEquals(this.serverAddress.getPort(), mockSpan.remoteEndpoint().port());\n        if (success) {\n            assertNull(mockSpan.tag(\"error\"));\n        }\n        if (!success) {\n            assertNotNull(mockSpan.tag(\"error\"));\n            assertEquals(error, mockSpan.tag(\"error\"));\n        }\n        assertNull(mockSpan.parentId());\n    }\n\n    protected void assertMetric(NameFactory nameFactory, MetricRegistry metricRegistry, boolean success) {\n        Map<String, Metric> metrics = metricRegistry.getMetrics();\n        Assert.assertFalse(metrics.isEmpty());\n        Assert.assertNotNull(metrics.get(nameFactory.timerName(this.cmdName, MetricSubType.DEFAULT)));\n        Assert.assertNotNull(metrics.get(nameFactory.meterName(this.cmdName, MetricSubType.DEFAULT)));\n        Assert.assertNotNull(metrics.get(nameFactory.counterName(this.cmdName, MetricSubType.DEFAULT)));\n        Assert.assertNotNull(metrics.get(nameFactory.gaugeName(this.cmdName, MetricSubType.DEFAULT)));\n        if (success) {\n            Assert.assertNull(metrics.get(nameFactory.meterName(this.cmdName, MetricSubType.ERROR)));\n            Assert.assertNull(metrics.get(nameFactory.counterName(this.cmdName, MetricSubType.ERROR)));\n        } else {\n            Assert.assertNotNull(metrics.get(nameFactory.meterName(this.cmdName, MetricSubType.ERROR)));\n            Assert.assertNotNull(metrics.get(nameFactory.counterName(this.cmdName, MetricSubType.ERROR)));\n        }\n        metricRegistry.remove(nameFactory.timerName(this.cmdName, MetricSubType.DEFAULT));\n        metricRegistry.remove(nameFactory.meterName(this.cmdName, MetricSubType.DEFAULT));\n        metricRegistry.remove(nameFactory.counterName(this.cmdName, MetricSubType.DEFAULT));\n        metricRegistry.remove(nameFactory.gaugeName(this.cmdName, MetricSubType.DEFAULT));\n\n        metricRegistry.remove(nameFactory.meterName(this.cmdName, MetricSubType.ERROR));\n        metricRegistry.remove(nameFactory.counterName(this.cmdName, MetricSubType.ERROR));\n    }\n\n}\n"
  },
  {
    "path": "plugins/mongodb/src/test/java/com/megaease/easeagent/plugin/mongodb/MongoMetricTest.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.mongodb;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.mongodb.interceptor.MongoClientConstruct4MetricInterceptor;\nimport com.megaease.easeagent.plugin.mongodb.interceptor.listener.MongoMetricCommandListener;\nimport com.mongodb.event.CommandFailedEvent;\nimport com.mongodb.event.CommandSucceededEvent;\nimport org.bson.BsonArray;\nimport org.bson.BsonDocument;\nimport org.bson.BsonString;\nimport org.bson.BsonValue;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class MongoMetricTest extends MongoBaseTest {\n    MongoMetricCommandListener listener;\n    MongoClientConstruct4MetricInterceptor interceptor;\n\n    @Before\n    public void before() {\n        super.before();\n        interceptor = new MongoClientConstruct4MetricInterceptor();\n        interceptor.init(config, \"\", \"\", \"\");\n        listener = new MongoMetricCommandListener(this.config, interceptor.getMongoMetric());\n    }\n\n    @Test\n    public void performSuccess() {\n        BsonDocument bsonDocument = new BsonDocument();\n        bsonDocument.put(\"collection\", new BsonString(collection));\n        CommandSucceededEvent succeededEvent = new CommandSucceededEvent(this.requestId, this.connectionDescription, this.cmdName, bsonDocument, 10);\n        this.listener.commandStarted(startedEvent);\n        this.listener.commandSucceeded(succeededEvent);\n        this.assertMetric(interceptor.getMongoMetric().getNameFactory(), interceptor.getMongoMetric().getMetricRegistry(), true);\n    }\n\n    @Test\n    public void performOpFail() {\n        BsonDocument errDoc = new BsonDocument();\n        errDoc.put(\"errmsg\", new BsonString(errMsg));\n        List<BsonValue> list = new ArrayList<>();\n        list.add(errDoc);\n        BsonArray bsonValues = new BsonArray(list);\n        BsonDocument bsonDocument = new BsonDocument();\n        bsonDocument.put(\"collection\", new BsonString(collection));\n        bsonDocument.put(\"writeErrors\", bsonValues);\n        CommandSucceededEvent succeededEvent = new CommandSucceededEvent(this.requestId, this.connectionDescription, this.cmdName, bsonDocument, 10);\n        this.listener.commandStarted(startedEvent);\n        this.listener.commandSucceeded(succeededEvent);\n        this.assertMetric(interceptor.getMongoMetric().getNameFactory(), interceptor.getMongoMetric().getMetricRegistry(), false);\n    }\n\n    @Test\n    public void performOpFail2() {\n        CommandFailedEvent failedEvent = new CommandFailedEvent(this.requestId, this.connectionDescription, this.cmdName, 10, new RuntimeException(this.errMsg));\n        this.listener.commandStarted(startedEvent);\n        this.listener.commandFailed(failedEvent);\n        this.assertMetric(interceptor.getMongoMetric().getNameFactory(), interceptor.getMongoMetric().getMetricRegistry(), false);\n    }\n}\n"
  },
  {
    "path": "plugins/mongodb/src/test/java/com/megaease/easeagent/plugin/mongodb/MongoReactiveMetricTest.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.mongodb;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.mongodb.interceptor.MongoInternalConnectionSendAndReceiveAsync4MetricInterceptor;\nimport com.megaease.easeagent.plugin.mongodb.interceptor.MongoReactiveInitMetricInterceptor;\nimport com.megaease.easeagent.plugin.mongodb.interceptor.listener.MongoMetricCommandListener;\nimport com.mongodb.event.CommandFailedEvent;\nimport com.mongodb.event.CommandSucceededEvent;\nimport com.mongodb.internal.async.SingleResultCallback;\nimport org.bson.BsonArray;\nimport org.bson.BsonDocument;\nimport org.bson.BsonString;\nimport org.bson.BsonValue;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport static org.mockito.Mockito.mock;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class MongoReactiveMetricTest extends MongoBaseTest {\n    MongoMetricCommandListener listener;\n    MongoInternalConnectionSendAndReceiveAsync4MetricInterceptor interceptor;\n    MongoReactiveInitMetricInterceptor initMetricInterceptor;\n\n    @Before\n    public void before() {\n        super.before();\n        interceptor = new MongoInternalConnectionSendAndReceiveAsync4MetricInterceptor();\n        initMetricInterceptor = new MongoReactiveInitMetricInterceptor();\n        initMetricInterceptor.init(config, \"\", \"\", \"\");\n        listener = new MongoMetricCommandListener(this.config, initMetricInterceptor.getMongoMetric());\n    }\n\n    @Test\n    public void performSuccess() {\n        Context context = EaseAgent.getContext();\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{null, null, null, mock(SingleResultCallback.class)}).build();\n        interceptor.before(methodInfo, context);\n\n        BsonDocument bsonDocument = new BsonDocument();\n        bsonDocument.put(\"collection\", new BsonString(collection));\n        CommandSucceededEvent succeededEvent = new CommandSucceededEvent(this.requestId, this.connectionDescription, this.cmdName, bsonDocument, 10);\n        this.listener.commandStarted(startedEvent);\n        this.listener.commandSucceeded(succeededEvent);\n        MongoInternalConnectionSendAndReceiveAsync4MetricInterceptor.SingleResultCallbackProxy<?> proxy =\n            (MongoInternalConnectionSendAndReceiveAsync4MetricInterceptor.SingleResultCallbackProxy<?>) methodInfo.getArgs()[3];\n        proxy.onResult(null, null);\n        this.assertMetric(initMetricInterceptor.getMongoMetric().getNameFactory(), initMetricInterceptor.getMongoMetric().getMetricRegistry(), true);\n        context.exit(interceptor.getEnterKey(methodInfo, context));\n    }\n\n    @Test\n    public void performOpFail() {\n        Context context = EaseAgent.getContext();\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{null, null, null, mock(SingleResultCallback.class)}).build();\n        interceptor.before(methodInfo, context);\n\n        BsonDocument errDoc = new BsonDocument();\n        errDoc.put(\"errmsg\", new BsonString(errMsg));\n        List<BsonValue> list = new ArrayList<>();\n        list.add(errDoc);\n        BsonArray bsonValues = new BsonArray(list);\n        BsonDocument bsonDocument = new BsonDocument();\n        bsonDocument.put(\"collection\", new BsonString(collection));\n        bsonDocument.put(\"writeErrors\", bsonValues);\n        CommandSucceededEvent succeededEvent = new CommandSucceededEvent(this.requestId, this.connectionDescription, this.cmdName, bsonDocument, 10);\n        this.listener.commandStarted(startedEvent);\n        this.listener.commandSucceeded(succeededEvent);\n        MongoInternalConnectionSendAndReceiveAsync4MetricInterceptor.SingleResultCallbackProxy<?> proxy =\n            (MongoInternalConnectionSendAndReceiveAsync4MetricInterceptor.SingleResultCallbackProxy<?>) methodInfo.getArgs()[3];\n        proxy.onResult(null, null);\n        this.assertMetric(initMetricInterceptor.getMongoMetric().getNameFactory(), initMetricInterceptor.getMongoMetric().getMetricRegistry(), false);\n        context.exit(interceptor.getEnterKey(methodInfo, context));\n    }\n\n    @Test\n    public void performOpFail2() {\n        Context context = EaseAgent.getContext();\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{null, null, null, mock(SingleResultCallback.class)}).build();\n        interceptor.before(methodInfo, context);\n\n        CommandFailedEvent failedEvent = new CommandFailedEvent(this.requestId, this.connectionDescription, this.cmdName, 10, new RuntimeException(this.errMsg));\n        this.listener.commandStarted(startedEvent);\n        this.listener.commandFailed(failedEvent);\n        MongoInternalConnectionSendAndReceiveAsync4MetricInterceptor.SingleResultCallbackProxy<?> proxy =\n            (MongoInternalConnectionSendAndReceiveAsync4MetricInterceptor.SingleResultCallbackProxy<?>) methodInfo.getArgs()[3];\n        proxy.onResult(null, null);\n        this.assertMetric(initMetricInterceptor.getMongoMetric().getNameFactory(), initMetricInterceptor.getMongoMetric().getMetricRegistry(), false);\n        context.exit(interceptor.getEnterKey(methodInfo, context));\n    }\n}\n"
  },
  {
    "path": "plugins/mongodb/src/test/java/com/megaease/easeagent/plugin/mongodb/MongoReactiveTraceTest.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.mongodb;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.mongodb.interceptor.MongoInternalConnectionSendAndReceiveAsync4TraceInterceptor;\nimport com.megaease.easeagent.plugin.mongodb.interceptor.listener.MongoReactiveTraceCommandListener;\nimport com.mongodb.event.CommandFailedEvent;\nimport com.mongodb.event.CommandSucceededEvent;\nimport com.mongodb.internal.async.SingleResultCallback;\nimport org.bson.BsonArray;\nimport org.bson.BsonDocument;\nimport org.bson.BsonString;\nimport org.bson.BsonValue;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport static org.mockito.Mockito.mock;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class MongoReactiveTraceTest extends MongoBaseTest {\n\n    MongoReactiveTraceCommandListener listener;\n\n    MongoInternalConnectionSendAndReceiveAsync4TraceInterceptor interceptor;\n\n    @Before\n    public void before() {\n        super.before();\n        listener = new MongoReactiveTraceCommandListener(this.config);\n        interceptor = new MongoInternalConnectionSendAndReceiveAsync4TraceInterceptor();\n    }\n\n    @Test\n    public void performSuccess() {\n        Context context = EaseAgent.getContext();\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{null, null, null, mock(SingleResultCallback.class)}).build();\n        interceptor.before(methodInfo, context);\n\n        BsonDocument bsonDocument = new BsonDocument();\n        bsonDocument.put(\"collection\", new BsonString(collection));\n        CommandSucceededEvent succeededEvent = new CommandSucceededEvent(this.requestId, this.connectionDescription, this.cmdName, bsonDocument, 10);\n        this.listener.commandStarted(startedEvent);\n        this.listener.commandSucceeded(succeededEvent);\n        MongoInternalConnectionSendAndReceiveAsync4TraceInterceptor.SingleResultCallbackProxy<?> proxy =\n            (MongoInternalConnectionSendAndReceiveAsync4TraceInterceptor.SingleResultCallbackProxy<?>) methodInfo.getArgs()[3];\n        proxy.onResult(null, null);\n        this.assertTrace(true, null);\n        context.exit(interceptor.getEnterKey(methodInfo, context));\n    }\n\n    @Test\n    public void performOpFail() {\n        Context context = EaseAgent.getContext();\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{null, null, null, mock(SingleResultCallback.class)}).build();\n        interceptor.before(methodInfo, context);\n\n        BsonDocument errDoc = new BsonDocument();\n        errDoc.put(\"errmsg\", new BsonString(errMsg));\n        List<BsonValue> list = new ArrayList<>();\n        list.add(errDoc);\n        BsonArray bsonValues = new BsonArray(list);\n        BsonDocument bsonDocument = new BsonDocument();\n        bsonDocument.put(\"collection\", new BsonString(collection));\n        bsonDocument.put(\"writeErrors\", bsonValues);\n        CommandSucceededEvent succeededEvent = new CommandSucceededEvent(this.requestId, this.connectionDescription, this.cmdName, bsonDocument, 10);\n        this.listener.commandStarted(startedEvent);\n        this.listener.commandSucceeded(succeededEvent);\n        MongoInternalConnectionSendAndReceiveAsync4TraceInterceptor.SingleResultCallbackProxy<?> proxy =\n            (MongoInternalConnectionSendAndReceiveAsync4TraceInterceptor.SingleResultCallbackProxy<?>) methodInfo.getArgs()[3];\n        proxy.onResult(null, null);\n        this.assertTrace(false, errMsg);\n        context.exit(interceptor.getEnterKey(methodInfo, context));\n    }\n\n    @Test\n    public void performOpFail2() {\n        Context context = EaseAgent.getContext();\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{null, null, null, mock(SingleResultCallback.class)}).build();\n        interceptor.before(methodInfo, context);\n\n        CommandFailedEvent failedEvent = new CommandFailedEvent(this.requestId, this.connectionDescription, this.cmdName, 10, new RuntimeException(this.errMsg));\n        this.listener.commandStarted(startedEvent);\n        this.listener.commandFailed(failedEvent);\n        MongoInternalConnectionSendAndReceiveAsync4TraceInterceptor.SingleResultCallbackProxy<?> proxy =\n            (MongoInternalConnectionSendAndReceiveAsync4TraceInterceptor.SingleResultCallbackProxy<?>) methodInfo.getArgs()[3];\n        proxy.onResult(null, null);\n        this.assertTrace(false, errMsg);\n        context.exit(interceptor.getEnterKey(methodInfo, context));\n    }\n\n}\n"
  },
  {
    "path": "plugins/mongodb/src/test/java/com/megaease/easeagent/plugin/mongodb/MongoTraceTest.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.mongodb;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.mongodb.interceptor.listener.MongoTraceCommandListener;\nimport com.mongodb.event.CommandFailedEvent;\nimport com.mongodb.event.CommandSucceededEvent;\nimport org.bson.BsonArray;\nimport org.bson.BsonDocument;\nimport org.bson.BsonString;\nimport org.bson.BsonValue;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class MongoTraceTest extends MongoBaseTest {\n    MongoTraceCommandListener listener;\n\n    @Before\n    public void before() {\n        super.before();\n        listener = new MongoTraceCommandListener(this.config);\n    }\n\n    @Test\n    public void performSuccess() {\n        BsonDocument bsonDocument = new BsonDocument();\n        bsonDocument.put(\"collection\", new BsonString(collection));\n        CommandSucceededEvent succeededEvent = new CommandSucceededEvent(this.requestId, this.connectionDescription, this.cmdName, bsonDocument, 10);\n        this.listener.commandStarted(startedEvent);\n        this.listener.commandSucceeded(succeededEvent);\n        this.assertTrace(true, null);\n    }\n\n    @Test\n    public void performOpFail() {\n        BsonDocument errDoc = new BsonDocument();\n        errDoc.put(\"errmsg\", new BsonString(errMsg));\n        List<BsonValue> list = new ArrayList<>();\n        list.add(errDoc);\n        BsonArray bsonValues = new BsonArray(list);\n        BsonDocument bsonDocument = new BsonDocument();\n        bsonDocument.put(\"collection\", new BsonString(collection));\n        bsonDocument.put(\"writeErrors\", bsonValues);\n        CommandSucceededEvent succeededEvent = new CommandSucceededEvent(this.requestId, this.connectionDescription, this.cmdName, bsonDocument, 10);\n        this.listener.commandStarted(startedEvent);\n        this.listener.commandSucceeded(succeededEvent);\n        this.assertTrace(false, errMsg);\n    }\n\n    @Test\n    public void performOpFail2() {\n        CommandFailedEvent failedEvent = new CommandFailedEvent(this.requestId, this.connectionDescription, this.cmdName, 10, new RuntimeException(this.errMsg));\n        this.listener.commandStarted(startedEvent);\n        this.listener.commandFailed(failedEvent);\n        this.assertTrace(false, errMsg);\n    }\n}\n"
  },
  {
    "path": "plugins/mongodb/src/test/java/com/megaease/easeagent/plugin/mongodb/TraceHelperTest.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.mongodb;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.mongodb.interceptor.TraceHelper;\nimport com.mongodb.event.CommandFailedEvent;\nimport com.mongodb.event.CommandSucceededEvent;\nimport org.bson.BsonArray;\nimport org.bson.BsonDocument;\nimport org.bson.BsonString;\nimport org.bson.BsonValue;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class TraceHelperTest extends MongoBaseTest {\n\n    @Test\n    public void performSuccess() {\n        BsonDocument bsonDocument = new BsonDocument();\n        bsonDocument.put(\"collection\", new BsonString(collection));\n        CommandSucceededEvent succeededEvent = new CommandSucceededEvent(this.requestId, this.connectionDescription, this.cmdName, bsonDocument, 10);\n        Context context = EaseAgent.getContext();\n        TraceHelper.commandStarted(context, this.config, this.startedEvent);\n        TraceHelper.commandSucceeded(context, succeededEvent);\n        this.assertTrace(true, null);\n    }\n\n    @Test\n    public void performOpFail() {\n        BsonDocument errDoc = new BsonDocument();\n        errDoc.put(\"errmsg\", new BsonString(errMsg));\n        List<BsonValue> list = new ArrayList<>();\n        list.add(errDoc);\n        BsonArray bsonValues = new BsonArray(list);\n        BsonDocument bsonDocument = new BsonDocument();\n        bsonDocument.put(\"collection\", new BsonString(collection));\n        bsonDocument.put(\"writeErrors\", bsonValues);\n        CommandSucceededEvent succeededEvent = new CommandSucceededEvent(this.requestId, this.connectionDescription, this.cmdName, bsonDocument, 10);\n        Context context = EaseAgent.getContext();\n        TraceHelper.commandStarted(context, this.config, this.startedEvent);\n        TraceHelper.commandSucceeded(context, succeededEvent);\n        this.assertTrace(false, errMsg);\n    }\n\n    @Test\n    public void performOpFail2() {\n        CommandFailedEvent failedEvent = new CommandFailedEvent(this.requestId, this.connectionDescription, this.cmdName, 10, new RuntimeException(this.errMsg));\n        Context context = EaseAgent.getContext();\n        TraceHelper.commandStarted(context, this.config, this.startedEvent);\n        TraceHelper.commandFailed(context, failedEvent);\n        this.assertTrace(false, errMsg);\n    }\n\n}\n"
  },
  {
    "path": "plugins/motan/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>plugins</artifactId>\n        <groupId>com.megaease.easeagent</groupId>\n        <version>2.3.0</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>motan</artifactId>\n\n    <properties>\n        <maven.compiler.source>18</maven.compiler.source>\n        <maven.compiler.target>18</maven.compiler.target>\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n        <motan.version>1.2.0</motan.version>\n    </properties>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>plugin-api</artifactId>\n            <scope>provided</scope>\n        </dependency>\n\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>plugin-api-mock</artifactId>\n            <version>${project.version}</version>\n            <scope>test</scope>\n        </dependency>\n\n        <!-- Motan -->\n        <dependency>\n            <groupId>com.weibo</groupId>\n            <artifactId>motan-core</artifactId>\n            <version>${motan.version}</version>\n            <scope>provided</scope>\n        </dependency>\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "plugins/motan/src/main/java/com/megaease/easeagent/plugin/motan/MotanPlugin.java",
    "content": "package com.megaease.easeagent.plugin.motan;\n\nimport com.megaease.easeagent.plugin.AgentPlugin;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\n\npublic class MotanPlugin implements AgentPlugin {\n\n    @Override\n    public String getNamespace() {\n        return ConfigConst.Namespace.MOTAN;\n    }\n\n    @Override\n    public String getDomain() {\n        return ConfigConst.OBSERVABILITY;\n    }\n}\n"
  },
  {
    "path": "plugins/motan/src/main/java/com/megaease/easeagent/plugin/motan/advice/MotanConsumerAdvice.java",
    "content": "package com.megaease.easeagent.plugin.motan.advice;\n\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.Set;\n\npublic class MotanConsumerAdvice implements Points {\n    private static final String ENHANCE_CLASS = \"com.weibo.api.motan.rpc.AbstractReferer\";\n\n    private static final String ENHANCE_METHOD = \"doCall\";\n\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return ClassMatcher.builder().hasSuperClass(ENHANCE_CLASS)\n            .build();\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.multiBuilder()\n            .match(\n                MethodMatcher.builder()\n                    .named(ENHANCE_METHOD)\n                    .build()\n            )\n            .build();\n    }\n}\n"
  },
  {
    "path": "plugins/motan/src/main/java/com/megaease/easeagent/plugin/motan/advice/MotanProviderAdvice.java",
    "content": "package com.megaease.easeagent.plugin.motan.advice;\n\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\nimport com.megaease.easeagent.plugin.tools.matcher.ClassMatcherUtils;\nimport com.megaease.easeagent.plugin.tools.matcher.MethodMatcherUtils;\n\nimport java.util.Set;\n\npublic class MotanProviderAdvice implements Points {\n    private static final String ENHANCE_CLASS = \"com.weibo.api.motan.transport.ProviderMessageRouter\";\n\n    private static final String ENHANCE_METHOD = \"call\";\n\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return ClassMatcherUtils.name(ENHANCE_CLASS);\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.multiBuilder()\n            .match(\n                MethodMatcherUtils.name(ENHANCE_METHOD)\n            )\n            .build();\n    }\n}\n"
  },
  {
    "path": "plugins/motan/src/main/java/com/megaease/easeagent/plugin/motan/config/MotanPluginConfig.java",
    "content": "package com.megaease.easeagent.plugin.motan.config;\n\nimport com.megaease.easeagent.plugin.api.config.AutoRefreshConfigSupplier;\nimport com.megaease.easeagent.plugin.api.config.AutoRefreshPluginConfig;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\n\npublic class MotanPluginConfig implements AutoRefreshPluginConfig {\n    public static final AutoRefreshConfigSupplier<MotanPluginConfig> SUPPLIER = new AutoRefreshConfigSupplier<MotanPluginConfig>(){\n        @Override\n        public MotanPluginConfig newInstance() {\n            return new MotanPluginConfig();\n        }\n    };\n\n    private static final String ARGS_COLLECT_ENABLED = \"args.collect.enabled\";\n    private static final String RESULT_COLLECT_ENABLED = \"result.collect.enabled\";\n\n    private volatile Boolean argsCollectEnabled = false;\n    private volatile Boolean resultCollectEnabled = false;\n\n    public Boolean argsCollectEnabled() {\n        return argsCollectEnabled;\n    }\n\n    public Boolean resultCollectEnabled() {\n        return resultCollectEnabled;\n    }\n\n    @Override\n    public void onChange(IPluginConfig oldConfig, IPluginConfig newConfig) {\n        String argsCollectEnabled = newConfig.getString(ARGS_COLLECT_ENABLED);\n        this.argsCollectEnabled = Boolean.parseBoolean(argsCollectEnabled);\n\n        String resultCollectEnabled = newConfig.getString(RESULT_COLLECT_ENABLED);\n        this.resultCollectEnabled = Boolean.parseBoolean(resultCollectEnabled);\n    }\n}\n"
  },
  {
    "path": "plugins/motan/src/main/java/com/megaease/easeagent/plugin/motan/interceptor/MotanClassUtils.java",
    "content": "package com.megaease.easeagent.plugin.motan.interceptor;\n\nimport com.megaease.easeagent.plugin.utils.ClassUtils.TypeChecker;\nimport com.weibo.api.motan.rpc.DefaultResponseFuture;\n\npublic enum MotanClassUtils {\n\tDefaultResponseFutureTypeChecker(new DefaultResponseFutureTypeChecker()),\n\t;\n\n\tprivate TypeChecker typeChecker;\n\n\tMotanClassUtils(TypeChecker typeChecker) {\n\t\tthis.typeChecker = typeChecker;\n\t}\n\n\tpublic TypeChecker getTypeChecker() {\n\t\treturn typeChecker;\n\t}\n\n\tpublic static class DefaultResponseFutureTypeChecker extends TypeChecker {\n\n\t\tpublic DefaultResponseFutureTypeChecker() {\n\t\t\tsuper(\"com.weibo.api.motan.rpc.DefaultResponseFuture\");\n\t\t}\n\n\t\t@Override\n\t\tprotected boolean isType(Object o) {\n\t\t\treturn o instanceof DefaultResponseFuture;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "plugins/motan/src/main/java/com/megaease/easeagent/plugin/motan/interceptor/MotanCtxUtils.java",
    "content": "package com.megaease.easeagent.plugin.motan.interceptor;\n\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.RequestContext;\nimport com.megaease.easeagent.plugin.api.middleware.Type;\nimport com.megaease.easeagent.plugin.api.trace.Scope;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.motan.config.MotanPluginConfig;\nimport com.megaease.easeagent.plugin.motan.interceptor.trace.MotanBaseInterceptor;\nimport com.megaease.easeagent.plugin.motan.interceptor.trace.MotanTags;\nimport com.megaease.easeagent.plugin.motan.interceptor.trace.consumer.MotanConsumerRequest;\nimport com.megaease.easeagent.plugin.motan.interceptor.trace.provider.MotanProviderRequest;\nimport com.megaease.easeagent.plugin.utils.common.JsonUtil;\nimport com.weibo.api.motan.common.URLParamType;\nimport com.weibo.api.motan.rpc.Future;\nimport com.weibo.api.motan.rpc.Request;\nimport com.weibo.api.motan.rpc.Response;\nimport com.weibo.api.motan.rpc.URL;\n\n\npublic class MotanCtxUtils {\n\n\tpublic static final String CLIENT_REQUEST_CONTEXT = MotanCtxUtils.class.getName() + \".CLIENT_REQUEST_CONTEXT\";\n\tpublic static final String SERVER_REQUEST_CONTEXT = MotanCtxUtils.class.getName() + \".SERVER_REQUEST_CONTEXT\";\n\tpublic static final String METRICS_SERVICE_NAME = MotanCtxUtils.class.getName() + \".METRICS_SERVICE_NAME\";\n\tpublic static final String BEGIN_TIME = MotanCtxUtils.class.getName() + \".BEGIN_TIME\";\n\n\tpublic static String interfaceSignature(Request request) {\n\t\treturn new StringBuilder(request.getInterfaceName())\n\t\t\t\t.append(\".\")\n\t\t\t\t.append(request.getMethodName())\n\t\t\t\t.append(\"(\")\n\t\t\t\t.append(getParametersDesc(request))\n\t\t\t\t.append(\")\")\n\t\t\t\t.toString();\n\t}\n\n\tpublic static String method(Request request) {\n\t\treturn new StringBuilder(request.getMethodName())\n\t\t\t\t.append(\"(\")\n\t\t\t\t.append(getParametersDesc(request))\n\t\t\t\t.append(\")\")\n\t\t\t\t.toString();\n\t}\n\n    private static String getParametersDesc(Request request) {\n        return request.getParamtersDesc().equals(void.class.getSimpleName()) ? \"\" : request.getParamtersDesc();\n    }\n\n    public static String name(Request request) {\n\t\tString interfaceFullName = request.getInterfaceName();\n\t\tString interfaceName = interfaceFullName.substring(interfaceFullName.lastIndexOf(\".\") + 1);\n\t\tStringBuilder argsStringBuilder = new StringBuilder();\n\t\tObject[] arguments = request.getArguments();\n        if (arguments != null) {\n            for (int i = 0; i < arguments.length; i++) {\n                argsStringBuilder.append(arguments[i].getClass().getSimpleName());\n                if (i != arguments.length - 1) {\n                    argsStringBuilder.append(\",\");\n                }\n            }\n        }\n\t\treturn String.format(\"%s/%s(%s)\", interfaceName, request.getMethodName(), argsStringBuilder);\n\t}\n\n\tpublic static void initProviderSpan(Context context, URL url, Request request) {\n\t\tMotanProviderRequest motanProviderRequest = new MotanProviderRequest(request);\n\t\tRequestContext requestContext = context.serverReceive(motanProviderRequest);\n\t\tSpan span = requestContext.span().start();\n\t\tspan.remoteServiceName(Type.MOTAN.getRemoteType());\n\t\tspan.kind(motanProviderRequest.kind());\n\t\tspan.name(motanProviderRequest.name());\n\t\tspan.remoteIpAndPort(url.getHost(), url.getPort());\n\t\tcontext.put(SERVER_REQUEST_CONTEXT, requestContext);\n\t}\n\n\tpublic static void initConsumerSpan(Context context, URL url, Request request) {\n\t\tMotanConsumerRequest motanClientRequest = new MotanConsumerRequest(request);\n\t\tRequestContext requestContext = context.clientRequest(motanClientRequest);\n\t\tSpan span = requestContext.span().start();\n\t\tspan.remoteServiceName(Type.MOTAN.getRemoteType());\n\t\tspan.kind(motanClientRequest.kind());\n\t\tspan.name(motanClientRequest.name());\n\t\tspan.tag(MotanTags.APPLICATION.name, url.getParameter(URLParamType.application.getName(),URLParamType.application.getValue()));\n\t\tspan.tag(MotanTags.GROUP.name, url.getGroup());\n\t\tspan.tag(MotanTags.MODULE.name, url.getParameter(URLParamType.module.getName(),URLParamType.module.getValue()));\n\t\tspan.tag(MotanTags.SERVICE.name, request.getInterfaceName());\n\t\tspan.tag(MotanTags.SERVICE_VERSION.name, url.getVersion());\n\t\tspan.tag(MotanTags.METHOD.name, method(request));\n\t\tString args = null;\n\t\tif (MotanBaseInterceptor.MOTAN_PLUGIN_CONFIG.argsCollectEnabled()) {\n\t\t\targs = JsonUtil.toJson(request.getArguments());\n\t\t}\n\t\tspan.tag(MotanTags.ARGUMENTS.name, args);\n\t\tspan.remoteIpAndPort(url.getHost(), url.getPort());\n\t\tcontext.put(CLIENT_REQUEST_CONTEXT, requestContext);\n\t}\n\n\tpublic static void finishConsumerSpan(Response response, Throwable throwable, Context context) {\n\t\tRequestContext requestContext = context.remove(CLIENT_REQUEST_CONTEXT);\n\t\tjudgmentFinishSpan(response, throwable, requestContext);\n\t}\n\n\tpublic static void finishConsumerSpan(Future future, Context context) {\n\t\tRequestContext requestContext = context.remove(CLIENT_REQUEST_CONTEXT);\n\t\tif (future.getException() != null) {\n\t\t\tfinishSpan(null, future.getException(), requestContext);\n\t\t} else {\n\t\t\tfinishSpan(future.getValue(), null, requestContext);\n\t\t}\n\t}\n\n\tpublic static void finishProviderSpan(Response response, Throwable throwable, Context context) {\n\t\tRequestContext requestContext = context.remove(SERVER_REQUEST_CONTEXT);\n\t\tjudgmentFinishSpan(response, throwable, requestContext);\n\t}\n\n\tprivate static void judgmentFinishSpan(Response response, Throwable throwable, RequestContext requestContext) {\n\t\tif (throwable != null) {\n\t\t\tfinishSpan(null, throwable, requestContext);\n\t\t} else if (response.getException() != null) {\n\t\t\tfinishSpan(null, response.getException(), requestContext);\n\t\t} else {\n\t\t\tfinishSpan(response.getValue(), null, requestContext);\n\t\t}\n\t}\n\n\tprivate static void finishSpan(Object retValue, Throwable throwable, RequestContext requestContext) {\n\t\tif (requestContext == null) {\n\t\t\treturn;\n\t\t}\n\t\tMotanPluginConfig motanPluginConfig = MotanBaseInterceptor.MOTAN_PLUGIN_CONFIG;\n\t\ttry (Scope scope = requestContext.scope()) {\n\t\t\tSpan span = requestContext.span();\n\t\t\tif (span.isNoop()) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (throwable != null) {\n\t\t\t\tspan.error(throwable);\n\t\t\t}\n\t\t\tif (motanPluginConfig.resultCollectEnabled() && retValue != null) {\n\t\t\t\tspan.tag(MotanTags.RESULT.name, JsonUtil.toJson(retValue));\n\t\t\t}\n\t\t\tspan.finish();\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "plugins/motan/src/main/java/com/megaease/easeagent/plugin/motan/interceptor/metrics/MetricsFutureListener.java",
    "content": "package com.megaease.easeagent.plugin.motan.interceptor.metrics;\n\nimport com.megaease.easeagent.plugin.api.Cleaner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.AsyncContext;\nimport com.megaease.easeagent.plugin.api.context.ContextUtils;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.motan.interceptor.MotanCtxUtils;\nimport com.weibo.api.motan.rpc.Future;\nimport com.weibo.api.motan.rpc.FutureListener;\n\nimport static com.megaease.easeagent.plugin.motan.interceptor.metrics.MotanBaseMetricsInterceptor.MOTAN_METRIC;\n\npublic class MetricsFutureListener implements FutureListener {\n\tprivate final AsyncContext asyncContext;\n\n\n\tpublic MetricsFutureListener(AsyncContext asyncContext) {\n\t\tthis.asyncContext = asyncContext;\n\t}\n\n\t@Override\n\tpublic void operationComplete(Future future) throws Exception {\n\t\ttry(Cleaner cleaner = asyncContext.importToCurrent()){\n\t\t\tContext context = EaseAgent.getContext();\n\t\t\tLong duration = ContextUtils.getDuration(context,MotanCtxUtils.BEGIN_TIME);\n\t\t\tString service = context.get(MotanCtxUtils.METRICS_SERVICE_NAME);\n\t\t\tboolean callResult = future.getException() == null;\n            MOTAN_METRIC.collectMetric(service, duration, callResult);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "plugins/motan/src/main/java/com/megaease/easeagent/plugin/motan/interceptor/metrics/MotanBaseMetricsInterceptor.java",
    "content": "package com.megaease.easeagent.plugin.motan.interceptor.metrics;\n\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.metric.ServiceMetricRegistry;\nimport com.megaease.easeagent.plugin.api.metric.name.Tags;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\n\npublic abstract class MotanBaseMetricsInterceptor implements Interceptor {\n    public static volatile MotanMetric MOTAN_METRIC;\n\n    @Override\n    public int order() {\n        return Order.METRIC.getOrder();\n    }\n\n    @Override\n    public String getType() {\n        return Order.METRIC.getName();\n    }\n\n    @Override\n    public void init(IPluginConfig config, String className, String methodName, String methodDescriptor) {\n        Tags tags = new Tags(MotanMetricTags.CATEGORY.name, MotanMetricTags.TYPE.name, MotanMetricTags.LABEL_NAME.name);\n        MOTAN_METRIC = ServiceMetricRegistry.getOrCreate(config, tags, MotanMetric.MOTAN_METRIC_SUPPLIER);\n    }\n\n}\n"
  },
  {
    "path": "plugins/motan/src/main/java/com/megaease/easeagent/plugin/motan/interceptor/metrics/MotanMetric.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.motan.interceptor.metrics;\n\nimport com.megaease.easeagent.plugin.api.metric.*;\nimport com.megaease.easeagent.plugin.api.metric.name.*;\nimport com.megaease.easeagent.plugin.tools.metrics.LastMinutesCounterGauge;\nimport com.megaease.easeagent.plugin.utils.ImmutableMap;\n\nimport javax.annotation.Nonnull;\nimport java.util.HashMap;\nimport java.util.concurrent.TimeUnit;\n\npublic class MotanMetric extends ServiceMetric {\n\n    public MotanMetric(@Nonnull MetricRegistry metricRegistry, @Nonnull NameFactory nameFactory) {\n        super(metricRegistry, nameFactory);\n    }\n\n    public static final ServiceMetricSupplier<MotanMetric> MOTAN_METRIC_SUPPLIER = new ServiceMetricSupplier<MotanMetric>() {\n        @Override\n        public NameFactory newNameFactory() {\n            return MotanMetric.nameFactory();\n        }\n\n        @Override\n        public MotanMetric newInstance(MetricRegistry metricRegistry, NameFactory nameFactory) {\n            return new MotanMetric(metricRegistry, nameFactory);\n        }\n    };\n\n    public static NameFactory nameFactory() {\n        return NameFactory.createBuilder()\n            .timerType(MetricSubType.DEFAULT,\n                ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                    .put(MetricField.MIN_EXECUTION_TIME, MetricValueFetcher.SnapshotMinValue)\n                    .put(MetricField.MAX_EXECUTION_TIME, MetricValueFetcher.SnapshotMaxValue)\n                    .put(MetricField.MEAN_EXECUTION_TIME, MetricValueFetcher.SnapshotMeanValue)\n                    .put(MetricField.P25_EXECUTION_TIME, MetricValueFetcher.Snapshot25Percentile)\n                    .put(MetricField.P50_EXECUTION_TIME, MetricValueFetcher.Snapshot50PercentileValue)\n                    .put(MetricField.P75_EXECUTION_TIME, MetricValueFetcher.Snapshot75PercentileValue)\n                    .put(MetricField.P95_EXECUTION_TIME, MetricValueFetcher.Snapshot95PercentileValue)\n                    .put(MetricField.P98_EXECUTION_TIME, MetricValueFetcher.Snapshot98PercentileValue)\n                    .put(MetricField.P99_EXECUTION_TIME, MetricValueFetcher.Snapshot99PercentileValue)\n                    .put(MetricField.P999_EXECUTION_TIME, MetricValueFetcher.Snapshot999PercentileValue)\n                    .build())\n            .gaugeType(MetricSubType.DEFAULT, new HashMap<>())\n            .meterType(MetricSubType.DEFAULT,\n                ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                    .put(MetricField.M1_RATE, MetricValueFetcher.MeteredM1Rate)\n                    .put(MetricField.M5_RATE, MetricValueFetcher.MeteredM5Rate)\n                    .put(MetricField.M15_RATE, MetricValueFetcher.MeteredM15Rate)\n                    .put(MetricField.MEAN_RATE, MetricValueFetcher.MeteredMeanRate)\n                    .build())\n            .meterType(MetricSubType.ERROR,\n                ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                    .put(MetricField.M1_ERROR_RATE, MetricValueFetcher.MeteredM1Rate)\n                    .put(MetricField.M5_ERROR_RATE, MetricValueFetcher.MeteredM5Rate)\n                    .put(MetricField.M15_ERROR_RATE, MetricValueFetcher.MeteredM15Rate)\n                    .build())\n            .counterType(MetricSubType.ERROR, ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                .put(MetricField.EXECUTION_ERROR_COUNT, MetricValueFetcher.CountingCount)\n                .build())\n            .counterType(MetricSubType.DEFAULT, ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                .put(MetricField.EXECUTION_COUNT, MetricValueFetcher.CountingCount)\n                .build())\n            .build();\n    }\n\n    public void collectMetric(String key, long duration, boolean success) {\n        metricRegistry.timer(this.nameFactory.timerName(key, MetricSubType.DEFAULT)).update(duration, TimeUnit.MILLISECONDS);\n        Meter defaultMeter = metricRegistry.meter(nameFactory.meterName(key, MetricSubType.DEFAULT));\n        Counter defaultCounter = metricRegistry.counter(nameFactory.counterName(key, MetricSubType.DEFAULT));\n        Meter errorMeter = metricRegistry.meter(nameFactory.meterName(key, MetricSubType.ERROR));\n        Counter errorCounter = metricRegistry.counter(nameFactory.counterName(key, MetricSubType.ERROR));\n        if (!success) {\n            errorMeter.mark();\n            errorCounter.inc();\n        }\n        defaultMeter.mark();\n        defaultCounter.inc();\n\n        MetricName gaugeName = nameFactory.gaugeNames(key).get(MetricSubType.DEFAULT);\n        metricRegistry.gauge(gaugeName.name(), () -> () ->\n            LastMinutesCounterGauge.builder()\n                .m1Count((long) (defaultMeter.getOneMinuteRate() * 60))\n                .m5Count((long) (defaultMeter.getFiveMinuteRate() * 60 * 5))\n                .m15Count((long) (defaultMeter.getFifteenMinuteRate() * 60 * 15))\n                .build());\n    }\n}\n"
  },
  {
    "path": "plugins/motan/src/main/java/com/megaease/easeagent/plugin/motan/interceptor/metrics/MotanMetricTags.java",
    "content": "package com.megaease.easeagent.plugin.motan.interceptor.metrics;\n\npublic enum MotanMetricTags {\n\tCATEGORY(\"application\"),\n\tTYPE(\"motan\"),\n\tLABEL_NAME(\"interface\"),\n\t;\n\n\tpublic final String name;\n\n\tMotanMetricTags(String name) {\n\t\tthis.name = name;\n\t}\n}\n"
  },
  {
    "path": "plugins/motan/src/main/java/com/megaease/easeagent/plugin/motan/interceptor/metrics/MotanMetricsInterceptor.java",
    "content": "package com.megaease.easeagent.plugin.motan.interceptor.metrics;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.ContextUtils;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.motan.MotanPlugin;\nimport com.megaease.easeagent.plugin.motan.advice.MotanConsumerAdvice;\nimport com.megaease.easeagent.plugin.motan.interceptor.MotanClassUtils;\nimport com.megaease.easeagent.plugin.motan.interceptor.MotanCtxUtils;\nimport com.megaease.easeagent.plugin.utils.SystemClock;\nimport com.weibo.api.motan.rpc.DefaultResponseFuture;\nimport com.weibo.api.motan.rpc.Request;\nimport com.weibo.api.motan.rpc.Response;\n\n@AdviceTo(value = MotanConsumerAdvice.class, plugin = MotanPlugin.class)\npublic class MotanMetricsInterceptor extends MotanBaseMetricsInterceptor {\n\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n        context.put(MotanCtxUtils.BEGIN_TIME, SystemClock.now());\n    }\n\n    @Override\n    public void after(MethodInfo methodInfo, Context context) {\n        Request request = (Request) methodInfo.getArgs()[0];\n        Response response = (Response) methodInfo.getRetValue();\n        Throwable throwable = methodInfo.getThrowable();\n        String interfaceSignature = MotanCtxUtils.interfaceSignature(request);\n        context.put(MotanCtxUtils.METRICS_SERVICE_NAME,interfaceSignature);\n\n        if (MotanClassUtils.DefaultResponseFutureTypeChecker.getTypeChecker().hasClassAndIsType(response)) {\n            DefaultResponseFuture defaultResponseFuture = (DefaultResponseFuture) response;\n            defaultResponseFuture.addListener(new MetricsFutureListener(context.exportAsync()));\n        } else {\n            Long duration = ContextUtils.getDuration(context,MotanCtxUtils.BEGIN_TIME);\n            boolean callResult = throwable == null && response != null && response.getException() == null;\n            MOTAN_METRIC.collectMetric(interfaceSignature, duration, callResult);\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/motan/src/main/java/com/megaease/easeagent/plugin/motan/interceptor/trace/MotanBaseInterceptor.java",
    "content": "package com.megaease.easeagent.plugin.motan.interceptor.trace;\n\nimport com.megaease.easeagent.plugin.api.config.AutoRefreshPluginConfigRegistry;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.motan.config.MotanPluginConfig;\n\npublic abstract class MotanBaseInterceptor implements Interceptor {\n\n    public static volatile MotanPluginConfig MOTAN_PLUGIN_CONFIG = null;\n\n    @Override\n    public void init(IPluginConfig config, String className, String methodName, String methodDescriptor) {\n        MOTAN_PLUGIN_CONFIG = AutoRefreshPluginConfigRegistry.getOrCreate(ConfigConst.OBSERVABILITY, ConfigConst.Namespace.MOTAN, this.getType(), MotanPluginConfig.SUPPLIER);\n    }\n}\n"
  },
  {
    "path": "plugins/motan/src/main/java/com/megaease/easeagent/plugin/motan/interceptor/trace/MotanTags.java",
    "content": "package com.megaease.easeagent.plugin.motan.interceptor.trace;\n\npublic enum MotanTags {\n    APPLICATION(\"motan.application\"),\n    GROUP(\"motan.group\"),\n    MODULE(\"motan.module\"),\n    SERVICE(\"motan.service\"),\n    SERVICE_VERSION(\"motan.service.version\"),\n    METHOD(\"motan.method\"),\n    ARGUMENTS(\"motan.args\"),\n    RESULT(\"motan.result\"),\n    ;\n\n    public final String name;\n\n    MotanTags(String tagName) {\n        this.name = tagName;\n    }\n}\n"
  },
  {
    "path": "plugins/motan/src/main/java/com/megaease/easeagent/plugin/motan/interceptor/trace/consumer/MotanConsumerRequest.java",
    "content": "package com.megaease.easeagent.plugin.motan.interceptor.trace.consumer;\n\nimport com.megaease.easeagent.plugin.api.trace.Request;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.motan.interceptor.MotanCtxUtils;\n\npublic class MotanConsumerRequest implements Request {\n    private com.weibo.api.motan.rpc.Request request;\n\n    public MotanConsumerRequest(com.weibo.api.motan.rpc.Request request) {\n        this.request = request;\n    }\n\n    @Override\n    public Span.Kind kind() {\n        return Span.Kind.CLIENT;\n    }\n\n    @Override\n    public String header(String name) {\n        return request.getAttachments().get(name);\n    }\n\n    @Override\n    public String name() {\n        return MotanCtxUtils.name(request);\n    }\n\n    @Override\n    public boolean cacheScope() {\n        return false;\n    }\n\n    @Override\n    public void setHeader(String name, String value) {\n        request.getAttachments().put(name, value);\n    }\n}\n"
  },
  {
    "path": "plugins/motan/src/main/java/com/megaease/easeagent/plugin/motan/interceptor/trace/consumer/MotanConsumerTraceInterceptor.java",
    "content": "package com.megaease.easeagent.plugin.motan.interceptor.trace.consumer;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.RequestContext;\nimport com.megaease.easeagent.plugin.api.trace.Scope;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.motan.MotanPlugin;\nimport com.megaease.easeagent.plugin.motan.advice.MotanConsumerAdvice;\nimport com.megaease.easeagent.plugin.motan.interceptor.MotanClassUtils;\nimport com.megaease.easeagent.plugin.motan.interceptor.MotanCtxUtils;\nimport com.megaease.easeagent.plugin.motan.interceptor.trace.MotanBaseInterceptor;\nimport com.weibo.api.motan.rpc.*;\n\n@AdviceTo(value = MotanConsumerAdvice.class, plugin = MotanPlugin.class)\npublic class MotanConsumerTraceInterceptor extends MotanBaseInterceptor {\n\n\t@Override\n\tpublic int order() {\n\t\treturn Order.TRACING.getOrder();\n\t}\n\n\t@Override\n\tpublic void before(MethodInfo methodInfo, Context context) {\n\t\tURL url = ((AbstractReferer<?>) methodInfo.getInvoker()).getUrl();\n\t\tRequest request = (Request) methodInfo.getArgs()[0];\n\t\tMotanCtxUtils.initConsumerSpan(context, url, request);\n\t}\n\n\t@Override\n\tpublic void after(MethodInfo methodInfo, Context context) {\n\t\tResponse response = (Response) methodInfo.getRetValue();\n\t\tif (MotanClassUtils.DefaultResponseFutureTypeChecker.getTypeChecker().hasClassAndIsType(response)) {\n            RequestContext requestContext = context.get(MotanCtxUtils.CLIENT_REQUEST_CONTEXT);\n            if (requestContext == null) {\n                return;\n            }\n            try(Scope scope = requestContext.scope()) {\n                DefaultResponseFuture defaultResponseFuture = (DefaultResponseFuture) response;\n                defaultResponseFuture.addListener(new TraceFutureListener(context.exportAsync()));\n            }\n\t\t} else {\n\t\t\tMotanCtxUtils.finishConsumerSpan(response, methodInfo.getThrowable(), context);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "plugins/motan/src/main/java/com/megaease/easeagent/plugin/motan/interceptor/trace/consumer/TraceFutureListener.java",
    "content": "package com.megaease.easeagent.plugin.motan.interceptor.trace.consumer;\n\nimport com.megaease.easeagent.plugin.api.Cleaner;\nimport com.megaease.easeagent.plugin.api.context.AsyncContext;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.motan.interceptor.MotanCtxUtils;\nimport com.weibo.api.motan.rpc.Future;\nimport com.weibo.api.motan.rpc.FutureListener;\n\npublic class TraceFutureListener implements FutureListener {\n\tprivate AsyncContext asyncContext;\n\n\tpublic TraceFutureListener(AsyncContext asyncContext) {\n\t\tthis.asyncContext = asyncContext;\n\t}\n\n\t@Override\n\tpublic void operationComplete(Future future) throws Exception {\n\t\ttry(Cleaner cleaner = asyncContext.importToCurrent()){\n\t\t\tMotanCtxUtils.finishConsumerSpan(future, EaseAgent.getContext());\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "plugins/motan/src/main/java/com/megaease/easeagent/plugin/motan/interceptor/trace/provider/MotanProviderRequest.java",
    "content": "package com.megaease.easeagent.plugin.motan.interceptor.trace.provider;\n\nimport com.megaease.easeagent.plugin.api.trace.Request;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.motan.interceptor.MotanCtxUtils;\n\npublic class MotanProviderRequest implements Request {\n\n    private final com.weibo.api.motan.rpc.Request request;\n\n    public MotanProviderRequest(com.weibo.api.motan.rpc.Request request) {\n        this.request = request;\n    }\n\n    @Override\n    public Span.Kind kind() {\n        return Span.Kind.SERVER;\n    }\n\n    @Override\n    public String header(String name) {\n        return request.getAttachments().get(name);\n    }\n\n    @Override\n    public String name() {\n        return MotanCtxUtils.name(request);\n    }\n\n    @Override\n    public boolean cacheScope() {\n        return false;\n    }\n\n    @Override\n    public void setHeader(String name, String value) {\n        request.getAttachments().put(name, value);\n    }\n}\n"
  },
  {
    "path": "plugins/motan/src/main/java/com/megaease/easeagent/plugin/motan/interceptor/trace/provider/MotanProviderTraceInterceptor.java",
    "content": "package com.megaease.easeagent.plugin.motan.interceptor.trace.provider;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.motan.MotanPlugin;\nimport com.megaease.easeagent.plugin.motan.advice.MotanProviderAdvice;\nimport com.megaease.easeagent.plugin.motan.interceptor.trace.MotanBaseInterceptor;\nimport com.megaease.easeagent.plugin.motan.interceptor.MotanCtxUtils;\nimport com.weibo.api.motan.rpc.Provider;\nimport com.weibo.api.motan.rpc.Request;\nimport com.weibo.api.motan.rpc.Response;\n\n@AdviceTo(value = MotanProviderAdvice.class, plugin = MotanPlugin.class)\npublic class MotanProviderTraceInterceptor extends MotanBaseInterceptor {\n\n    @Override\n    public int order() {\n        return Order.TRACING.getOrder();\n    }\n\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n        Request request = (Request) methodInfo.getArgs()[0];\n        Provider<?> provider = (Provider<?>) methodInfo.getArgs()[1];\n        MotanCtxUtils.initProviderSpan(context, provider.getUrl(), request);\n    }\n\n    @Override\n    public void after(MethodInfo methodInfo, Context context) {\n        Response response = (Response) methodInfo.getRetValue();\n        Throwable throwable = methodInfo.getThrowable();\n        MotanCtxUtils.finishProviderSpan(response, throwable, context);\n    }\n\n}\n"
  },
  {
    "path": "plugins/motan/src/test/java/com/megaease/easeagent/plugin/motan/interceptor/metrics/MotanMetricsInterceptorTest.java",
    "content": "package com.megaease.easeagent.plugin.motan.interceptor.metrics;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.api.context.ContextUtils;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.motan.interceptor.MotanCtxUtils;\nimport com.megaease.easeagent.plugin.motan.interceptor.trace.MotanInterceptorTest;\nimport com.weibo.api.motan.rpc.DefaultResponseFuture;\nimport com.weibo.api.motan.rpc.ResponseFuture;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotNull;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class MotanMetricsInterceptorTest extends MotanInterceptorTest {\n\n    private static final MotanMetricsInterceptor motanMetricsInterceptor = new MotanMetricsInterceptor();\n\n    @Test\n    public void getType() {\n        assertEquals(ConfigConst.PluginID.METRIC, motanMetricsInterceptor.getType());\n    }\n\n    @Test\n    public void order() {\n        assertEquals(Order.METRIC.getOrder(), motanMetricsInterceptor.order());\n    }\n\n    @Override\n    protected Interceptor createInterceptor() {\n        return motanMetricsInterceptor;\n    }\n\n\n    @Test\n    public void before() {\n        Context context = EaseAgent.getContext();\n        MethodInfo methodInfo = MethodInfo.builder()\n            .invoker(defaultRpcReferer)\n            .args(new Object[]{request})\n            .retValue(failureResponse)\n            .build();\n        motanMetricsInterceptor.before(methodInfo, context);\n        assertNotNull(ContextUtils.getFromContext(context, MotanCtxUtils.BEGIN_TIME));\n    }\n\n    @Test\n    public void rpcCallFailure() {\n\n        MethodInfo methodInfo = MethodInfo.builder()\n                .invoker(defaultRpcReferer)\n                .args(new Object[]{request})\n                .retValue(failureResponse)\n                .build();\n\n        Context context = EaseAgent.getContext();\n        motanMetricsInterceptor.before(methodInfo, context);\n        motanMetricsInterceptor.after(methodInfo, context);\n\n        assertFailureMetrics();\n    }\n\n    @Test\n    public void rpcCallException() {\n\n        MethodInfo methodInfo = MethodInfo.builder()\n                .invoker(defaultRpcReferer)\n                .args(new Object[]{request})\n                .throwable(failureResponse.getException())\n                .build();\n\n        Context context = EaseAgent.getContext();\n        motanMetricsInterceptor.before(methodInfo, context);\n        motanMetricsInterceptor.after(methodInfo, context);\n\n        assertFailureMetrics();\n    }\n\n    @Test\n    public void rpcCallSuccess() {\n\n        MethodInfo methodInfo = MethodInfo.builder()\n                .invoker(defaultRpcReferer)\n                .args(new Object[]{request})\n                .retValue(successResponse)\n                .build();\n\n        Context context = EaseAgent.getContext();\n        motanMetricsInterceptor.before(methodInfo, context);\n        motanMetricsInterceptor.after(methodInfo, context);\n\n        assertSuccessMetrics();\n    }\n\n\n    @Test\n    public void rpcAsyncCallFailure() throws InterruptedException {\n        DefaultResponseFuture defaultResponseFuture = new DefaultResponseFuture(null, 0, null);\n\n        MethodInfo methodInfo = MethodInfo.builder()\n                .invoker(defaultRpcReferer)\n                .args(new Object[]{request})\n                .retValue(defaultResponseFuture)\n                .build();\n\n        Context context = EaseAgent.getContext();\n        motanMetricsInterceptor.before(methodInfo, context);\n        motanMetricsInterceptor.after(methodInfo, context);\n        ResponseFuture retValue = (ResponseFuture) methodInfo.getRetValue();\n        Thread thread = new Thread(() -> {\n            retValue.onFailure(failureResponse);\n        });\n        thread.start();\n        thread.join();\n\n        assertFailureMetrics();\n    }\n\n    @Test\n    public void rpcAsyncCallSuccess() throws InterruptedException {\n        DefaultResponseFuture defaultResponseFuture = new DefaultResponseFuture(null, 0, null);\n\n        MethodInfo methodInfo = MethodInfo.builder()\n                .invoker(defaultRpcReferer)\n                .args(new Object[]{request})\n                .retValue(defaultResponseFuture)\n                .build();\n\n        Context context = EaseAgent.getContext();\n        motanMetricsInterceptor.before(methodInfo, context);\n        motanMetricsInterceptor.after(methodInfo, context);\n        ResponseFuture retValue = (ResponseFuture) methodInfo.getRetValue();\n        Thread thread = new Thread(() -> {\n            retValue.onSuccess(successResponse);\n        });\n        thread.start();\n        thread.join();\n\n        assertSuccessMetrics();\n    }\n}\n"
  },
  {
    "path": "plugins/motan/src/test/java/com/megaease/easeagent/plugin/motan/interceptor/trace/MotanInterceptorTest.java",
    "content": "package com.megaease.easeagent.plugin.motan.interceptor.trace;\n\nimport com.megaease.easeagent.mock.config.MockConfig;\nimport com.megaease.easeagent.mock.plugin.api.MockEaseAgent;\nimport com.megaease.easeagent.mock.plugin.api.utils.InterceptorTestUtils;\nimport com.megaease.easeagent.mock.plugin.api.utils.TagVerifier;\nimport com.megaease.easeagent.mock.report.impl.LastJsonReporter;\nimport com.megaease.easeagent.plugin.api.metric.name.MetricField;\nimport com.megaease.easeagent.plugin.api.metric.name.Tags;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.motan.MotanPlugin;\nimport com.megaease.easeagent.plugin.motan.interceptor.MotanCtxUtils;\nimport com.megaease.easeagent.plugin.motan.interceptor.metrics.MotanMetricTags;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport com.megaease.easeagent.plugin.utils.common.JsonUtil;\nimport com.weibo.api.motan.protocol.rpc.DefaultRpcReferer;\nimport com.weibo.api.motan.rpc.*;\nimport org.junit.Before;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static org.junit.Assert.*;\nimport static org.mockito.Mockito.when;\n\npublic abstract class MotanInterceptorTest {\n\t@Mock\n\tprotected DefaultRpcReferer<?> defaultRpcReferer;\n\n\t@Mock\n\tprotected Provider<?> provider;\n\n\tprotected DefaultRequest request;\n\n\tprotected DefaultResponse successResponse = new DefaultResponse(\"success\");\n\n\tprotected DefaultResponse failureResponse = new DefaultResponse();\n\n\tprotected RuntimeException motanException = new RuntimeException(\"motan exception\");\n\tprivate static final MotanPlugin motanPlugin = new MotanPlugin();\n\n\tprotected abstract Interceptor createInterceptor();\n\n\t@Before\n\tpublic void init() {\n\t\tMockitoAnnotations.openMocks(this);\n\n\t\tURL url = URL.valueOf(\"motan://127.0.0.1:1234/com.megaease.easeagent.motan.TestService.test(String,Integer)\");\n\t\twhen(defaultRpcReferer.getUrl()).thenReturn(url);\n\t\twhen(provider.getUrl()).thenReturn(url);\n\n\t\tMap<String, String> attachments = new HashMap<>();\n\n\t\trequest = new DefaultRequest();\n\t\trequest.setInterfaceName(\"com.megaease.easeagent.motan.TestService\");\n\t\trequest.setMethodName(\"test\");\n\t\trequest.setParamtersDesc(\"String,Integer\");\n\t\trequest.setArguments(new Object[]{\"motan test\", 123});\n\t\trequest.setAttachments(attachments);\n\n\t\tfailureResponse.setException(motanException);\n\n\t\tEaseAgent.configFactory = MockConfig.getPluginConfigManager();\n\t\tInterceptorTestUtils.init(createInterceptor(), motanPlugin);\n\t}\n\n\tprotected void assertSuccessMetrics() {\n\t\tassertMetrics(true);\n\t}\n\n\tprotected void assertFailureMetrics() {\n\t\tassertMetrics(false);\n\t}\n\n\tprivate void assertMetrics(boolean isSuccess) {\n\t\tTagVerifier tagVerifier = new TagVerifier()\n\t\t\t\t.add(Tags.CATEGORY, MotanMetricTags.CATEGORY.name)\n\t\t\t\t.add(Tags.TYPE, MotanMetricTags.TYPE.name)\n\t\t\t\t.add(MotanMetricTags.LABEL_NAME.name, MotanCtxUtils.interfaceSignature(request));\n\t\tLastJsonReporter lastJsonReporter = MockEaseAgent.lastMetricJsonReporter(tagVerifier::verifyAnd);\n\t\tMap<String, Object> metrics = lastJsonReporter.flushAndOnlyOne();\n\t\tassertEquals(1, metrics.get(MetricField.EXECUTION_COUNT.getField()));\n\t\tif (isSuccess) {\n\t\t\tassertEquals(0, metrics.get(MetricField.EXECUTION_ERROR_COUNT.getField()));\n\t\t} else {\n\t\t\tassertEquals(1, metrics.get(MetricField.EXECUTION_ERROR_COUNT.getField()));\n\t\t}\n\t}\n\n\tprotected void assertConsumerTrace(Object result, String errorMessage) {\n\t\tURL clientUrl = defaultRpcReferer.getUrl();\n\t\tReportSpan reportSpan = MockEaseAgent.getLastSpan();\n\t\tassertNotNull(reportSpan);\n\t\tassertEquals(reportSpan.name(), MotanCtxUtils.name(request).toLowerCase());\n\t\tassertEquals(Span.Kind.CLIENT.name(), reportSpan.kind());\n\t\tassertEquals(clientUrl.getApplication(), reportSpan.tag(MotanTags.APPLICATION.name));\n\t\tassertEquals(clientUrl.getGroup(), reportSpan.tag(MotanTags.GROUP.name));\n\t\tassertEquals(clientUrl.getModule(), reportSpan.tag(MotanTags.MODULE.name));\n\t\tassertEquals(request.getInterfaceName(), reportSpan.tag(MotanTags.SERVICE.name));\n\t\tassertEquals(clientUrl.getVersion(), reportSpan.tag(MotanTags.SERVICE_VERSION.name));\n\t\tassertEquals(MotanCtxUtils.method(request), reportSpan.tag(MotanTags.METHOD.name));\n\t\tString expectedArgs = null;\n\t\tif (MotanBaseInterceptor.MOTAN_PLUGIN_CONFIG.argsCollectEnabled()) {\n\t\t\texpectedArgs = JsonUtil.toJson(request.getArguments());\n\t\t}\n\t\tString expectedResult = null;\n\t\tif (MotanBaseInterceptor.MOTAN_PLUGIN_CONFIG.resultCollectEnabled() && result != null) {\n\t\t\texpectedResult = JsonUtil.toJson(result);\n\t\t}\n\t\tassertEquals(expectedArgs, reportSpan.tag(MotanTags.ARGUMENTS.name));\n\t\tassertEquals(expectedResult, reportSpan.tag(MotanTags.RESULT.name));\n\t\tif (result != null) {\n\t\t\tassertFalse(reportSpan.hasError());\n\t\t} else {\n\t\t\tassertTrue(reportSpan.hasError());\n\t\t\tif (errorMessage != null) {\n\t\t\t\tassertEquals(errorMessage, reportSpan.errorInfo());\n\t\t\t}\n\t\t}\n\t}\n\n\tprotected void assertProviderTrace(Object result, String errorMessage) {\n\t\tReportSpan reportSpan = MockEaseAgent.getLastSpan();\n\t\tassertNotNull(reportSpan);\n\t\tassertEquals(reportSpan.name(), MotanCtxUtils.name(request).toLowerCase());\n\t\tassertEquals(Span.Kind.SERVER.name(), reportSpan.kind());\n\t\tString expectedResult = null;\n\t\tif (MotanBaseInterceptor.MOTAN_PLUGIN_CONFIG.resultCollectEnabled() && result != null) {\n\t\t\texpectedResult = JsonUtil.toJson(result);\n\t\t}\n\t\tassertEquals(expectedResult, reportSpan.tag(MotanTags.RESULT.name));\n\t\tif (result != null) {\n\t\t\tassertFalse(reportSpan.hasError());\n\t\t} else {\n\t\t\tassertTrue(reportSpan.hasError());\n\t\t\tassertEquals(errorMessage, reportSpan.errorInfo());\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "plugins/motan/src/test/java/com/megaease/easeagent/plugin/motan/interceptor/trace/consumer/MotanConsumerInterceptorTest.java",
    "content": "package com.megaease.easeagent.plugin.motan.interceptor.trace.consumer;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.motan.interceptor.trace.MotanBaseInterceptor;\nimport com.megaease.easeagent.plugin.motan.interceptor.trace.MotanInterceptorTest;\nimport com.weibo.api.motan.rpc.DefaultResponse;\nimport com.weibo.api.motan.rpc.DefaultResponseFuture;\nimport com.weibo.api.motan.rpc.ResponseFuture;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class MotanConsumerInterceptorTest extends MotanInterceptorTest {\n\n    private static final MotanConsumerTraceInterceptor motanConsumerTraceInterceptor = new MotanConsumerTraceInterceptor();\n\n    @Override\n    protected Interceptor createInterceptor() {\n        return motanConsumerTraceInterceptor;\n    }\n\n    @Test\n    public void order() {\n        assertEquals(Order.TRACING.getOrder(), motanConsumerTraceInterceptor.order());\n    }\n\n    @Test\n    public void getType() {\n        assertEquals(ConfigConst.PluginID.TRACING, motanConsumerTraceInterceptor.getType());\n    }\n\n    @Test\n    public void testExternalConfig() {\n        assertNotNull(MotanBaseInterceptor.MOTAN_PLUGIN_CONFIG);\n        assertTrue(MotanBaseInterceptor.MOTAN_PLUGIN_CONFIG.argsCollectEnabled());\n        assertTrue(MotanBaseInterceptor.MOTAN_PLUGIN_CONFIG.resultCollectEnabled());\n    }\n\n    @Test\n    public void rpcConsumerCallSuccess() {\n        Context context = EaseAgent.getContext();\n        MethodInfo methodInfo = MethodInfo.builder()\n                .invoker(defaultRpcReferer)\n                .args(new Object[]{request})\n                .retValue(successResponse)\n                .build();\n\n        motanConsumerTraceInterceptor.before(methodInfo, context);\n        motanConsumerTraceInterceptor.after(methodInfo, context);\n        DefaultResponse defaultResponse = (DefaultResponse) methodInfo.getRetValue();\n        assertConsumerTrace(defaultResponse.getValue(), null);\n    }\n\n    @Test\n    public void rpcConsumerCallFail() {\n        Context context = EaseAgent.getContext();\n        MethodInfo methodInfo = MethodInfo.builder()\n                .invoker(defaultRpcReferer)\n                .args(new Object[]{request})\n                .retValue(failureResponse)\n                .build();\n\n        motanConsumerTraceInterceptor.before(methodInfo, context);\n        motanConsumerTraceInterceptor.after(methodInfo, context);\n        DefaultResponse defaultResponse = (DefaultResponse) methodInfo.getRetValue();\n        assertConsumerTrace(null, defaultResponse.getException().getMessage());\n    }\n\n    @Test\n    public void rpcConsumerAsyncCallFail() throws InterruptedException {\n        DefaultResponseFuture failureResponseFuture = new DefaultResponseFuture(null, 0, null);\n        Context context = EaseAgent.getContext();\n        MethodInfo methodInfo = MethodInfo.builder()\n            .invoker(defaultRpcReferer)\n            .args(new Object[]{request})\n            .retValue(failureResponseFuture)\n            .build();\n\n        motanConsumerTraceInterceptor.before(methodInfo, context);\n        motanConsumerTraceInterceptor.after(methodInfo, context);\n        ResponseFuture responseFuture = (ResponseFuture) methodInfo.getRetValue();\n        Thread thread = new Thread(() -> {\n            responseFuture.onFailure(failureResponse);\n        });\n        thread.start();\n        thread.join();\n        assertConsumerTrace(null, responseFuture.getException().getMessage());\n    }\n\n    @Test\n    public void rpcConsumerAsyncCallSuccess() throws InterruptedException {\n        DefaultResponseFuture successResponseFuture = new DefaultResponseFuture(null, 0, null);\n        MethodInfo methodInfo = MethodInfo.builder()\n            .invoker(defaultRpcReferer)\n            .args(new Object[]{request})\n            .retValue(successResponseFuture)\n            .build();\n\n        Context context = EaseAgent.getContext();\n        motanConsumerTraceInterceptor.before(methodInfo, context);\n        motanConsumerTraceInterceptor.after(methodInfo, context);\n        ResponseFuture retValue = (ResponseFuture) methodInfo.getRetValue();\n        Thread thread = new Thread(() -> {\n            retValue.onSuccess(successResponse);\n        });\n        thread.start();\n        thread.join();\n        assertConsumerTrace(retValue.getValue(), null);\n    }\n\n}\n"
  },
  {
    "path": "plugins/motan/src/test/java/com/megaease/easeagent/plugin/motan/interceptor/trace/provider/MotanProviderInterceptorTest.java",
    "content": "package com.megaease.easeagent.plugin.motan.interceptor.trace.provider;\n\nimport com.megaease.easeagent.mock.plugin.api.MockEaseAgent;\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.mock.plugin.api.utils.SpanTestUtils;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.api.context.RequestContext;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.motan.interceptor.MotanCtxUtils;\nimport com.megaease.easeagent.plugin.motan.interceptor.trace.MotanBaseInterceptor;\nimport com.megaease.easeagent.plugin.motan.interceptor.trace.MotanInterceptorTest;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport com.weibo.api.motan.rpc.Response;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class MotanProviderInterceptorTest extends MotanInterceptorTest {\n\n    private static final MotanProviderTraceInterceptor motanProviderTraceInterceptor = new MotanProviderTraceInterceptor();\n\n    @Override\n    protected Interceptor createInterceptor() {\n        return motanProviderTraceInterceptor;\n    }\n\n\n    @Test\n    public void order() {\n        assertEquals(Order.TRACING.getOrder(), motanProviderTraceInterceptor.order());\n    }\n\n    @Test\n    public void getType(){\n        assertEquals(ConfigConst.PluginID.TRACING,motanProviderTraceInterceptor.getType());\n    }\n\n    @Test\n    public void testExternalConfig() {\n        assertNotNull(MotanBaseInterceptor.MOTAN_PLUGIN_CONFIG);\n        assertTrue(MotanBaseInterceptor.MOTAN_PLUGIN_CONFIG.argsCollectEnabled());\n        assertTrue(MotanBaseInterceptor.MOTAN_PLUGIN_CONFIG.resultCollectEnabled());\n    }\n\n    @Test\n    public void rpcProviderCallSuccess() {\n        Context context = EaseAgent.getContext();\n        MethodInfo methodInfo = MethodInfo.builder()\n            .invoker(this)\n            .args(new Object[]{request, provider})\n            .retValue(successResponse)\n            .build();\n\n        motanProviderTraceInterceptor.before(methodInfo, context);\n        Span span = ((RequestContext) context.get(MotanCtxUtils.SERVER_REQUEST_CONTEXT)).span();\n        motanProviderTraceInterceptor.after(methodInfo, context);\n        ReportSpan reportSpan = MockEaseAgent.getLastSpan();\n        Response retValue = (Response) methodInfo.getRetValue();\n        assertProviderTrace(retValue.getValue(),null);\n        SpanTestUtils.sameId(span, reportSpan);\n    }\n\n    @Test\n    public void rpcProviderCallFailure() {\n        Context context = EaseAgent.getContext();\n        MethodInfo methodInfo = MethodInfo.builder()\n                .invoker(this)\n                .args(new Object[]{request, provider})\n                .retValue(failureResponse)\n                .build();\n\n        motanProviderTraceInterceptor.before(methodInfo, context);\n        Span span = ((RequestContext) context.get(MotanCtxUtils.SERVER_REQUEST_CONTEXT)).span();\n        motanProviderTraceInterceptor.after(methodInfo, context);\n        ReportSpan reportSpan = MockEaseAgent.getLastSpan();\n        assertProviderTrace(null, failureResponse.getException().getMessage());\n        SpanTestUtils.sameId(span, reportSpan);\n    }\n\n    @Test\n    public void rpcProviderCallException() {\n        Context context = EaseAgent.getContext();\n        MethodInfo methodInfo = MethodInfo.builder()\n                .invoker(this)\n                .args(new Object[]{request, provider})\n                .throwable(motanException)\n                .build();\n\n        motanProviderTraceInterceptor.before(methodInfo, context);\n        Span span = ((RequestContext) context.get(MotanCtxUtils.SERVER_REQUEST_CONTEXT)).span();\n        motanProviderTraceInterceptor.after(methodInfo, context);\n        ReportSpan reportSpan = MockEaseAgent.getLastSpan();\n        assertProviderTrace(null, methodInfo.getThrowable().getMessage());\n        SpanTestUtils.sameId(span, reportSpan);\n    }\n}\n"
  },
  {
    "path": "plugins/motan/src/test/resources/mock_agent.properties",
    "content": "#\n# -------------------- motan ---------------------\n# motan tracing\nplugin.observability.motan.tracing.enabled=true\nplugin.observability.motan.tracing.args.collect.enabled=true\nplugin.observability.motan.tracing.result.collect.enabled=true\n# motan metric\nplugin.observability.motan.metric.enabled=true\nplugin.observability.motan.metric.interval=30\nplugin.observability.motan.metric.topic=platform-metrics\nplugin.observability.motan.metric.url=/platform-metrics\n# plugin.observability.motan.metric.appendType=kafka\n"
  },
  {
    "path": "plugins/okhttp/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright (c) 2017, MegaEase\n  ~ All rights reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>plugins</artifactId>\n        <groupId>com.megaease.easeagent</groupId>\n        <version>2.3.0</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>okhttp</artifactId>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>plugin-api</artifactId>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.squareup.okhttp3</groupId>\n            <artifactId>okhttp</artifactId>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>plugin-api-mock</artifactId>\n            <version>${project.version}</version>\n            <scope>test</scope>\n        </dependency>\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "plugins/okhttp/src/main/java/com/megaease/easeagent/plugin/okhttp/ForwardedPlugin.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.okhttp;\n\nimport com.megaease.easeagent.plugin.AgentPlugin;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\n\npublic class ForwardedPlugin implements AgentPlugin {\n    @Override\n    public String getNamespace() {\n        return ConfigConst.Namespace.FORWARDED;\n    }\n\n    @Override\n    public String getDomain() {\n        return ConfigConst.INTEGRABILITY;\n    }\n}\n"
  },
  {
    "path": "plugins/okhttp/src/main/java/com/megaease/easeagent/plugin/okhttp/OkHttpPlugin.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.okhttp;\n\nimport com.megaease.easeagent.plugin.AgentPlugin;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\n\npublic class OkHttpPlugin implements AgentPlugin {\n    @Override\n    public String getNamespace() {\n        return ConfigConst.Namespace.OK_HTTP;\n    }\n\n    @Override\n    public String getDomain() {\n        return ConfigConst.OBSERVABILITY;\n    }\n}\n"
  },
  {
    "path": "plugins/okhttp/src/main/java/com/megaease/easeagent/plugin/okhttp/advice/OkHttpAdvice.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.okhttp.advice;\n\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.Set;\n\npublic class OkHttpAdvice implements Points {\n\n    //        return def.type(hasSuperType(named(\"okhttp3.Call\"))\n    //                        .or(named(\"okhttp3.internal.connection.RealCall\")))\n    //                .transform(execute(named(\"execute\")))\n    //                .transform(asyncExecute(named(\"enqueue\")))\n    //                .end();\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return ClassMatcher.builder().hasSuperClass(\"okhttp3.Call\").build()\n            .or(ClassMatcher.builder().hasClassName(\"okhttp3.internal.connection.RealCall\").build());\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.multiBuilder()\n            .match(MethodMatcher.builder().named(\"execute\").qualifier(\"execute\").build())\n            .match(MethodMatcher.builder().named(\"enqueue\").qualifier(\"enqueue\").build())\n            .build();\n    }\n}\n"
  },
  {
    "path": "plugins/okhttp/src/main/java/com/megaease/easeagent/plugin/okhttp/interceptor/ForwardedRequest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.okhttp.interceptor;\n\nimport com.megaease.easeagent.plugin.api.trace.Setter;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\nimport okhttp3.Request;\n\npublic class ForwardedRequest implements Setter {\n    private final Object realCall;\n    private Request.Builder requestBuilder;\n\n    public ForwardedRequest(Object realCall) {\n        this.realCall = realCall;\n    }\n\n    private Request.Builder builder() {\n        if (requestBuilder == null) {\n            Request originalRequest = AgentFieldReflectAccessor.getFieldValue(realCall, \"originalRequest\");\n            if (originalRequest == null) {\n                return null;\n            }\n            requestBuilder = originalRequest.newBuilder();\n        }\n        return requestBuilder;\n    }\n\n    public Request.Builder getRequestBuilder() {\n        return requestBuilder;\n    }\n\n    @Override\n    public void setHeader(String name, String value) {\n        Request.Builder builder = builder();\n        if (builder == null) {\n            return;\n        }\n        builder.addHeader(name, value);\n    }\n}\n"
  },
  {
    "path": "plugins/okhttp/src/main/java/com/megaease/easeagent/plugin/okhttp/interceptor/InternalRequest.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.okhttp.interceptor;\n\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.tools.trace.HttpRequest;\nimport okhttp3.Request;\n\npublic class InternalRequest implements HttpRequest {\n\n    private final Request originalRequest;\n    private final Request.Builder requestBuilder;\n\n    public InternalRequest(Request originalRequest, Request.Builder requestBuilder) {\n        this.originalRequest = originalRequest;\n        this.requestBuilder = requestBuilder;\n    }\n\n    @Override\n    public String method() {\n        return originalRequest.method();\n    }\n\n    @Override\n    public String path() {\n        return originalRequest.url().uri().toString();\n    }\n\n    @Override\n    public String route() {\n        return null;\n    }\n\n    @Override\n    public String getRemoteAddr() {\n        return null;\n    }\n\n    @Override\n    public int getRemotePort() {\n        return 0;\n    }\n\n    @Override\n    public Span.Kind kind() {\n        return Span.Kind.CLIENT;\n    }\n\n    @Override\n    public String header(String name) {\n        return originalRequest.header(name);\n    }\n\n    @Override\n    public boolean cacheScope() {\n        return false;\n    }\n\n    @Override\n    public void setHeader(String name, String value) {\n        requestBuilder.addHeader(name, value);\n    }\n}\n"
  },
  {
    "path": "plugins/okhttp/src/main/java/com/megaease/easeagent/plugin/okhttp/interceptor/InternalResponse.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.okhttp.interceptor;\n\nimport com.megaease.easeagent.plugin.tools.trace.HttpResponse;\nimport okhttp3.Response;\n\npublic class InternalResponse implements HttpResponse {\n    private final Throwable caught;\n    private final String method;\n    private final Response response;\n\n    public InternalResponse(Throwable caught, String method, Response response) {\n        this.caught = caught;\n        this.method = method;\n        this.response = response;\n    }\n\n    @Override\n    public String method() {\n        return method;\n    }\n\n    @Override\n    public String route() {\n        return null;\n    }\n\n    @Override\n    public int statusCode() {\n        return this.response.code();\n    }\n\n    @Override\n    public Throwable maybeError() {\n        return caught;\n    }\n\n    @Override\n    public String header(String name) {\n        return response.header(name);\n    }\n}\n"
  },
  {
    "path": "plugins/okhttp/src/main/java/com/megaease/easeagent/plugin/okhttp/interceptor/OkHttpAsyncTracingInterceptor.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.okhttp.interceptor;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.RequestContext;\nimport com.megaease.easeagent.plugin.api.trace.Scope;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.interceptor.NonReentrantInterceptor;\nimport com.megaease.easeagent.plugin.okhttp.OkHttpPlugin;\nimport com.megaease.easeagent.plugin.okhttp.advice.OkHttpAdvice;\nimport com.megaease.easeagent.plugin.tools.trace.HttpUtils;\nimport okhttp3.Call;\nimport okhttp3.Callback;\nimport okhttp3.Request;\nimport okhttp3.Response;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.io.IOException;\n\n@AdviceTo(value = OkHttpAdvice.class, qualifier = \"enqueue\", plugin = OkHttpPlugin.class)\npublic class OkHttpAsyncTracingInterceptor implements NonReentrantInterceptor {\n    @Override\n    public void doBefore(MethodInfo methodInfo, Context context) {\n        Object realCall = methodInfo.getInvoker();\n        Request originalRequest = AgentFieldReflectAccessor.getFieldValue(realCall, \"originalRequest\");\n        if (originalRequest == null) {\n            return;\n        }\n        Request.Builder requestBuilder = originalRequest.newBuilder();\n        InternalRequest request = new InternalRequest(originalRequest, requestBuilder);\n        RequestContext requestContext = context.clientRequest(request);\n        HttpUtils.handleReceive(requestContext.span().start(), request);\n        context.put(OkHttpAsyncTracingInterceptor.class, requestContext);\n        Callback callback = (Callback) methodInfo.getArgs()[0];\n        InternalCallback internalCallback = new InternalCallback(callback, request.method(), requestContext);\n        methodInfo.changeArg(0, internalCallback);\n        AgentFieldReflectAccessor.setFieldValue(realCall, \"originalRequest\", requestBuilder.build());\n    }\n\n    @Override\n    public void doAfter(MethodInfo methodInfo, Context context) {\n        RequestContext requestContext = context.remove(OkHttpAsyncTracingInterceptor.class);\n        if (requestContext == null) {\n            return;\n        }\n        try (Scope ignored = requestContext.scope()) {\n            if (methodInfo.isSuccess()) {\n                return;\n            }\n            requestContext.span().error(methodInfo.getThrowable()).finish();\n        }\n    }\n\n    @Override\n    public int order() {\n        return Order.TRACING.getOrder();\n    }\n\n    public static class InternalCallback implements Callback {\n        private final Callback delegate;\n        private final String method;\n        private final RequestContext requestContext;\n\n        public InternalCallback(Callback delegate, String method, RequestContext requestContext) {\n            this.delegate = delegate;\n            this.method = method;\n            this.requestContext = requestContext;\n        }\n\n        @Override\n        public void onFailure(@NotNull Call call, @NotNull IOException e) {\n            this.delegate.onFailure(call, e);\n            if (this.requestContext != null) {\n                this.requestContext.span().abandon();\n            }\n        }\n\n        @Override\n        public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {\n            this.delegate.onResponse(call, response);\n            if (this.requestContext != null) {\n                InternalResponse internalResponse = new InternalResponse(null, method, response);\n                HttpUtils.save(requestContext.span(), internalResponse);\n                requestContext.finish(internalResponse);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/okhttp/src/main/java/com/megaease/easeagent/plugin/okhttp/interceptor/OkHttpForwardedInterceptor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.okhttp.interceptor;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.okhttp.ForwardedPlugin;\nimport com.megaease.easeagent.plugin.okhttp.advice.OkHttpAdvice;\nimport okhttp3.Request;\n\n@AdviceTo(value = OkHttpAdvice.class, qualifier = \"enqueue\", plugin = ForwardedPlugin.class)\n@AdviceTo(value = OkHttpAdvice.class, qualifier = \"execute\", plugin = ForwardedPlugin.class)\npublic class OkHttpForwardedInterceptor implements Interceptor {\n\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n        ForwardedRequest forwardedRequest = new ForwardedRequest(methodInfo.getInvoker());\n        context.injectForwardedHeaders(forwardedRequest);\n        Request.Builder requestBuilder = forwardedRequest.getRequestBuilder();\n        if (requestBuilder != null) {\n            AgentFieldReflectAccessor.setFieldValue(methodInfo.getInvoker(), \"originalRequest\", requestBuilder.build());\n        }\n    }\n\n    @Override\n    public String getType() {\n        return ConfigConst.PluginID.FORWARDED;\n    }\n\n    @Override\n    public int order() {\n        return Order.FORWARDED.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/okhttp/src/main/java/com/megaease/easeagent/plugin/okhttp/interceptor/OkHttpTracingInterceptor.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.okhttp.interceptor;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.okhttp.OkHttpPlugin;\nimport com.megaease.easeagent.plugin.okhttp.advice.OkHttpAdvice;\nimport com.megaease.easeagent.plugin.tools.trace.BaseHttpClientTracingInterceptor;\nimport com.megaease.easeagent.plugin.tools.trace.HttpRequest;\nimport com.megaease.easeagent.plugin.tools.trace.HttpResponse;\nimport okhttp3.Request;\nimport okhttp3.Response;\n\n@AdviceTo(value = OkHttpAdvice.class, qualifier = \"execute\", plugin = OkHttpPlugin.class)\npublic class OkHttpTracingInterceptor extends BaseHttpClientTracingInterceptor {\n    public static Object REQUEST_BUILDER_KEY = new Object();\n    public static Object METHOD_KEY = new Object();\n\n    @Override\n    public Object getProgressKey() {\n        return OkHttpTracingInterceptor.class;\n    }\n\n    @Override\n    public void doBefore(MethodInfo methodInfo, Context context) {\n        super.doBefore(methodInfo, context);\n        Object realCall = methodInfo.getInvoker();\n        Request.Builder requestBuilder = context.remove(REQUEST_BUILDER_KEY);\n        AgentFieldReflectAccessor.setFieldValue(realCall, \"originalRequest\", requestBuilder.build());\n    }\n\n    @Override\n    protected HttpRequest getRequest(MethodInfo methodInfo, Context context) {\n        Object realCall = methodInfo.getInvoker();\n        Request originalRequest = AgentFieldReflectAccessor.getFieldValue(realCall, \"originalRequest\");\n        if (originalRequest == null) {\n            return null;\n        }\n        context.put(METHOD_KEY, originalRequest.method());\n        Request.Builder requestBuilder = originalRequest.newBuilder();\n        context.put(REQUEST_BUILDER_KEY, requestBuilder);\n        return new InternalRequest(originalRequest, requestBuilder);\n    }\n\n    @Override\n    protected HttpResponse getResponse(MethodInfo methodInfo, Context context) {\n        return new InternalResponse(methodInfo.getThrowable(), context.remove(METHOD_KEY), (Response) methodInfo.getRetValue());\n    }\n}\n"
  },
  {
    "path": "plugins/okhttp/src/test/java/com/megaease/easeagent/plugin/okhttp/interceptor/OkHttpAsyncTracingInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.okhttp.interceptor;\n\nimport com.megaease.easeagent.mock.plugin.api.MockEaseAgent;\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.trace.Scope;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport okhttp3.Call;\nimport okhttp3.Callback;\nimport okhttp3.Response;\nimport org.jetbrains.annotations.NotNull;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport java.io.IOException;\nimport java.util.function.BiConsumer;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class OkHttpAsyncTracingInterceptorTest {\n\n    @Test\n    public void doBefore() throws InterruptedException {\n\n        ReportSpan mockSpan = runOne((call, callback) -> {\n            Response response = OkHttpTestUtils.responseBuilder(call)\n                .addHeader(TestConst.RESPONSE_TAG_NAME, TestConst.RESPONSE_TAG_VALUE)\n                .build();\n            try {\n                callback.onResponse(call, response);\n            } catch (IOException e) {\n                throw new RuntimeException(\"onResponse fail.\", e);\n            }\n        });\n        assertNotNull(mockSpan);\n        assertEquals(Span.Kind.CLIENT.name(), mockSpan.kind());\n        assertEquals(TestConst.RESPONSE_TAG_VALUE, mockSpan.tag(TestConst.RESPONSE_TAG_NAME));\n        assertNull(mockSpan.parentId());\n\n        Context context = EaseAgent.getContext();\n        Span span = context.nextSpan();\n        try (Scope ignored = span.maybeScope()) {\n            mockSpan = runOne((call, callback) -> {\n                Response response = OkHttpTestUtils.responseBuilder(call)\n                    .addHeader(TestConst.RESPONSE_TAG_NAME, TestConst.RESPONSE_TAG_VALUE)\n                    .build();\n                try {\n                    callback.onResponse(call, response);\n                } catch (IOException e) {\n                    throw new RuntimeException(\"onResponse fail.\", e);\n                }\n            });\n            assertNotNull(mockSpan);\n            assertEquals(span.traceIdString(), mockSpan.traceId());\n            assertEquals(span.spanIdString(), mockSpan.parentId());\n            assertNotNull(mockSpan.id());\n        }\n        span.abandon();\n\n        mockSpan = runOne((call, callback) -> {\n            callback.onFailure(call, new IOException(\"test error\"));\n        });\n        assertNull(mockSpan);\n    }\n\n    private static ReportSpan runOne(final BiConsumer<Call, Callback> consumer) throws InterruptedException {\n        Call call = OkHttpTestUtils.buildCall();\n        MethodInfo.MethodInfoBuilder methodInfoBuilder = MethodInfo.builder().invoker(call).args(new Object[]{\n            new Callback() {\n                @Override\n                public void onFailure(@NotNull Call call, @NotNull IOException e) {\n\n                }\n\n                @Override\n                public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {\n\n                }\n            }\n        });\n        MethodInfo methodInfo = methodInfoBuilder.build();\n        Context context = EaseAgent.getContext();\n        OkHttpAsyncTracingInterceptor okHttpAsyncTracingInterceptor = new OkHttpAsyncTracingInterceptor();\n        MockEaseAgent.cleanLastSpan();\n\n        okHttpAsyncTracingInterceptor.doBefore(methodInfo, context);\n        okHttpAsyncTracingInterceptor.doAfter(methodInfo, context);\n\n        Callback callback = (Callback) methodInfo.getArgs()[0];\n        assertNull(MockEaseAgent.getLastSpan());\n\n        Thread thread = new Thread(() -> consumer.accept(call, callback));\n        thread.start();\n        thread.join();\n        return MockEaseAgent.getLastSpan();\n    }\n\n    @Test\n    public void doAfter() throws InterruptedException {\n        doBefore();\n    }\n}\n"
  },
  {
    "path": "plugins/okhttp/src/test/java/com/megaease/easeagent/plugin/okhttp/interceptor/OkHttpForwardedInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.okhttp.interceptor;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport okhttp3.Call;\nimport okhttp3.OkHttpClient;\nimport okhttp3.Request;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotNull;\nimport static org.junit.Assert.assertNull;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class OkHttpForwardedInterceptorTest {\n\n    @Test\n    public void before() {\n        OkHttpForwardedInterceptor okHttpForwardedInterceptor = new OkHttpForwardedInterceptor();\n        Request request = new Request.Builder()\n            .url(\"http://127.0.0.1:8080/test\")\n            .build();\n        OkHttpClient client = new OkHttpClient();\n        Call call = client.newCall(request);\n        MethodInfo methodInfo = MethodInfo.builder().invoker(call).build();\n        Context context = EaseAgent.getContext();\n        okHttpForwardedInterceptor.before(methodInfo, context);\n        assertNull(call.request().header(TestConst.FORWARDED_NAME));\n        context.put(TestConst.FORWARDED_NAME, TestConst.FORWARDED_VALUE);\n        try {\n            okHttpForwardedInterceptor.before(methodInfo, context);\n            assertNotNull(call.request().header(TestConst.FORWARDED_NAME));\n            assertEquals(TestConst.FORWARDED_VALUE, call.request().header(TestConst.FORWARDED_NAME));\n        } finally {\n            context.remove(TestConst.FORWARDED_NAME);\n        }\n    }\n\n    @Test\n    public void getType() {\n        OkHttpForwardedInterceptor okHttpForwardedInterceptor = new OkHttpForwardedInterceptor();\n        assertEquals(ConfigConst.PluginID.FORWARDED, okHttpForwardedInterceptor.getType());\n    }\n}\n"
  },
  {
    "path": "plugins/okhttp/src/test/java/com/megaease/easeagent/plugin/okhttp/interceptor/OkHttpTestUtils.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.okhttp.interceptor;\n\nimport okhttp3.*;\n\npublic class OkHttpTestUtils {\n    public static final String URL = \"http://127.0.0.1:8080/test\";\n\n    public static Call buildCall() {\n        Request okRequest = new Request.Builder()\n            .url(URL)\n            .build();\n        OkHttpClient client = new OkHttpClient();\n        return client.newCall(okRequest);\n    }\n\n    public static Response.Builder responseBuilder(Call call) {\n        Response.Builder builder = new Response.Builder();\n        builder.code(200);\n        builder.request(call.request());\n        builder.protocol(Protocol.HTTP_2);\n        builder.addHeader(\"aa\", \"bb\");\n        builder.message(\"test\");\n        return builder;\n    }\n}\n"
  },
  {
    "path": "plugins/okhttp/src/test/java/com/megaease/easeagent/plugin/okhttp/interceptor/OkHttpTracingInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.okhttp.interceptor;\n\nimport com.megaease.easeagent.mock.plugin.api.MockEaseAgent;\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.trace.Scope;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport com.megaease.easeagent.plugin.tools.trace.HttpRequest;\nimport com.megaease.easeagent.plugin.tools.trace.HttpResponse;\nimport okhttp3.Call;\nimport okhttp3.Response;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class OkHttpTracingInterceptorTest {\n\n    @Test\n    public void doBefore() {\n        Call call = OkHttpTestUtils.buildCall();\n        MethodInfo.MethodInfoBuilder methodInfoBuilder = MethodInfo.builder().invoker(call);\n        MethodInfo methodInfo = methodInfoBuilder.build();\n        Context context = EaseAgent.getContext();\n        OkHttpTracingInterceptor okHttpTracingInterceptor = new OkHttpTracingInterceptor();\n        MockEaseAgent.cleanLastSpan();\n\n        okHttpTracingInterceptor.before(methodInfo, context);\n        methodInfo = methodInfoBuilder.retValue(OkHttpTestUtils.responseBuilder(call)\n            .addHeader(TestConst.RESPONSE_TAG_NAME, TestConst.RESPONSE_TAG_VALUE)\n            .build()).build();\n        okHttpTracingInterceptor.after(methodInfo, context);\n\n        ReportSpan mockSpan = MockEaseAgent.getLastSpan();\n        assertNotNull(mockSpan);\n        assertEquals(Span.Kind.CLIENT.name(), mockSpan.kind());\n        assertEquals(TestConst.RESPONSE_TAG_VALUE, mockSpan.tag(TestConst.RESPONSE_TAG_NAME));\n        assertNull(mockSpan.parentId());\n\n        call = OkHttpTestUtils.buildCall();\n        methodInfo = methodInfoBuilder.invoker(call).retValue(null).build();\n        okHttpTracingInterceptor.before(methodInfo, context);\n        methodInfo.retValue(OkHttpTestUtils.responseBuilder(call)\n            .addHeader(TestConst.RESPONSE_TAG_NAME, TestConst.RESPONSE_TAG_VALUE)\n            .build());\n        RuntimeException runtimeException = new RuntimeException(\"test error\");\n        methodInfo.throwable(runtimeException);\n        okHttpTracingInterceptor.after(methodInfo, context);\n\n        mockSpan = MockEaseAgent.getLastSpan();\n        assertNotNull(mockSpan);\n        assertEquals(Span.Kind.CLIENT.name(), mockSpan.kind());\n        assertEquals(TestConst.RESPONSE_TAG_VALUE, mockSpan.tag(TestConst.RESPONSE_TAG_NAME));\n        assertNull(mockSpan.parentId());\n\n\n        call = OkHttpTestUtils.buildCall();\n        methodInfo = methodInfoBuilder.invoker(call).retValue(null).build();\n        Span span = context.nextSpan();\n        try (Scope ignored = span.maybeScope()) {\n            okHttpTracingInterceptor.before(methodInfo, context);\n            methodInfo.retValue(OkHttpTestUtils.responseBuilder(call)\n                .addHeader(TestConst.RESPONSE_TAG_NAME, TestConst.RESPONSE_TAG_VALUE)\n                .build());\n            okHttpTracingInterceptor.after(methodInfo, context);\n            mockSpan = MockEaseAgent.getLastSpan();\n            assertEquals(span.traceIdString(), mockSpan.traceId());\n            assertEquals(span.spanIdString(), mockSpan.parentId());\n            assertNotNull(mockSpan.id());\n        }\n        span.abandon();\n    }\n\n\n    @Test\n    public void getRequest() {\n        Call call = OkHttpTestUtils.buildCall();\n\n        MethodInfo methodInfo = MethodInfo.builder().invoker(call).build();\n        Context context = EaseAgent.getContext();\n        OkHttpTracingInterceptor okHttpTracingInterceptor = new OkHttpTracingInterceptor();\n        HttpRequest request = okHttpTracingInterceptor.getRequest(methodInfo, context);\n        assertEquals(com.megaease.easeagent.plugin.api.trace.Span.Kind.CLIENT, request.kind());\n        assertEquals(\"GET\", request.method());\n        assertEquals(OkHttpTestUtils.URL, request.path());\n    }\n\n    @Test\n    public void getResponse() {\n        Call call = OkHttpTestUtils.buildCall();\n        Response.Builder builder = OkHttpTestUtils.responseBuilder(call);\n        Response response = builder.build();\n        MethodInfo.MethodInfoBuilder methodInfoBuilder = MethodInfo.builder().invoker(call).retValue(response);\n        MethodInfo methodInfo = methodInfoBuilder.build();\n\n        OkHttpTracingInterceptor okHttpTracingInterceptor = new OkHttpTracingInterceptor();\n        EaseAgent.getContext().put(OkHttpTracingInterceptor.METHOD_KEY, call.request().method());\n\n        HttpResponse httpResponse = okHttpTracingInterceptor.getResponse(methodInfo, EaseAgent.getContext());\n        assertEquals(\"GET\", httpResponse.method());\n        assertNull(httpResponse.route());\n        assertNull(httpResponse.maybeError());\n        assertEquals(200, httpResponse.statusCode());\n\n        RuntimeException runtimeException = new RuntimeException(\"test error\");\n        methodInfoBuilder.throwable(runtimeException);\n        httpResponse = okHttpTracingInterceptor.getResponse(methodInfoBuilder.build(), EaseAgent.getContext());\n        assertEquals(runtimeException, httpResponse.maybeError());\n    }\n}\n"
  },
  {
    "path": "plugins/okhttp/src/test/java/com/megaease/easeagent/plugin/okhttp/interceptor/TestConst.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.okhttp.interceptor;\n\npublic class TestConst {\n    public static final String FORWARDED_NAME = \"X-Forwarded-For\";\n    public static final String FORWARDED_VALUE = \"testForwarded\";\n    public static final String RESPONSE_TAG_NAME = \"X-EG-Test\";\n    public static final String RESPONSE_TAG_VALUE = \"X-EG-Test-Value\";\n}\n"
  },
  {
    "path": "plugins/okhttp/src/test/resources/mock_agent.properties",
    "content": "easeagent.progress.forwarded.headers=X-Forwarded-For\nobservability.tracings.tag.response.headers.eg.0=X-EG-Test\n"
  },
  {
    "path": "plugins/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright (c) 2021, MegaEase\n  ~ All rights reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~   http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <parent>\n        <artifactId>easeagent</artifactId>\n        <groupId>com.megaease.easeagent</groupId>\n        <version>2.3.0</version>\n    </parent>\n\n    <artifactId>plugins</artifactId>\n    <version>2.3.0</version>\n    <packaging>pom</packaging>\n    <name>easeagent-plugins</name>\n\n    <modules>\n        <module>async</module>\n        <module>jdbc</module>\n        <module>rabbitmq</module>\n        <module>springweb</module>\n        <module>spring-gateway</module>\n        <module>httpservlet</module>\n        <module>redis</module>\n        <module>kafka</module>\n        <module>servicename</module>\n        <module>httpclient</module>\n        <module>okhttp</module>\n        <module>elasticsearch</module>\n        <module>healthy</module>\n        <module>mongodb</module>\n        <module>logback</module>\n        <module>log4j2-log-plugin</module>\n        <module>dubbo</module>\n        <module>motan</module>\n        <module>sofarpc</module>\n        <module>httpurlconnection</module>\n    </modules>\n\n    <dependencyManagement>\n        <dependencies>\n            <dependency>\n                <groupId>com.megaease.easeagent</groupId>\n                <artifactId>plugin-api</artifactId>\n                <version>${project.version}</version>\n            </dependency>\n        </dependencies>\n    </dependencyManagement>\n    <!--\n    -->\n\n    <build>\n        <pluginManagement>\n            <plugins>\n                <plugin>\n                    <groupId>org.apache.maven.plugins</groupId>\n                    <artifactId>maven-compiler-plugin</artifactId>\n                    <version>3.6.1</version>\n                    <configuration>\n                        <source>${version.java}</source>\n                        <target>${version.java}</target>\n                    </configuration>\n                </plugin>\n\n                <!--\n                <plugin>\n                    <artifactId>maven-assembly-plugin</artifactId>\n                    <version>3.0.0</version>\n                </plugin>\n                -->\n\n                <plugin>\n                    <groupId>org.apache.maven.plugins</groupId>\n                    <artifactId>maven-jar-plugin</artifactId>\n                    <version>3.2.0</version>\n                    <configuration>\n                        <archive>\n                            <manifest>\n                                <addDefaultImplementationEntries>true</addDefaultImplementationEntries>\n                                <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>\n                            </manifest>\n                        </archive>\n                    </configuration>\n                </plugin>\n            </plugins>\n        </pluginManagement>\n    </build>\n\n    <profiles>\n        <profile>\n            <id>include-jdk17-module</id>\n            <activation>\n                <jdk>[17,)</jdk>\n            </activation>\n            <modules>\n                <module>tomcat-jdk17</module>\n                <module>httpurlconnection-jdk17</module>\n                <module>spring-boot-3.5.3</module>\n            </modules>\n        </profile>\n    </profiles>\n</project>\n\n"
  },
  {
    "path": "plugins/rabbitmq/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright (c) 2021, MegaEase\n  ~ All rights reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~   http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <parent>\n        <groupId>com.megaease.easeagent</groupId>\n        <artifactId>plugins</artifactId>\n        <version>2.3.0</version>\n    </parent>\n\n    <!--\n    <groupId>com.megaease.easeagent.plugin</groupId>\n    -->\n    <artifactId>rabbitMq</artifactId>\n\n    <packaging>jar</packaging>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>plugin-api</artifactId>\n            <scope>provided</scope>\n        </dependency>\n\n        <dependency>\n            <groupId>org.springframework.amqp</groupId>\n            <artifactId>spring-rabbit</artifactId>\n            <version>2.2.5.RELEASE</version>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.rabbitmq</groupId>\n            <artifactId>amqp-client</artifactId>\n            <version>5.11.0</version>\n            <scope>provided</scope>\n        </dependency>\n\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>plugin-api-mock</artifactId>\n            <version>${project.version}</version>\n            <scope>test</scope>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-compiler-plugin</artifactId>\n            </plugin>\n\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-resources-plugin</artifactId>\n                <version>3.0.1</version>\n            </plugin>\n\n            <!--\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-jar-plugin</artifactId>\n                <configuration>\n                    <archive>\n                        <manifest>\n                            <addDefaultImplementationEntries>true</addDefaultImplementationEntries>\n                            <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>\n                        </manifest>\n                    </archive>\n                </configuration>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-release-plugin</artifactId>\n                <version>2.5.3</version>\n            </plugin>\n            -->\n        </plugins>\n    </build>\n</project>\n"
  },
  {
    "path": "plugins/rabbitmq/src/main/java/com/megaease/easeagent/plugin/rabbitmq/RabbitMqConsumerMetric.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.rabbitmq;\n\nimport com.megaease.easeagent.plugin.api.metric.*;\nimport com.megaease.easeagent.plugin.api.metric.name.*;\nimport com.megaease.easeagent.plugin.api.middleware.Redirect;\nimport com.megaease.easeagent.plugin.api.middleware.RedirectProcessor;\nimport com.megaease.easeagent.plugin.utils.ImmutableMap;\n\nimport javax.annotation.Nonnull;\nimport java.util.concurrent.TimeUnit;\n\npublic class RabbitMqConsumerMetric extends ServiceMetric {\n    public static final ServiceMetricSupplier<RabbitMqConsumerMetric> SERVICE_METRIC_SUPPLIER = new ServiceMetricSupplier<RabbitMqConsumerMetric>() {\n        @Override\n        public NameFactory newNameFactory() {\n            return buildNameFactory();\n        }\n\n        @Override\n        public RabbitMqConsumerMetric newInstance(MetricRegistry metricRegistry, NameFactory nameFactory) {\n            return new RabbitMqConsumerMetric(metricRegistry, nameFactory);\n        }\n    };\n\n\n    public RabbitMqConsumerMetric(@Nonnull MetricRegistry metricRegistry, @Nonnull NameFactory nameFactory) {\n        super(metricRegistry, nameFactory);\n    }\n\n    public static Tags buildOnMessageTags() {\n        Tags tags = new Tags(\"application\", \"rabbitmq-queue\", \"resource\");\n        RedirectProcessor.setTagsIfRedirected(Redirect.RABBITMQ, tags);\n        return tags;\n    }\n\n    public static Tags buildConsumerTags() {\n        Tags tags = new Tags(\"application\", \"rabbitmq-consumer\", \"resource\");\n        RedirectProcessor.setTagsIfRedirected(Redirect.RABBITMQ, tags);\n        return tags;\n    }\n\n    public static NameFactory buildNameFactory() {\n        return NameFactory.createBuilder()\n            .timerType(MetricSubType.DEFAULT,\n                ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                    .put(MetricField.MIN_EXECUTION_TIME, MetricValueFetcher.SnapshotMinValue)\n                    .put(MetricField.MAX_EXECUTION_TIME, MetricValueFetcher.SnapshotMaxValue)\n                    .put(MetricField.MEAN_EXECUTION_TIME, MetricValueFetcher.SnapshotMeanValue)\n                    .put(MetricField.P25_EXECUTION_TIME, MetricValueFetcher.Snapshot25Percentile)\n                    .put(MetricField.P50_EXECUTION_TIME, MetricValueFetcher.Snapshot50PercentileValue)\n                    .put(MetricField.P75_EXECUTION_TIME, MetricValueFetcher.Snapshot75PercentileValue)\n                    .put(MetricField.P95_EXECUTION_TIME, MetricValueFetcher.Snapshot95PercentileValue)\n                    .put(MetricField.P98_EXECUTION_TIME, MetricValueFetcher.Snapshot98PercentileValue)\n                    .put(MetricField.P99_EXECUTION_TIME, MetricValueFetcher.Snapshot99PercentileValue)\n                    .put(MetricField.P999_EXECUTION_TIME, MetricValueFetcher.Snapshot999PercentileValue)\n                    .build())\n            .meterType(MetricSubType.CONSUMER, ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                .put(MetricField.QUEUE_M1_RATE, MetricValueFetcher.MeteredM1Rate)\n                .put(MetricField.QUEUE_M5_RATE, MetricValueFetcher.MeteredM5Rate)\n                .put(MetricField.QUEUE_M15_RATE, MetricValueFetcher.MeteredM15Rate)\n                .build())\n            .meterType(MetricSubType.CONSUMER_ERROR, ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                .put(MetricField.QUEUE_M1_ERROR_RATE, MetricValueFetcher.MeteredM1Rate)\n                .put(MetricField.QUEUE_M5_ERROR_RATE, MetricValueFetcher.MeteredM5Rate)\n                .put(MetricField.QUEUE_M15_ERROR_RATE, MetricValueFetcher.MeteredM15Rate)\n                .build())\n            .build();\n    }\n\n    public void metricAfter(String queue, long beginTime, boolean success) {\n        Timer timer = timer(queue, MetricSubType.DEFAULT);\n        timer.update(System.currentTimeMillis() - beginTime, TimeUnit.MILLISECONDS);\n        final Meter defaultMeter = meter(queue, MetricSubType.CONSUMER);\n        final Meter errorMeter = meter(queue, MetricSubType.CONSUMER_ERROR);\n        if (!success) {\n            errorMeter.mark();\n        }\n        defaultMeter.mark();\n    }\n}\n"
  },
  {
    "path": "plugins/rabbitmq/src/main/java/com/megaease/easeagent/plugin/rabbitmq/RabbitMqPlugin.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.rabbitmq;\n\nimport com.megaease.easeagent.plugin.AgentPlugin;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\n\npublic class RabbitMqPlugin implements AgentPlugin {\n    @Override\n    public String getNamespace() {\n        return ConfigConst.Namespace.RABBITMQ;\n    }\n\n    @Override\n    public String getDomain() {\n        return ConfigConst.OBSERVABILITY;\n    }\n}\n"
  },
  {
    "path": "plugins/rabbitmq/src/main/java/com/megaease/easeagent/plugin/rabbitmq/RabbitMqProducerMetric.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.rabbitmq;\n\nimport com.megaease.easeagent.plugin.api.metric.*;\nimport com.megaease.easeagent.plugin.api.metric.name.*;\nimport com.megaease.easeagent.plugin.api.middleware.Redirect;\nimport com.megaease.easeagent.plugin.api.middleware.RedirectProcessor;\nimport com.megaease.easeagent.plugin.utils.ImmutableMap;\n\nimport javax.annotation.Nonnull;\nimport java.util.concurrent.TimeUnit;\n\npublic class RabbitMqProducerMetric extends ServiceMetric {\n    public static final ServiceMetricSupplier<RabbitMqProducerMetric> SERVICE_METRIC_SUPPLIER = new ServiceMetricSupplier<RabbitMqProducerMetric>() {\n        @Override\n        public NameFactory newNameFactory() {\n            return buildNameFactory();\n        }\n\n        @Override\n        public RabbitMqProducerMetric newInstance(MetricRegistry metricRegistry, NameFactory nameFactory) {\n            return new RabbitMqProducerMetric(metricRegistry, nameFactory);\n        }\n    };\n\n    public RabbitMqProducerMetric(@Nonnull MetricRegistry metricRegistry, @Nonnull NameFactory nameFactory) {\n        super(metricRegistry, nameFactory);\n    }\n\n    public void metricAfter(String exchange, String routingKey, long beginTime, boolean success) {\n        String key = String.join(\"-\", exchange, routingKey);\n        Timer timer = timer(key, MetricSubType.DEFAULT);\n        timer.update(System.currentTimeMillis() - beginTime, TimeUnit.MILLISECONDS);\n        final Meter defaultMeter = meter(key, MetricSubType.PRODUCER);\n        final Meter errorMeter = meter(key, MetricSubType.PRODUCER_ERROR);\n        if (!success) {\n            errorMeter.mark();\n        }\n        defaultMeter.mark();\n    }\n\n    public static Tags buildTags() {\n        Tags tags = new Tags(\"application\", \"rabbitmq-ex-ro\", \"resource\");\n        RedirectProcessor.setTagsIfRedirected(Redirect.RABBITMQ, tags);\n        return tags;\n    }\n\n    public static NameFactory buildNameFactory() {\n        return NameFactory.createBuilder()\n            .timerType(MetricSubType.DEFAULT,\n                ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                    .put(MetricField.MIN_EXECUTION_TIME, MetricValueFetcher.SnapshotMinValue)\n                    .put(MetricField.MAX_EXECUTION_TIME, MetricValueFetcher.SnapshotMaxValue)\n                    .put(MetricField.MEAN_EXECUTION_TIME, MetricValueFetcher.SnapshotMeanValue)\n                    .put(MetricField.P25_EXECUTION_TIME, MetricValueFetcher.Snapshot25Percentile)\n                    .put(MetricField.P50_EXECUTION_TIME, MetricValueFetcher.Snapshot50PercentileValue)\n                    .put(MetricField.P75_EXECUTION_TIME, MetricValueFetcher.Snapshot75PercentileValue)\n                    .put(MetricField.P95_EXECUTION_TIME, MetricValueFetcher.Snapshot95PercentileValue)\n                    .put(MetricField.P98_EXECUTION_TIME, MetricValueFetcher.Snapshot98PercentileValue)\n                    .put(MetricField.P99_EXECUTION_TIME, MetricValueFetcher.Snapshot99PercentileValue)\n                    .put(MetricField.P999_EXECUTION_TIME, MetricValueFetcher.Snapshot999PercentileValue)\n                    .build())\n            .meterType(MetricSubType.PRODUCER, ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                .put(MetricField.PRODUCER_M1_RATE, MetricValueFetcher.MeteredM1Rate)\n                .put(MetricField.PRODUCER_M5_RATE, MetricValueFetcher.MeteredM5Rate)\n                .put(MetricField.PRODUCER_M15_RATE, MetricValueFetcher.MeteredM15Rate)\n                .build())\n            .meterType(MetricSubType.PRODUCER_ERROR, ImmutableMap.<MetricField, MetricValueFetcher>builder()\n                .put(MetricField.PRODUCER_M1_ERROR_RATE, MetricValueFetcher.MeteredM1Rate)\n                .put(MetricField.PRODUCER_M5_ERROR_RATE, MetricValueFetcher.MeteredM5Rate)\n                .put(MetricField.PRODUCER_M15_ERROR_RATE, MetricValueFetcher.MeteredM15Rate)\n                .build())\n            .build();\n    }\n}\n"
  },
  {
    "path": "plugins/rabbitmq/src/main/java/com/megaease/easeagent/plugin/rabbitmq/RabbitMqRedirectPlugin.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.rabbitmq;\n\nimport com.megaease.easeagent.plugin.AgentPlugin;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\n\npublic class RabbitMqRedirectPlugin implements AgentPlugin {\n    @Override\n    public String getNamespace() {\n        return ConfigConst.Namespace.RABBITMQ;\n    }\n\n    @Override\n    public String getDomain() {\n        return ConfigConst.INTEGRABILITY;\n    }\n}\n"
  },
  {
    "path": "plugins/rabbitmq/src/main/java/com/megaease/easeagent/plugin/rabbitmq/spring/RabbitMqMessageListenerAdvice.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.rabbitmq.spring;\n\n\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.Set;\n\npublic class RabbitMqMessageListenerAdvice implements Points {\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return ClassMatcher.builder()\n            .hasInterface(\"org.springframework.amqp.core.MessageListener\")\n            .build();\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.builder()\n            .named(\"OnMessage\")\n            .build()\n            .or(MethodMatcher.builder()\n                .named(\"OnMessageBatch\")\n                .build())\n            .toSet();\n    }\n}\n"
  },
  {
    "path": "plugins/rabbitmq/src/main/java/com/megaease/easeagent/plugin/rabbitmq/spring/interceptor/RabbitMqMessageListenerOnMessageInterceptor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.rabbitmq.spring.interceptor;\n\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.ContextCons;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.rabbitmq.spring.RabbitMqMessageListenerAdvice;\nimport org.springframework.amqp.core.Message;\n\nimport java.util.List;\n\n@AdviceTo(RabbitMqMessageListenerAdvice.class)\npublic class RabbitMqMessageListenerOnMessageInterceptor implements Interceptor {\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public void before(MethodInfo methodInfo, Context context) {\n        Message message;\n        if (methodInfo.getArgs()[0] instanceof List) {\n            List<Message> messageList = (List<Message>) methodInfo.getArgs()[0];\n            message = messageList.get(0);\n        } else {\n            message = (Message) methodInfo.getArgs()[0];\n        }\n        String uri = message.getMessageProperties().getHeader(ContextCons.MQ_URI);\n        context.put(ContextCons.MQ_URI, uri);\n    }\n\n    @Override\n    public int order() {\n        return Order.HIGHEST.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/rabbitmq/src/main/java/com/megaease/easeagent/plugin/rabbitmq/spring/interceptor/RabbitMqOnMessageMetricInterceptor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.rabbitmq.spring.interceptor;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.logging.Logger;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.rabbitmq.RabbitMqConsumerMetric;\nimport com.megaease.easeagent.plugin.rabbitmq.spring.RabbitMqMessageListenerAdvice;\nimport org.springframework.amqp.core.Message;\n\nimport java.util.List;\n\n@SuppressWarnings(\"unused\")\n@AdviceTo(RabbitMqMessageListenerAdvice.class)\npublic class RabbitMqOnMessageMetricInterceptor implements Interceptor {\n    private static final Logger LOGGER = EaseAgent.getLogger(RabbitMqOnMessageMetricInterceptor.class);\n    private static final String AFTER_MARK = RabbitMqOnMessageMetricInterceptor.class.getName() + \"$AfterMark\";\n    private static final Object START = new Object();\n    private static volatile RabbitMqConsumerMetric metric = null;\n\n    @Override\n    @SuppressWarnings(\"all\")\n    public void init(IPluginConfig config, String className, String methodName, String methodDescriptor) {\n        metric = EaseAgent.getOrCreateServiceMetric(config, RabbitMqConsumerMetric.buildOnMessageTags(), RabbitMqConsumerMetric.SERVICE_METRIC_SUPPLIER);\n    }\n\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n        context.put(START, System.currentTimeMillis());\n    }\n\n    @Override\n    public void after(MethodInfo methodInfo, Context context) {\n        if (methodInfo.getArgs()[0] instanceof List) {\n            List<Message> messageList = (List<Message>) methodInfo.getArgs()[0];\n            for (Message message : messageList) {\n                metric.metricAfter(message.getMessageProperties().getConsumerQueue(),\n                    context.get(START), methodInfo.isSuccess());\n            }\n        } else {\n            Message message = (Message) methodInfo.getArgs()[0];\n            metric.metricAfter(message.getMessageProperties().getConsumerQueue(),\n                context.get(START), methodInfo.isSuccess());\n        }\n    }\n\n    @Override\n    public String getType() {\n        return Order.METRIC.getName();\n    }\n\n    @Override\n    public int order() {\n        return Order.METRIC.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/rabbitmq/src/main/java/com/megaease/easeagent/plugin/rabbitmq/spring/interceptor/RabbitMqOnMessageTracingInterceptor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.rabbitmq.spring.interceptor;\n\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.ContextCons;\nimport com.megaease.easeagent.plugin.api.context.ContextUtils;\nimport com.megaease.easeagent.plugin.api.middleware.MiddlewareConstants;\nimport com.megaease.easeagent.plugin.api.middleware.Redirect;\nimport com.megaease.easeagent.plugin.api.middleware.RedirectProcessor;\nimport com.megaease.easeagent.plugin.api.middleware.Type;\nimport com.megaease.easeagent.plugin.api.trace.MessagingRequest;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.rabbitmq.spring.RabbitMqMessageListenerAdvice;\nimport org.springframework.amqp.core.Message;\nimport org.springframework.amqp.core.MessageProperties;\n\nimport java.util.List;\n\n@SuppressWarnings(\"unused\")\n@AdviceTo(RabbitMqMessageListenerAdvice.class)\npublic class RabbitMqOnMessageTracingInterceptor implements Interceptor {\n    protected static final String SCOPE_CONTEXT_KEY = RabbitMqOnMessageTracingInterceptor.class.getName() + \"-Tracer.SpanInScope\";\n    protected static final String SPAN_CONTEXT_KEY = RabbitMqOnMessageTracingInterceptor.class.getName() + \"-Span\";\n\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n        if (methodInfo.getArgs()[0] instanceof List) {\n            Span span = context.currentTracing().nextSpan();\n            span.name(\"on-message-list\").cacheScope().start();\n            context.put(SCOPE_CONTEXT_KEY, span);\n            this.before4List(methodInfo, context);\n        } else {\n            this.before4Single(methodInfo, context);\n        }\n    }\n\n    @Override\n    public void after(MethodInfo methodInfo, Context context) {\n        if (methodInfo.getArgs()[0] instanceof List) {\n            this.after4List(methodInfo, context);\n            Span span = context.remove(SCOPE_CONTEXT_KEY);\n            span.finish();\n        } else {\n            this.after4Single(methodInfo, context);\n        }\n    }\n\n    @Override\n    public int order() {\n        return Order.TRACING.getOrder();\n    }\n\n    protected void after4Single(MethodInfo methodInfo, Context context) {\n        this.processMessageAfter(methodInfo, context, 0);\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    protected void after4List(MethodInfo methodInfo, Context context) {\n        List<Message> messageList = (List<Message>) methodInfo.getArgs()[0];\n        for (int i = 0; i < messageList.size(); i++) {\n            this.processMessageAfter(methodInfo, context, i);\n        }\n    }\n\n    protected void processMessageAfter(MethodInfo methodInfo, Context context, int index) {\n        // CurrentTraceContext.Scope newScope = ContextUtils.getFromContext(context, SCOPE_CONTEXT_KEY + index);\n        Span span = context.remove(SPAN_CONTEXT_KEY + index);\n        if (!methodInfo.isSuccess()) {\n            span.error(methodInfo.getThrowable());\n        }\n        // newScope.close();\n        span.finish();\n    }\n\n\n    protected void before4Single(MethodInfo methodInfo, Context context) {\n        Message message = (Message) methodInfo.getArgs()[0];\n        this.processMessageBefore(message, context, 0);\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    protected void before4List(MethodInfo methodInfo, Context context) {\n        List<Message> messageList = (List<Message>) methodInfo.getArgs()[0];\n        for (int i = 0; i < messageList.size(); i++) {\n            Message message = messageList.get(i);\n            this.processMessageBefore(message, context, i);\n        }\n    }\n\n    protected void processMessageBefore(Message message, Context context, int index) {\n        String uri = ContextUtils.getFromContext(context, ContextCons.MQ_URI);\n        MessageProperties messageProperties = message.getMessageProperties();\n        RabbitConsumerRequest request = new RabbitConsumerRequest(message);\n        // RequestContext progressContext = context.serverReceive(request);\n        // TraceContextOrSamplingFlags samplingFlags = this.extractor.extract(request);\n        // Span span = Tracing.currentTracer().nextSpan(samplingFlags);\n        Span span = context.consumerSpan(request);\n        span.tag(\"rabbit.exchange\", messageProperties.getReceivedExchange());\n        span.tag(\"rabbit.routing_key\", messageProperties.getReceivedRoutingKey());\n        span.tag(\"rabbit.queue\", messageProperties.getConsumerQueue());\n        if (uri != null) {\n            span.tag(\"rabbit.broker\", uri);\n        }\n        span.remoteServiceName(\"rabbitmq\");\n        span.tag(MiddlewareConstants.TYPE_TAG_NAME, Type.RABBITMQ.getRemoteType());\n        RedirectProcessor.setTagsIfRedirected(Redirect.RABBITMQ, span);\n        span.start();\n\n        // CurrentTraceContext currentTraceContext = Tracing.current().currentTraceContext();\n        // CurrentTraceContext.Scope newScope = currentTraceContext.newScope(span.context());\n        // context.put(SCOPE_CONTEXT_KEY + index, newScope);\n        context.put(SPAN_CONTEXT_KEY + index, span);\n    }\n\n    @Override\n    public String getType() {\n        return Order.TRACING.getName();\n    }\n\n    static class RabbitConsumerRequest implements MessagingRequest {\n        private final Message message;\n\n        public RabbitConsumerRequest(Message message) {\n            this.message = message;\n        }\n\n        @Override\n        public String operation() {\n            return \"receive\";\n        }\n\n        @Override\n        public String channelKind() {\n            return \"queue\";\n        }\n\n        @Override\n        public String channelName() {\n            return this.message.getMessageProperties().getConsumerQueue();\n        }\n\n        @Override\n        public Object unwrap() {\n            return message;\n        }\n\n        @Override\n        public Span.Kind kind() {\n            return Span.Kind.CONSUMER;\n        }\n\n        public String header(String name) {\n            return message.getMessageProperties().getHeader(name);\n        }\n\n        @Override\n        public String name() {\n            return \"on-message\";\n        }\n\n        @Override\n        public boolean cacheScope() {\n            return false;\n        }\n\n        @Override\n        public void setHeader(String name, String value) {\n            message.getMessageProperties().setHeader(name, value);\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/rabbitmq/src/main/java/com/megaease/easeagent/plugin/rabbitmq/v5/advice/RabbitMqChannelAdvice.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.rabbitmq.v5.advice;\n\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.Set;\n\npublic class RabbitMqChannelAdvice implements Points {\n    public IClassMatcher getClassMatcher() {\n        return ClassMatcher.builder().hasInterface(\"com.rabbitmq.client.Channel\")\n                .notAbstract()\n                .notInterface()\n                .build();\n    }\n\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.multiBuilder()\n                .match(MethodMatcher.builder().named(\"basicPublish\")\n                        .isPublic()\n                        .argsLength(6)\n                        .returnType(\"void\")\n                        .qualifier(\"basicPublish\")\n                        .build())\n                .match(MethodMatcher.builder().named(\"basicConsume\")\n                        .isPublic()\n                        .argsLength(7)\n                        .qualifier(\"basicConsume\")\n                        .build())\n                .build();\n    }\n\n    public boolean isAddDynamicField() {\n        return false;\n    }\n\n    /*\n    @AdviceTo(value = RabbitMqChannelAdvice.class, qualifier = \"basicPublish\")\n    public static class PublishInterceptor implements Interceptor {\n        public void before(MethodInfo methodInfo, Map<Object, Object> context) {\n            return;\n        }\n\n        public Object after(MethodInfo methodInfo, Map<Object, Object> context) {\n            return null;\n        }\n    }\n\n    @AdviceTo(value = RabbitMqChannelAdvice.class, qualifier = \"basicConsume\")\n    public static class ConsumeInterceptor implements Interceptor {\n        public void before(MethodInfo methodInfo, Map<Object, Object> context) {\n            return;\n        }\n\n        public Object after(MethodInfo methodInfo, Map<Object, Object> context) {\n            return null;\n        }\n    }\n    */\n\n    /**\n    @Override\n    public <T extends Definition> T define(Definition<T> def) {\n        return def\n                .type(hasSuperType(named(\"com.rabbitmq.client.Channel\")).and(not(isInterface().or(isAbstract()))))\n                .transform(objConstruct(none(), AgentDynamicFieldAccessor.DYNAMIC_FIELD_NAME))\n                .transform(doBasicPublish(named(\"basicPublish\")\n                        .and(takesArguments(6))\n                        .and(isPublic()).and(returns(TypeDescription.VOID))))\n                .transform(doBasicConsume(named(\"basicConsume\")\n                        .and(takesArguments(7))\n                        .and(isPublic())))\n                .end()\n                ;\n    }\n\n    @AdviceTo(ObjConstruct.class)\n    public abstract Definition.Transformer objConstruct(ElementMatcher<? super MethodDescription> matcher, String fieldName);\n\n    static class ObjConstruct extends AbstractAdvice {\n\n        public ObjConstruct() {\n            super(null, null);\n        }\n\n        @Advice.OnMethodExit\n        public void exit() {\n\n        }\n    }\n\n    @AdviceTo(DoBasicPublish.class)\n    public abstract Definition.Transformer doBasicPublish(ElementMatcher<? super MethodDescription> matcher);\n\n    static class DoBasicPublish extends AbstractAdvice {\n        @Injection.Autowire\n        public DoBasicPublish(@Injection.Qualifier(\"supplier4RabbitMqBasicPublish\") Supplier<AgentInterceptorChain.Builder> supplier,\n                              AgentInterceptorChainInvoker chainInvoker) {\n            super(supplier, chainInvoker);\n        }\n\n        @Advice.OnMethodEnter\n        public ForwardLock.Release<Map<Object, Object>> enter(\n                @Advice.This Object invoker,\n                @Advice.Origin(\"#m\") String method,\n                @Advice.AllArguments Object[] args\n        ) {\n            return this.doEnter(invoker, method, args);\n        }\n\n        @Advice.OnMethodExit(onThrowable = Throwable.class)\n        public void exit(@Advice.Enter ForwardLock.Release<Map<Object, Object>> release,\n                         @Advice.This Object invoker,\n                         @Advice.Origin(\"#m\") String method,\n                         @Advice.AllArguments(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object[] args,\n                         @Advice.Thrown Throwable throwable\n        ) {\n            this.doExitNoRetValue(release, invoker, method, args, throwable);\n        }\n    }\n\n    @AdviceTo(DoBasicConsume.class)\n    public abstract Definition.Transformer doBasicConsume(ElementMatcher<? super MethodDescription> matcher);\n\n    static class DoBasicConsume extends AbstractAdvice {\n\n        @Injection.Autowire\n        public DoBasicConsume(@Injection.Qualifier(\"supplier4RabbitMqBasicConsume\") Supplier<AgentInterceptorChain.Builder> supplier,\n                              AgentInterceptorChainInvoker chainInvoker) {\n            super(supplier, chainInvoker);\n        }\n\n        @Advice.OnMethodEnter\n        public ForwardLock.Release<Map<Object, Object>> enter(\n                @Advice.This Object invoker,\n                @Advice.Origin(\"#m\") String method,\n                @Advice.AllArguments Object[] args\n        ) {\n            return this.doEnter(invoker, method, args);\n        }\n\n        @Advice.OnMethodExit(onThrowable = Throwable.class)\n        public void exit(@Advice.Enter ForwardLock.Release<Map<Object, Object>> release,\n                         @Advice.This Object invoker,\n                         @Advice.Origin(\"#m\") String method,\n                         @Advice.AllArguments(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object[] args,\n                         @Advice.Thrown Throwable throwable\n        ) {\n            this.doExitNoRetValue(release, invoker, method, args, throwable);\n        }\n    }\n    */\n}\n"
  },
  {
    "path": "plugins/rabbitmq/src/main/java/com/megaease/easeagent/plugin/rabbitmq/v5/advice/RabbitMqConfigFactoryAdvice.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.rabbitmq.v5.advice;\n\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.Set;\n\npublic class RabbitMqConfigFactoryAdvice implements Points {\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return ClassMatcher.builder()\n            .hasSuperClass(\"com.rabbitmq.client.ConnectionFactory\")\n            .build();\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.builder()\n            .nameStartWith(\"set\").build().toSet();\n    }\n}\n"
  },
  {
    "path": "plugins/rabbitmq/src/main/java/com/megaease/easeagent/plugin/rabbitmq/v5/advice/RabbitMqConsumerAdvice.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.rabbitmq.v5.advice;\n\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.Set;\n\npublic class RabbitMqConsumerAdvice implements Points {\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return ClassMatcher.builder()\n            .hasSuperClass(\"com.rabbitmq.client.Consumer\")\n            .notAbstract()\n            .notInterface()\n            .build();\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.builder().named(\"handleDelivery\")\n            .build()\n            .toSet();\n    }\n\n    @Override\n    public boolean isAddDynamicField() {\n        return true;\n    }\n\n    /*\n    @Override\n    public <T extends Definition> T define(Definition<T> def) {\n        return def\n                .type(hasSuperType(named(\"com.rabbitmq.client.Consumer\")).and(not(isInterface().or(isAbstract()))))\n                .transform(objConstruct(none(), AgentDynamicFieldAccessor.DYNAMIC_FIELD_NAME))\n                .transform(doHandleDelivery(named(\"handleDelivery\")))\n                .end()\n                ;\n    }\n\n    @AdviceTo(ObjConstruct.class)\n    public abstract Definition.Transformer objConstruct(ElementMatcher<? super MethodDescription> matcher, String fieldName);\n\n    static class ObjConstruct extends AbstractAdvice {\n\n        public ObjConstruct() {\n            super(null, null);\n        }\n\n        @Advice.OnMethodExit\n        public void exit() {\n\n        }\n    }\n\n\n    @AdviceTo(DoHandleDelivery.class)\n    public abstract Definition.Transformer doHandleDelivery(ElementMatcher<? super MethodDescription> matcher);\n\n    static class DoHandleDelivery extends AbstractAdvice {\n        @Injection.Autowire\n        public DoHandleDelivery(@Injection.Qualifier(\"supplier4RabbitMqHandleDelivery\") Supplier<AgentInterceptorChain.Builder> supplier,\n                                AgentInterceptorChainInvoker chainInvoker) {\n            super(supplier, chainInvoker);\n        }\n\n        @Advice.OnMethodEnter\n        public ForwardLock.Release<Map<Object, Object>> enter(\n                @Advice.This Object invoker,\n                @Advice.Origin(\"#m\") String method,\n                @Advice.AllArguments Object[] args\n        ) {\n            return this.doEnter(invoker, method, args);\n        }\n\n        @Advice.OnMethodExit(onThrowable = Throwable.class)\n        public void exit(@Advice.Enter ForwardLock.Release<Map<Object, Object>> release,\n                         @Advice.This Object invoker,\n                         @Advice.Origin(\"#m\") String method,\n                         @Advice.AllArguments Object[] args,\n                         @Advice.Thrown Throwable throwable\n        ) {\n            this.doExitNoRetValue(release, invoker, method, args, throwable);\n        }\n    }\n    */\n\n}\n"
  },
  {
    "path": "plugins/rabbitmq/src/main/java/com/megaease/easeagent/plugin/rabbitmq/v5/advice/RabbitMqPropertyAdvice.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.rabbitmq.v5.advice;\n\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.Set;\n\npublic class RabbitMqPropertyAdvice implements Points {\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return ClassMatcher.builder()\n            .hasClassName(\"org.springframework.boot.autoconfigure.amqp.RabbitProperties\")\n            .build();\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.builder()\n            .nameStartWith(\"set\").build().toSet();\n    }\n}\n"
  },
  {
    "path": "plugins/rabbitmq/src/main/java/com/megaease/easeagent/plugin/rabbitmq/v5/interceptor/RabbitMqChannelConsumeInterceptor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.rabbitmq.v5.interceptor;\n\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.field.AgentDynamicFieldAccessor;\nimport com.megaease.easeagent.plugin.rabbitmq.RabbitMqPlugin;\nimport com.megaease.easeagent.plugin.rabbitmq.v5.advice.RabbitMqChannelAdvice;\nimport com.rabbitmq.client.Channel;\nimport com.rabbitmq.client.Connection;\nimport com.rabbitmq.client.Consumer;\n\nimport java.net.InetAddress;\n\n@SuppressWarnings(\"unused\")\n@AdviceTo(value = RabbitMqChannelAdvice.class, qualifier = \"basicConsume\", plugin = RabbitMqPlugin.class)\npublic class RabbitMqChannelConsumeInterceptor implements Interceptor {\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n        Channel channel = (Channel) methodInfo.getInvoker();\n        Connection connection = channel.getConnection();\n        InetAddress address = connection.getAddress();\n        String hostAddress = address.getHostAddress();\n        String uri = hostAddress + \":\" + connection.getPort();\n        Consumer consumer = (Consumer) methodInfo.getArgs()[6];\n        AgentDynamicFieldAccessor.setDynamicFieldValue(consumer, uri);\n    }\n\n    @Override\n    public int order() {\n        return Order.HIGHEST.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/rabbitmq/src/main/java/com/megaease/easeagent/plugin/rabbitmq/v5/interceptor/RabbitMqChannelConsumerDeliveryInterceptor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.rabbitmq.v5.interceptor;\n\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.ContextCons;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.field.AgentDynamicFieldAccessor;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\nimport com.megaease.easeagent.plugin.rabbitmq.RabbitMqPlugin;\nimport com.megaease.easeagent.plugin.rabbitmq.v5.advice.RabbitMqConsumerAdvice;\nimport com.rabbitmq.client.AMQP;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n@AdviceTo(value = RabbitMqConsumerAdvice.class, plugin = RabbitMqPlugin.class)\npublic class RabbitMqChannelConsumerDeliveryInterceptor implements Interceptor {\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n        String uri = AgentDynamicFieldAccessor.getDynamicFieldValue(methodInfo.getInvoker());\n        context.put(ContextCons.MQ_URI, uri);\n        AMQP.BasicProperties properties = (AMQP.BasicProperties) methodInfo.getArgs()[2];\n        Map<String, Object> headers = new HashMap<>();\n        headers.put(ContextCons.MQ_URI, uri);\n        if (properties.getHeaders() != null) {\n            headers.putAll(properties.getHeaders());\n        }\n        AgentFieldReflectAccessor.setFieldValue(properties, \"headers\", headers);\n    }\n\n    @Override\n    public int order() {\n        return Order.HIGHEST.getOrder();\n    }\n\n    @Override\n    public String getType() {\n        return Order.TRACING.getName();\n    }\n}\n"
  },
  {
    "path": "plugins/rabbitmq/src/main/java/com/megaease/easeagent/plugin/rabbitmq/v5/interceptor/RabbitMqChannelPublishInterceptor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.rabbitmq.v5.interceptor;\n\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.ContextCons;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.rabbitmq.RabbitMqPlugin;\nimport com.megaease.easeagent.plugin.rabbitmq.v5.advice.RabbitMqChannelAdvice;\nimport com.rabbitmq.client.AMQP;\nimport com.rabbitmq.client.Channel;\nimport com.rabbitmq.client.Connection;\nimport com.rabbitmq.client.MessageProperties;\n\nimport java.net.InetAddress;\n\n@AdviceTo(value = RabbitMqChannelAdvice.class, qualifier = \"basicPublish\", plugin = RabbitMqPlugin.class)\npublic class RabbitMqChannelPublishInterceptor implements Interceptor {\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n        Channel channel = (Channel) methodInfo.getInvoker();\n        AMQP.BasicProperties basicProperties = (AMQP.BasicProperties) methodInfo.getArgs()[4];\n        if (basicProperties == null) {\n            basicProperties = MessageProperties.MINIMAL_BASIC;\n            methodInfo.getArgs()[4] = basicProperties;\n        }\n        Connection connection = channel.getConnection();\n        InetAddress address = connection.getAddress();\n        String hostAddress = address.getHostAddress();\n        String uri = hostAddress + \":\" + connection.getPort();\n        context.put(ContextCons.MQ_URI, uri);\n    }\n\n    @Override\n    public int order() {\n        return Order.HIGHEST.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/rabbitmq/src/main/java/com/megaease/easeagent/plugin/rabbitmq/v5/interceptor/RabbitMqConsumerHandleDeliveryInterceptor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.rabbitmq.v5.interceptor;\n\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.ContextCons;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.field.AgentDynamicFieldAccessor;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\nimport com.megaease.easeagent.plugin.rabbitmq.RabbitMqPlugin;\nimport com.megaease.easeagent.plugin.rabbitmq.v5.advice.RabbitMqConsumerAdvice;\nimport com.rabbitmq.client.AMQP;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n@AdviceTo(value = RabbitMqConsumerAdvice.class, plugin = RabbitMqPlugin.class)\npublic class RabbitMqConsumerHandleDeliveryInterceptor implements Interceptor {\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n        String uri = AgentDynamicFieldAccessor.getDynamicFieldValue(methodInfo.getInvoker());\n        // context.put(ContextCons.MQ_URI, uri);\n        AMQP.BasicProperties properties = (AMQP.BasicProperties) methodInfo.getArgs()[2];\n        Map<String, Object> headers = new HashMap<>();\n        headers.put(ContextCons.MQ_URI, uri);\n        if (properties.getHeaders() != null) {\n            headers.putAll(properties.getHeaders());\n        }\n        AgentFieldReflectAccessor.setFieldValue(properties, \"headers\", headers);\n    }\n\n    @Override\n    public int order() {\n        return Order.HIGHEST.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/rabbitmq/src/main/java/com/megaease/easeagent/plugin/rabbitmq/v5/interceptor/metirc/RabbitMqConsumerMetricInterceptor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.rabbitmq.v5.interceptor.metirc;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.rabbitmq.RabbitMqConsumerMetric;\nimport com.megaease.easeagent.plugin.rabbitmq.RabbitMqPlugin;\nimport com.megaease.easeagent.plugin.rabbitmq.v5.advice.RabbitMqConsumerAdvice;\nimport com.rabbitmq.client.Envelope;\n\n@AdviceTo(value = RabbitMqConsumerAdvice.class, plugin = RabbitMqPlugin.class)\npublic class RabbitMqConsumerMetricInterceptor implements Interceptor {\n    private static final Object START = new Object();\n    private static volatile RabbitMqConsumerMetric metric = null;\n\n    @Override\n    @SuppressWarnings(\"all\")\n    public void init(IPluginConfig config, String className, String methodName, String methodDescriptor) {\n        metric = EaseAgent.getOrCreateServiceMetric(config, RabbitMqConsumerMetric.buildConsumerTags(), RabbitMqConsumerMetric.SERVICE_METRIC_SUPPLIER);\n    }\n\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n        context.put(START, System.currentTimeMillis());\n    }\n\n    @Override\n    public void after(MethodInfo methodInfo, Context context) {\n        Envelope envelope = (Envelope) methodInfo.getArgs()[1];\n        metric.metricAfter(envelope.getRoutingKey(), context.get(START), methodInfo.isSuccess());\n    }\n\n    @Override\n    public String getType() {\n        return Order.METRIC.getName();\n    }\n\n    @Override\n    public int order() {\n        return Order.METRIC.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/rabbitmq/src/main/java/com/megaease/easeagent/plugin/rabbitmq/v5/interceptor/metirc/RabbitMqProducerMetricInterceptor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.rabbitmq.v5.interceptor.metirc;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.context.ContextUtils;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.rabbitmq.RabbitMqPlugin;\nimport com.megaease.easeagent.plugin.rabbitmq.RabbitMqProducerMetric;\nimport com.megaease.easeagent.plugin.rabbitmq.v5.advice.RabbitMqChannelAdvice;\n\n@SuppressWarnings(\"unused\")\n@AdviceTo(value = RabbitMqChannelAdvice.class, qualifier = \"basicPublish\", plugin = RabbitMqPlugin.class)\npublic class RabbitMqProducerMetricInterceptor implements Interceptor {\n    private static volatile RabbitMqProducerMetric metric = null;\n\n    @Override\n    public void init(IPluginConfig config, String className, String methodName, String methodDescriptor) {\n        metric = EaseAgent.getOrCreateServiceMetric(config, RabbitMqProducerMetric.buildTags(), RabbitMqProducerMetric.SERVICE_METRIC_SUPPLIER);\n    }\n\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n    }\n\n    @Override\n    public void after(MethodInfo methodInfo, Context context) {\n        String exchange = (String) methodInfo.getArgs()[0];\n        String routingKey = (String) methodInfo.getArgs()[1];\n        metric.metricAfter(exchange, routingKey, ContextUtils.getBeginTime(context), methodInfo.isSuccess());\n    }\n\n    @Override\n    public String getType() {\n        return Order.METRIC.getName();\n    }\n\n    @Override\n    public int order() {\n        return Order.METRIC.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/rabbitmq/src/main/java/com/megaease/easeagent/plugin/rabbitmq/v5/interceptor/redirect/RabbitMqConfigFactoryInterceptor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.rabbitmq.v5.interceptor.redirect;\n\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.rabbitmq.RabbitMqRedirectPlugin;\nimport com.megaease.easeagent.plugin.rabbitmq.v5.advice.RabbitMqConfigFactoryAdvice;\n\n@AdviceTo(value = RabbitMqConfigFactoryAdvice.class, plugin = RabbitMqRedirectPlugin.class)\npublic class RabbitMqConfigFactoryInterceptor implements Interceptor {\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n    }\n\n    @Override\n    public String getType() {\n        return Order.REDIRECT.getName();\n    }\n\n    @Override\n    public int order() {\n        return Order.REDIRECT.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/rabbitmq/src/main/java/com/megaease/easeagent/plugin/rabbitmq/v5/interceptor/redirect/RabbitMqPropertyInterceptor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.rabbitmq.v5.interceptor.redirect;\n\n\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.logging.Logger;\nimport com.megaease.easeagent.plugin.api.middleware.Redirect;\nimport com.megaease.easeagent.plugin.api.middleware.RedirectProcessor;\nimport com.megaease.easeagent.plugin.api.middleware.ResourceConfig;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.rabbitmq.RabbitMqRedirectPlugin;\nimport com.megaease.easeagent.plugin.rabbitmq.v5.advice.RabbitMqPropertyAdvice;\nimport com.megaease.easeagent.plugin.utils.common.StringUtils;\nimport lombok.SneakyThrows;\n\nimport java.net.URI;\n\n@AdviceTo(value = RabbitMqPropertyAdvice.class, plugin = RabbitMqRedirectPlugin.class)\npublic class RabbitMqPropertyInterceptor implements Interceptor {\n    private static final Logger LOGGER = EaseAgent.getLogger(RabbitMqPropertyInterceptor.class);\n\n\n    @SneakyThrows\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n        ResourceConfig cnf = Redirect.RABBITMQ.getConfig();\n        if (cnf == null) {\n            return;\n        }\n        String method = methodInfo.getMethod();\n        ResourceConfig.HostAndPort hostAndPort = cnf.getFirstHostAndPort();\n        String host = hostAndPort.getHost();\n        Integer port = hostAndPort.getPort();\n        String uriStr = hostAndPort.uri();\n        if (method.equals(\"setHost\") && host != null) {\n            LOGGER.info(\"Redirect RabbitMq host: {} to {}\", methodInfo.getArgs()[0], host);\n            methodInfo.changeArg(0, host);\n            RedirectProcessor.redirected(Redirect.RABBITMQ, hostAndPort.uri());\n        } else if (method.equals(\"setPort\") && port != null) {\n            LOGGER.info(\"Redirect RabbitMq port: {} to {}\", methodInfo.getArgs()[0], port);\n            methodInfo.changeArg(0, port);\n            RedirectProcessor.redirected(Redirect.RABBITMQ, hostAndPort.uri());\n        } else if (method.equals(\"setUri\") && uriStr != null) {\n            if (methodInfo.getArgs()[0] instanceof URI) {\n                URI oldURI = (URI) methodInfo.getArgs()[0];\n                URI newURI = new URI(oldURI.getScheme(), oldURI.getUserInfo(), host, port, oldURI.getPath(), oldURI.getQuery(), oldURI.getFragment());\n                LOGGER.info(\"Redirect RabbitMq uri: {} to {}\", oldURI, newURI);\n                methodInfo.changeArg(0, newURI);\n                RedirectProcessor.redirected(Redirect.RABBITMQ, hostAndPort.uri());\n            } else if (methodInfo.getArgs()[0] instanceof String) {\n                URI oldURI = new URI((String) methodInfo.getArgs()[0]);\n                URI newURI = new URI(oldURI.getScheme(), oldURI.getUserInfo(), host, port, oldURI.getPath(), oldURI.getQuery(), oldURI.getFragment());\n                LOGGER.info(\"Redirect RabbitMq uri: {} to {}\", oldURI, newURI);\n                methodInfo.changeArg(0, newURI.toString());\n                RedirectProcessor.redirected(Redirect.RABBITMQ, hostAndPort.uri());\n            }\n        } else if (methodInfo.getMethod().equals(\"setUsername\") && StringUtils.isNotEmpty(cnf.getUserName())) {\n            LOGGER.info(\"Redirect RabbitMq Username: {} to {}\", methodInfo.getArgs()[0], cnf.getUserName());\n            methodInfo.changeArg(0, cnf.getUserName());\n        } else if (methodInfo.getMethod().equals(\"setPassword\") && StringUtils.isNotEmpty(cnf.getPassword())) {\n            LOGGER.info(\"Redirect RabbitMq Password: *** to ***\");\n            methodInfo.changeArg(0, cnf.getPassword());\n        }\n    }\n\n    @Override\n    public String getType() {\n        return Order.REDIRECT.getName();\n    }\n\n    @Override\n    public int order() {\n        return Order.REDIRECT.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/rabbitmq/src/main/java/com/megaease/easeagent/plugin/rabbitmq/v5/interceptor/tracing/RabbitMqChannelPublishTracingInterceptor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.rabbitmq.v5.interceptor.tracing;\n\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.ContextCons;\nimport com.megaease.easeagent.plugin.api.context.ContextUtils;\nimport com.megaease.easeagent.plugin.api.middleware.MiddlewareConstants;\nimport com.megaease.easeagent.plugin.api.middleware.Redirect;\nimport com.megaease.easeagent.plugin.api.middleware.RedirectProcessor;\nimport com.megaease.easeagent.plugin.api.middleware.Type;\nimport com.megaease.easeagent.plugin.api.trace.MessagingRequest;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\nimport com.megaease.easeagent.plugin.rabbitmq.RabbitMqPlugin;\nimport com.megaease.easeagent.plugin.rabbitmq.v5.advice.RabbitMqChannelAdvice;\nimport com.rabbitmq.client.AMQP;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n@SuppressWarnings(\"unused\")\n@AdviceTo(value = RabbitMqChannelAdvice.class, qualifier = \"basicPublish\", plugin = RabbitMqPlugin.class)\npublic class RabbitMqChannelPublishTracingInterceptor implements Interceptor {\n    private static final String SPAN_CONTEXT_KEY = RabbitMqChannelPublishTracingInterceptor.class.getName() + \"-Span\";\n\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n        String uri = ContextUtils.getFromContext(context, ContextCons.MQ_URI);\n        String exchange = null;\n        if (methodInfo.getArgs()[0] != null) {\n            exchange = (String) methodInfo.getArgs()[0];\n        }\n        String routingKey = null;\n        if (methodInfo.getArgs()[1] != null) {\n            routingKey = (String) methodInfo.getArgs()[1];\n        }\n        AMQP.BasicProperties basicProperties = (AMQP.BasicProperties) methodInfo.getArgs()[4];\n        RabbitProducerRequest producerRequest = new RabbitProducerRequest(exchange, routingKey, basicProperties);\n\n        Span span = context.producerSpan(producerRequest);\n        span.tag(MiddlewareConstants.TYPE_TAG_NAME, Type.RABBITMQ.getRemoteType());\n        RedirectProcessor.setTagsIfRedirected(Redirect.RABBITMQ, span);\n        if (exchange != null) {\n            span.tag(\"rabbit.exchange\", exchange);\n        }\n        if (routingKey != null) {\n            span.tag(\"rabbit.routing_key\", routingKey);\n        }\n        span.tag(\"rabbit.broker\", uri);\n        if (!span.isNoop()) {\n            span.remoteServiceName(\"rabbitmq\");\n            span.start();\n        }\n\n        context.put(SPAN_CONTEXT_KEY, span);\n    }\n\n    @Override\n    public void after(MethodInfo method, Context context) {\n        Span span = ContextUtils.getFromContext(context, SPAN_CONTEXT_KEY);\n        if (span == null) {\n            return;\n        }\n        if (!method.isSuccess()) {\n            span.error(method.getThrowable());\n        }\n        span.finish();\n    }\n\n    @Override\n    public String getType() {\n        return Order.TRACING.getName();\n    }\n\n    @Override\n    public int order() {\n        return Order.TRACING.getOrder();\n    }\n\n    static class RabbitProducerRequest implements MessagingRequest {\n        private final String exchange;\n        private final String routingKey;\n        private final AMQP.BasicProperties basicProperties;\n\n        public RabbitProducerRequest(String exchange, String routingKey, AMQP.BasicProperties basicProperties) {\n            this.exchange = exchange;\n            this.routingKey = routingKey;\n            this.basicProperties = basicProperties;\n\n            Map<String, Object> headers = new HashMap<>();\n            if (this.basicProperties.getHeaders() != null) {\n                headers.putAll(this.basicProperties.getHeaders());\n            }\n            AgentFieldReflectAccessor.setFieldValue(this.basicProperties, \"headers\", headers);\n        }\n\n        @Override\n        public String operation() {\n            return \"send\";\n        }\n\n        @Override\n        public String channelKind() {\n            return \"queue\";\n        }\n\n        @Override\n        public String channelName() {\n            if (this.exchange != null && this.exchange.length() > 0) {\n                return this.exchange;\n            }\n            return this.routingKey;\n        }\n\n        @Override\n        public Object unwrap() {\n            return null;\n        }\n\n        @Override\n        public Span.Kind kind() {\n            return Span.Kind.PRODUCER;\n        }\n\n        @Override\n        public String header(String key) {\n            Map<String, Object> headers = this.basicProperties.getHeaders();\n            Object obj = headers.get(key);\n            if (obj == null) {\n                return null;\n            }\n            return obj.toString();\n        }\n\n        @Override\n        public String name() {\n            return \"publish\";\n        }\n\n        @Override\n        public boolean cacheScope() {\n            return false;\n        }\n\n        @Override\n        public void setHeader(String key, String value) {\n            this.basicProperties.getHeaders().put(key, value);\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/rabbitmq/src/main/java/com/megaease/easeagent/plugin/rabbitmq/v5/interceptor/tracing/RabbitMqConsumerTracingInterceptor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.rabbitmq.v5.interceptor.tracing;\n\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.ContextCons;\nimport com.megaease.easeagent.plugin.api.context.ContextUtils;\nimport com.megaease.easeagent.plugin.api.middleware.MiddlewareConstants;\nimport com.megaease.easeagent.plugin.api.middleware.Redirect;\nimport com.megaease.easeagent.plugin.api.middleware.RedirectProcessor;\nimport com.megaease.easeagent.plugin.api.middleware.Type;\nimport com.megaease.easeagent.plugin.api.trace.MessagingRequest;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\nimport com.megaease.easeagent.plugin.rabbitmq.RabbitMqPlugin;\nimport com.megaease.easeagent.plugin.rabbitmq.v5.advice.RabbitMqConsumerAdvice;\nimport com.rabbitmq.client.AMQP;\nimport com.rabbitmq.client.Envelope;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n@SuppressWarnings(\"unused\")\n@AdviceTo(value = RabbitMqConsumerAdvice.class, plugin = RabbitMqPlugin.class)\npublic class RabbitMqConsumerTracingInterceptor implements Interceptor {\n    private static final String SPAN_CONTEXT_KEY = RabbitMqConsumerTracingInterceptor.class.getName() + \"-Span\";\n\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n        String uri = ContextUtils.getFromContext(context, ContextCons.MQ_URI);\n        Envelope envelope = (Envelope) methodInfo.getArgs()[1];\n        AMQP.BasicProperties basicProperties = (AMQP.BasicProperties) methodInfo.getArgs()[2];\n        RabbitConsumerRequest consumerRequest = new RabbitConsumerRequest(envelope, basicProperties);\n\n        Span span = context.consumerSpan(consumerRequest);\n        span.kind(Span.Kind.CONSUMER);\n        span.tag(\"rabbit.exchange\", envelope.getExchange());\n        span.tag(\"rabbit.routing_key\", envelope.getRoutingKey());\n        span.tag(\"rabbit.queue\", envelope.getRoutingKey());\n        if (uri != null) {\n            span.tag(\"rabbit.broker\", uri);\n        }\n        span.remoteServiceName(\"rabbitmq\");\n        span.tag(MiddlewareConstants.TYPE_TAG_NAME, Type.RABBITMQ.getRemoteType());\n        RedirectProcessor.setTagsIfRedirected(Redirect.RABBITMQ, span);\n        span.start();\n        context.put(SPAN_CONTEXT_KEY, span);\n        context.consumerInject(span, consumerRequest);\n    }\n\n    @Override\n    public void after(MethodInfo methodInfo, Context context) {\n        Span span = context.remove(SPAN_CONTEXT_KEY);\n        if (span == null) {\n            return;\n        }\n        if (!methodInfo.isSuccess()) {\n            span.error(methodInfo.getThrowable());\n        }\n        span.finish();\n    }\n\n    @Override\n    public String getType() {\n        return Order.TRACING.getName();\n    }\n\n    @Override\n    public int order() {\n        return Order.TRACING.getOrder();\n    }\n\n    static class RabbitConsumerRequest implements MessagingRequest {\n        private final Envelope envelope;\n        private final Map<String, Object> headers = new HashMap<>();\n\n        public RabbitConsumerRequest(Envelope envelope, AMQP.BasicProperties basicProperties) {\n            this.envelope = envelope;\n            Map<String, Object> originHeaders = basicProperties.getHeaders();\n            if (originHeaders != null) {\n                headers.putAll(originHeaders);\n            }\n            AgentFieldReflectAccessor.setFieldValue(basicProperties, \"headers\", headers);\n        }\n\n        @Override\n        public String operation() {\n            return \"receive\";\n        }\n\n        @Override\n        public String channelKind() {\n            return \"queue\";\n        }\n\n        @Override\n        public String channelName() {\n            return this.envelope.getRoutingKey();\n        }\n\n        @Override\n        public Object unwrap() {\n            return null;\n        }\n\n        @Override\n        public Span.Kind kind() {\n            return Span.Kind.CONSUMER;\n        }\n\n        public String header(String key) {\n            Object obj = headers.get(key);\n            if (obj == null) {\n                return null;\n            }\n            return obj.toString();\n        }\n\n        @Override\n        public String name() {\n            return \"next-message\";\n        }\n\n        @Override\n        public boolean cacheScope() {\n            return false;\n        }\n\n        @Override\n        public void setHeader(String name, String value) {\n            this.headers.put(name, value);\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/rabbitmq/src/test/java/com/megaease/easeagent/plugin/rabbitmq/RabbitMqConsumerMetricTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.rabbitmq;\n\nimport com.megaease.easeagent.mock.plugin.api.MockEaseAgent;\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.mock.plugin.api.utils.TagVerifier;\nimport com.megaease.easeagent.mock.report.impl.LastJsonReporter;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.metric.Meter;\nimport com.megaease.easeagent.plugin.api.metric.name.MetricField;\nimport com.megaease.easeagent.plugin.api.metric.name.MetricSubType;\nimport com.megaease.easeagent.plugin.api.metric.name.NameFactory;\nimport com.megaease.easeagent.plugin.api.metric.name.Tags;\nimport com.megaease.easeagent.plugin.api.middleware.Redirect;\nimport com.megaease.easeagent.plugin.api.middleware.RedirectProcessor;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport java.util.Collections;\nimport java.util.Map;\nimport java.util.Objects;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertFalse;\nimport static org.junit.Assert.assertTrue;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class RabbitMqConsumerMetricTest {\n\n    @Test\n    public void buildOnMessageTags() {\n        Tags tags = RabbitMqConsumerMetric.buildOnMessageTags();\n        assertEquals(\"application\", tags.getCategory());\n        assertEquals(\"rabbitmq-queue\", tags.getType());\n        assertEquals(\"resource\", tags.getKeyFieldName());\n        assertTrue(tags.getTags().isEmpty());\n        RedirectProcessor.redirected(Redirect.RABBITMQ, TestUtils.getRedirectUri());\n        String testKey = \"testKey\";\n        String testValue = \"testValue\";\n        AgentFieldReflectAccessor.setFieldValue(RedirectProcessor.INSTANCE, \"tags\", Collections.singletonMap(testKey, testValue));\n\n        tags = RabbitMqConsumerMetric.buildOnMessageTags();\n        assertFalse(tags.getTags().isEmpty());\n        assertEquals(testValue, tags.getTags().get(testKey));\n\n\n        Map<Redirect, String> redirectedUris = AgentFieldReflectAccessor.getFieldValue(RedirectProcessor.INSTANCE, \"redirectedUris\");\n        Objects.requireNonNull(redirectedUris).remove(Redirect.RABBITMQ);\n    }\n\n    @Test\n    public void buildConsumerTags() {\n        Tags tags = RabbitMqConsumerMetric.buildConsumerTags();\n        assertEquals(\"application\", tags.getCategory());\n        assertEquals(\"rabbitmq-consumer\", tags.getType());\n        assertEquals(\"resource\", tags.getKeyFieldName());\n        assertTrue(tags.getTags().isEmpty());\n        RedirectProcessor.redirected(Redirect.RABBITMQ, TestUtils.getRedirectUri());\n        String testKey = \"testKey\";\n        String testValue = \"testValue\";\n        AgentFieldReflectAccessor.setFieldValue(RedirectProcessor.INSTANCE, \"tags\", Collections.singletonMap(testKey, testValue));\n\n        tags = RabbitMqConsumerMetric.buildConsumerTags();\n        assertFalse(tags.getTags().isEmpty());\n        assertEquals(testValue, tags.getTags().get(testKey));\n\n\n        Map<Redirect, String> redirectedUris = AgentFieldReflectAccessor.getFieldValue(RedirectProcessor.INSTANCE, \"redirectedUris\");\n        Objects.requireNonNull(redirectedUris).remove(Redirect.RABBITMQ);\n\n    }\n\n    @Test\n    public void buildNameFactory() {\n        NameFactory nameFactory = RabbitMqConsumerMetric.buildNameFactory();\n        String key = \"testBuildNameFactory\";\n        assertEquals(1, nameFactory.timerNames(key).size());\n        assertEquals(2, nameFactory.meterNames(key).size());\n    }\n\n    @Test\n    public void metricAfter() {\n        IPluginConfig config = TestUtils.getMqMetricConfig();\n        RabbitMqConsumerMetric metric = EaseAgent.getOrCreateServiceMetric(config, RabbitMqConsumerMetric.buildConsumerTags(), RabbitMqConsumerMetric.SERVICE_METRIC_SUPPLIER);\n        String key = \"testMetricAfter\";\n        metric.metricAfter(key, System.currentTimeMillis() - 101, true);\n        TagVerifier tagVerifier = TagVerifier.build(RabbitMqConsumerMetric.buildConsumerTags(), key);\n        LastJsonReporter lastJsonReporter = MockEaseAgent.lastMetricJsonReporter(tagVerifier::verifyAnd);\n        Map<String, Object> metrics = lastJsonReporter.flushAndOnlyOne();\n        assertTrue((int) (double) metrics.get(MetricField.MIN_EXECUTION_TIME.getField()) > 100);\n        Meter meter = metric.meter(key, MetricSubType.CONSUMER);\n        Meter meterError = metric.meter(key, MetricSubType.CONSUMER_ERROR);\n        assertEquals(1, meter.getCount());\n        assertEquals(0, meterError.getCount());\n\n        metric.metricAfter(key, System.currentTimeMillis() - 100, false);\n        assertEquals(2, meter.getCount());\n        assertEquals(1, meterError.getCount());\n    }\n}\n"
  },
  {
    "path": "plugins/rabbitmq/src/test/java/com/megaease/easeagent/plugin/rabbitmq/RabbitMqProducerMetricTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.rabbitmq;\n\nimport com.megaease.easeagent.mock.plugin.api.MockEaseAgent;\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.mock.plugin.api.utils.TagVerifier;\nimport com.megaease.easeagent.mock.report.impl.LastJsonReporter;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.metric.Meter;\nimport com.megaease.easeagent.plugin.api.metric.name.MetricField;\nimport com.megaease.easeagent.plugin.api.metric.name.MetricSubType;\nimport com.megaease.easeagent.plugin.api.metric.name.NameFactory;\nimport com.megaease.easeagent.plugin.api.metric.name.Tags;\nimport com.megaease.easeagent.plugin.api.middleware.Redirect;\nimport com.megaease.easeagent.plugin.api.middleware.RedirectProcessor;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport java.util.Collections;\nimport java.util.Map;\nimport java.util.Objects;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class RabbitMqProducerMetricTest {\n\n    @Test\n    public void metricAfter() {\n        IPluginConfig config = TestUtils.getMqMetricConfig();\n        RabbitMqProducerMetric metric = EaseAgent.getOrCreateServiceMetric(config, RabbitMqProducerMetric.buildTags(), RabbitMqProducerMetric.SERVICE_METRIC_SUPPLIER);\n        String exchange = \"testExchange\";\n        String routingKey = \"testRoutingKey\";\n        String key = String.join(\"-\", exchange, routingKey);\n        metric.metricAfter(exchange, routingKey, System.currentTimeMillis() - 101, true);\n        TagVerifier tagVerifier = TagVerifier.build(RabbitMqProducerMetric.buildTags(), key);\n        LastJsonReporter lastJsonReporter = MockEaseAgent.lastMetricJsonReporter(tagVerifier::verifyAnd);\n        Map<String, Object> metrics = lastJsonReporter.flushAndOnlyOne();\n        assertTrue(((int) (double) metrics.get(MetricField.MIN_EXECUTION_TIME.getField())) > 100);\n        Meter meter = metric.meter(key, MetricSubType.PRODUCER);\n        Meter meterError = metric.meter(key, MetricSubType.PRODUCER_ERROR);\n        assertEquals(1, meter.getCount());\n        assertEquals(0, meterError.getCount());\n\n        metric.metricAfter(exchange, routingKey, System.currentTimeMillis() - 101, false);\n        assertEquals(2, meter.getCount());\n        assertEquals(1, meterError.getCount());\n\n    }\n\n    @Test\n    public void buildTags() {\n        Tags tags = RabbitMqProducerMetric.buildTags();\n        assertEquals(\"application\", tags.getCategory());\n        assertEquals(\"rabbitmq-ex-ro\", tags.getType());\n        assertEquals(\"resource\", tags.getKeyFieldName());\n        assertTrue(tags.getTags().isEmpty());\n        RedirectProcessor.redirected(Redirect.RABBITMQ, TestUtils.getRedirectUri());\n        String testKey = \"testKey\";\n        String testValue = \"testValue\";\n        AgentFieldReflectAccessor.setFieldValue(RedirectProcessor.INSTANCE, \"tags\", Collections.singletonMap(testKey, testValue));\n\n        tags = RabbitMqProducerMetric.buildTags();\n        assertFalse(tags.getTags().isEmpty());\n        assertEquals(testValue, tags.getTags().get(testKey));\n\n\n        Map<Redirect, String> redirectedUris = AgentFieldReflectAccessor.getFieldValue(RedirectProcessor.INSTANCE, \"redirectedUris\");\n        Objects.requireNonNull(redirectedUris).remove(Redirect.RABBITMQ);\n\n    }\n\n    @Test\n    public void buildNameFactory() {\n        NameFactory nameFactory = RabbitMqProducerMetric.buildNameFactory();\n        String key = \"testBuildNameFactory\";\n        assertEquals(1, nameFactory.timerNames(key).size());\n        assertEquals(2, nameFactory.meterNames(key).size());\n    }\n}\n"
  },
  {
    "path": "plugins/rabbitmq/src/test/java/com/megaease/easeagent/plugin/rabbitmq/TestUtils.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.rabbitmq;\n\nimport com.megaease.easeagent.mock.utils.MockSystemEnv;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.middleware.MiddlewareConstants;\nimport com.megaease.easeagent.plugin.api.middleware.Redirect;\nimport com.megaease.easeagent.plugin.api.middleware.RedirectProcessor;\nimport com.megaease.easeagent.plugin.api.middleware.ResourceConfig;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\npublic class TestUtils {\n    public static final String REDIRECT_HOST = \"192.186.0.12\";\n    public static final int REDIRECT_PORT = 5672;\n    public static final String REDIRECT_USERNAME = \"testUserName\";\n    public static final String REDIRECT_PASSWORD = \"testPassword\";\n\n    public static IPluginConfig getMqMetricConfig() {\n        RabbitMqPlugin rabbitMqPlugin = new RabbitMqPlugin();\n        return EaseAgent.getConfig(rabbitMqPlugin.getDomain(), rabbitMqPlugin.getNamespace(), ConfigConst.PluginID.METRIC);\n    }\n\n    public static String getRedirectUri() {\n        return String.format(\"%s:%s\", REDIRECT_HOST, REDIRECT_PORT);\n    }\n\n    public static void setRedirect() {\n        MockSystemEnv.set(MiddlewareConstants.ENV_RABBITMQ, String.format(\"{\\\"uris\\\":\\\"%s\\\", \\\"userName\\\":\\\"%s\\\",\\\"password\\\":\\\"%s\\\"}\", getRedirectUri(), REDIRECT_USERNAME, REDIRECT_PASSWORD));\n        AgentFieldReflectAccessor.setFieldValue(Redirect.RABBITMQ, \"config\", ResourceConfig.getResourceConfig(Redirect.RABBITMQ.getEnv(), Redirect.RABBITMQ.isNeedParse()));\n    }\n\n    public static String getRedirectedUri() {\n        Map<Redirect, String> redirectedUris = AgentFieldReflectAccessor.getFieldValue(RedirectProcessor.INSTANCE, \"redirectedUris\");\n        return redirectedUris.get(Redirect.RABBITMQ);\n    }\n\n    public static void cleanRedirectedUri() {\n        AgentFieldReflectAccessor.setFieldValue(RedirectProcessor.INSTANCE, \"redirectedUris\", new HashMap<>());\n    }\n}\n"
  },
  {
    "path": "plugins/rabbitmq/src/test/java/com/megaease/easeagent/plugin/rabbitmq/spring/interceptor/RabbitMqMessageListenerOnMessageInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.rabbitmq.spring.interceptor;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.ContextCons;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.amqp.core.Message;\nimport org.springframework.amqp.core.MessageProperties;\n\nimport java.util.Collections;\n\nimport static org.junit.Assert.assertEquals;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class RabbitMqMessageListenerOnMessageInterceptorTest {\n\n    @Test\n    public void before() {\n        RabbitMqMessageListenerOnMessageInterceptor interceptor = new RabbitMqMessageListenerOnMessageInterceptor();\n        MessageProperties messageProperties = new MessageProperties();\n        String testMqUri = \"testMqUri\";\n        messageProperties.setHeader(ContextCons.MQ_URI, testMqUri);\n        Message message = new Message(\"testBody\".getBytes(), messageProperties);\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{message}).build();\n        Context context = EaseAgent.getContext();\n        interceptor.before(methodInfo, context);\n        assertEquals(testMqUri, context.get(ContextCons.MQ_URI));\n        context.remove(ContextCons.MQ_URI);\n\n        methodInfo = MethodInfo.builder().args(new Object[]{Collections.singletonList(message)}).build();\n        interceptor.before(methodInfo, context);\n        assertEquals(testMqUri, context.get(ContextCons.MQ_URI));\n    }\n\n    @Test\n    public void order() {\n        RabbitMqMessageListenerOnMessageInterceptor interceptor = new RabbitMqMessageListenerOnMessageInterceptor();\n        assertEquals(Order.HIGHEST.getOrder(), interceptor.order());\n    }\n}\n"
  },
  {
    "path": "plugins/rabbitmq/src/test/java/com/megaease/easeagent/plugin/rabbitmq/spring/interceptor/RabbitMqOnMessageMetricInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.rabbitmq.spring.interceptor;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.mock.plugin.api.utils.InterceptorTestUtils;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.api.context.ContextCons;\nimport com.megaease.easeagent.plugin.api.metric.Meter;\nimport com.megaease.easeagent.plugin.api.metric.name.MetricSubType;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.rabbitmq.RabbitMqConsumerMetric;\nimport com.megaease.easeagent.plugin.rabbitmq.RabbitMqPlugin;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.amqp.core.Message;\nimport org.springframework.amqp.core.MessageProperties;\n\nimport java.util.Arrays;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class RabbitMqOnMessageMetricInterceptorTest {\n    private static final Object START_KEY = AgentFieldReflectAccessor.getStaticFieldValue(RabbitMqOnMessageMetricInterceptor.class, \"START\");\n\n    @Test\n    public void init() {\n        RabbitMqOnMessageMetricInterceptor interceptor = new RabbitMqOnMessageMetricInterceptor();\n        InterceptorTestUtils.init(interceptor, new RabbitMqPlugin());\n        assertNotNull(AgentFieldReflectAccessor.getStaticFieldValue(RabbitMqOnMessageMetricInterceptor.class, \"metric\"));\n    }\n\n    @Test\n    public void before() {\n        RabbitMqOnMessageMetricInterceptor interceptor = new RabbitMqOnMessageMetricInterceptor();\n        interceptor.before(null, EaseAgent.getContext());\n        assertNotNull(EaseAgent.getContext().get(START_KEY));\n    }\n\n    @Test\n    public void after() {\n        RabbitMqOnMessageMetricInterceptor interceptor = new RabbitMqOnMessageMetricInterceptor();\n        InterceptorTestUtils.init(interceptor, new RabbitMqPlugin());\n\n        MessageProperties messageProperties = new MessageProperties();\n        String queue = \"testConsumerQueue\";\n        messageProperties.setConsumerQueue(queue);\n        String testMqUri = \"testMqUri\";\n        messageProperties.setHeader(ContextCons.MQ_URI, testMqUri);\n        Message message = new Message(\"testBody\".getBytes(), messageProperties);\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{message}).build();\n        Context context = EaseAgent.getContext();\n        context.put(START_KEY, System.currentTimeMillis() - 100);\n        interceptor.after(methodInfo, context);\n\n        RabbitMqConsumerMetric metric = AgentFieldReflectAccessor.getStaticFieldValue(RabbitMqOnMessageMetricInterceptor.class, \"metric\");\n        Meter meter = metric.meter(queue, MetricSubType.CONSUMER);\n        Meter meterError = metric.meter(queue, MetricSubType.CONSUMER_ERROR);\n        assertEquals(1, meter.getCount());\n        assertEquals(0, meterError.getCount());\n\n        methodInfo = MethodInfo.builder().args(new Object[]{Arrays.asList(message, message)}).build();\n        interceptor.after(methodInfo, context);\n        assertEquals(3, meter.getCount());\n        assertEquals(0, meterError.getCount());\n\n        methodInfo = MethodInfo.builder().args(new Object[]{message}).throwable(new RuntimeException(\"testError\")).build();\n        interceptor.after(methodInfo, context);\n        assertEquals(4, meter.getCount());\n        assertEquals(1, meterError.getCount());\n\n        methodInfo = MethodInfo.builder().args(new Object[]{Arrays.asList(message, message)}).throwable(new RuntimeException(\"testError\")).build();\n        interceptor.after(methodInfo, context);\n        assertEquals(6, meter.getCount());\n        assertEquals(3, meterError.getCount());\n    }\n\n    @Test\n    public void getType() {\n        RabbitMqOnMessageMetricInterceptor interceptor = new RabbitMqOnMessageMetricInterceptor();\n        assertEquals(ConfigConst.PluginID.METRIC, interceptor.getType());\n    }\n\n    @Test\n    public void order() {\n        RabbitMqOnMessageMetricInterceptor interceptor = new RabbitMqOnMessageMetricInterceptor();\n        assertEquals(Order.METRIC.getOrder(), interceptor.order());\n    }\n}\n"
  },
  {
    "path": "plugins/rabbitmq/src/test/java/com/megaease/easeagent/plugin/rabbitmq/spring/interceptor/RabbitMqOnMessageTracingInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.rabbitmq.spring.interceptor;\n\nimport com.megaease.easeagent.mock.plugin.api.MockEaseAgent;\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.mock.plugin.api.utils.SpanTestUtils;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.api.context.ContextCons;\nimport com.megaease.easeagent.plugin.api.middleware.MiddlewareConstants;\nimport com.megaease.easeagent.plugin.api.middleware.Redirect;\nimport com.megaease.easeagent.plugin.api.middleware.RedirectProcessor;\nimport com.megaease.easeagent.plugin.api.middleware.Type;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.rabbitmq.TestUtils;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.amqp.core.Message;\nimport org.springframework.amqp.core.MessageProperties;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class RabbitMqOnMessageTracingInterceptorTest {\n    String uri = \"testUri\";\n    String exchange = \"testExchange\";\n    String routingKey = \"testRoutingKey\";\n    String queue = \"testConsumerQueue\";\n    String testMqUri = \"testMqUri\";\n    String body = \"testBody\";\n\n    @Test\n    public void before() {\n        RabbitMqOnMessageTracingInterceptor interceptor = new RabbitMqOnMessageTracingInterceptor();\n        Context context = EaseAgent.getContext();\n        Message message = buildMessage(context);\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{message}).build();\n        interceptor.before(methodInfo, context);\n        assertNotNull(context.get(RabbitMqOnMessageTracingInterceptor.SPAN_CONTEXT_KEY + 0));\n        context.<Span>remove(RabbitMqOnMessageTracingInterceptor.SPAN_CONTEXT_KEY + 0).finish();\n        assertNull(context.get(RabbitMqOnMessageTracingInterceptor.SPAN_CONTEXT_KEY + 1));\n\n        int count = 5;\n        List<Message> messages = new ArrayList<>();\n        for (int i = 0; i < count; i++) {\n            messages.add(buildMessage(context));\n        }\n        methodInfo = MethodInfo.builder().args(new Object[]{messages}).build();\n        interceptor.before(methodInfo, context);\n        for (int i = 0; i < count; i++) {\n            assertNotNull(context.get(RabbitMqOnMessageTracingInterceptor.SPAN_CONTEXT_KEY + i));\n            context.<Span>remove(RabbitMqOnMessageTracingInterceptor.SPAN_CONTEXT_KEY + i).finish();\n        }\n        assertNull(context.get(RabbitMqOnMessageTracingInterceptor.SPAN_CONTEXT_KEY + (count + 1)));\n        assertNotNull(context.get(RabbitMqOnMessageTracingInterceptor.SCOPE_CONTEXT_KEY));\n\n        context.<Span>remove(RabbitMqOnMessageTracingInterceptor.SCOPE_CONTEXT_KEY).finish();\n        ReportSpan reportSpan = MockEaseAgent.getLastSpan();\n        assertEquals(\"on-message-list\", reportSpan.name());\n    }\n\n    @Test\n    public void after() {\n        RabbitMqOnMessageTracingInterceptor interceptor = new RabbitMqOnMessageTracingInterceptor();\n        Context context = EaseAgent.getContext();\n        Message message = buildMessage(context);\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{message}).build();\n        interceptor.before(methodInfo, context);\n        Span span = context.get(RabbitMqOnMessageTracingInterceptor.SPAN_CONTEXT_KEY + 0);\n        interceptor.after(methodInfo, context);\n        ReportSpan reportSpan = MockEaseAgent.getLastSpan();\n        SpanTestUtils.sameId(span, reportSpan);\n\n        int count = 5;\n        List<Message> messages = new ArrayList<>();\n        for (int i = 0; i < count; i++) {\n            messages.add(buildMessage(context));\n        }\n        methodInfo = MethodInfo.builder().args(new Object[]{messages}).build();\n        interceptor.before(methodInfo, context);\n        List<Span> spans = new ArrayList<>();\n        for (int i = 0; i < count; i++) {\n            spans.add(context.get(RabbitMqOnMessageTracingInterceptor.SPAN_CONTEXT_KEY + i));\n        }\n        span = context.get(RabbitMqOnMessageTracingInterceptor.SCOPE_CONTEXT_KEY);\n\n        List<ReportSpan> reportSpans = new ArrayList<>();\n        MockEaseAgent.setMockSpanReport(reportSpans::add);\n        interceptor.after(methodInfo, context);\n\n        assertEquals(spans.size() + 1, reportSpans.size());\n\n        for (int i = 0; i < spans.size(); i++) {\n            ReportSpan child = reportSpans.get(i);\n            SpanTestUtils.sameId(spans.get(i), child);\n            assertEquals(span.traceIdString(), child.traceId());\n            assertEquals(span.spanIdString(), child.parentId());\n        }\n\n        reportSpan = reportSpans.get(reportSpans.size() - 1);\n        assertEquals(\"on-message-list\", reportSpan.name());\n    }\n\n    @Test\n    public void before4Single() {\n        RabbitMqOnMessageTracingInterceptor interceptor = new RabbitMqOnMessageTracingInterceptor();\n        Context context = EaseAgent.getContext();\n        Message message = buildMessage(context);\n\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{message}).build();\n        interceptor.before4Single(methodInfo, context);\n        assertNotNull(context.get(RabbitMqOnMessageTracingInterceptor.SPAN_CONTEXT_KEY + 0));\n        context.<Span>get(RabbitMqOnMessageTracingInterceptor.SPAN_CONTEXT_KEY + 0).finish();\n        assertNull(context.get(RabbitMqOnMessageTracingInterceptor.SPAN_CONTEXT_KEY + 1));\n\n    }\n\n    @Test\n    public void before4List() {\n        RabbitMqOnMessageTracingInterceptor interceptor = new RabbitMqOnMessageTracingInterceptor();\n        Context context = EaseAgent.getContext();\n        int count = 5;\n        List<Message> messages = new ArrayList<>();\n        for (int i = 0; i < count; i++) {\n            messages.add(buildMessage(context));\n        }\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{messages}).build();\n        interceptor.before4List(methodInfo, context);\n        for (int i = 0; i < count; i++) {\n            assertNotNull(context.get(RabbitMqOnMessageTracingInterceptor.SPAN_CONTEXT_KEY + i));\n            context.<Span>remove(RabbitMqOnMessageTracingInterceptor.SPAN_CONTEXT_KEY + i).finish();\n        }\n        assertNull(context.get(RabbitMqOnMessageTracingInterceptor.SPAN_CONTEXT_KEY + (count + 1)));\n    }\n\n    public Message buildMessage(Context context) {\n        context.put(ContextCons.MQ_URI, uri);\n        MessageProperties messageProperties = new MessageProperties();\n        messageProperties.setReceivedExchange(exchange);\n        messageProperties.setReceivedRoutingKey(routingKey);\n        messageProperties.setConsumerQueue(queue);\n        messageProperties.setHeader(ContextCons.MQ_URI, testMqUri);\n        return new Message(body.getBytes(), messageProperties);\n    }\n\n    @Test\n    public void processMessageBefore() {\n        RabbitMqOnMessageTracingInterceptor interceptor = new RabbitMqOnMessageTracingInterceptor();\n        Context context = EaseAgent.getContext();\n        Message message = buildMessage(context);\n\n        interceptor.processMessageBefore(message, context, 0);\n        Span span = context.get(RabbitMqOnMessageTracingInterceptor.SPAN_CONTEXT_KEY + 0);\n        assertNotNull(span);\n        span.finish();\n        ReportSpan reportSpan = MockEaseAgent.getLastSpan();\n\n        assertEquals(Span.Kind.CONSUMER.name(), reportSpan.kind());\n        assertEquals(exchange, reportSpan.tag(\"rabbit.exchange\"));\n        assertEquals(routingKey, reportSpan.tag(\"rabbit.routing_key\"));\n        assertEquals(queue, reportSpan.tag(\"rabbit.queue\"));\n        assertEquals(uri, reportSpan.tag(\"rabbit.broker\"));\n        assertEquals(\"rabbitmq\", reportSpan.remoteServiceName());\n        assertEquals(Type.RABBITMQ.getRemoteType(), reportSpan.tag(MiddlewareConstants.TYPE_TAG_NAME));\n        assertNull(reportSpan.tag(MiddlewareConstants.REDIRECTED_LABEL_REMOTE_TAG_NAME));\n\n        TestUtils.setRedirect();\n        RedirectProcessor.redirected(Redirect.RABBITMQ, TestUtils.getRedirectUri());\n        interceptor.processMessageBefore(message, context, 0);\n        span = context.get(RabbitMqOnMessageTracingInterceptor.SPAN_CONTEXT_KEY + 0);\n        span.finish();\n        reportSpan = MockEaseAgent.getLastSpan();\n        assertEquals(TestUtils.getRedirectUri(), reportSpan.tag(MiddlewareConstants.REDIRECTED_LABEL_REMOTE_TAG_NAME));\n    }\n\n    @Test\n    public void after4Single() {\n        RabbitMqOnMessageTracingInterceptor interceptor = new RabbitMqOnMessageTracingInterceptor();\n        Context context = EaseAgent.getContext();\n        Message message = buildMessage(context);\n\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{message}).build();\n        interceptor.before4Single(methodInfo, context);\n        interceptor.after4Single(methodInfo, context);\n        ReportSpan reportSpan = MockEaseAgent.getLastSpan();\n        assertNotNull(reportSpan);\n    }\n\n    @Test\n    public void after4List() {\n        RabbitMqOnMessageTracingInterceptor interceptor = new RabbitMqOnMessageTracingInterceptor();\n        Context context = EaseAgent.getContext();\n        int count = 5;\n        List<Message> messages = new ArrayList<>();\n        for (int i = 0; i < count; i++) {\n            messages.add(buildMessage(context));\n        }\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{messages}).build();\n        interceptor.before4List(methodInfo, context);\n        List<ReportSpan> reportSpans = new ArrayList<>();\n        MockEaseAgent.setMockSpanReport(reportSpans::add);\n        interceptor.after4List(methodInfo, context);\n        assertEquals(count, reportSpans.size());\n    }\n\n    @Test\n    public void processMessageAfter() {\n        RabbitMqOnMessageTracingInterceptor interceptor = new RabbitMqOnMessageTracingInterceptor();\n        Context context = EaseAgent.getContext();\n        Message message = buildMessage(context);\n\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{message}).build();\n        interceptor.before4Single(methodInfo, context);\n        Span span = context.get(RabbitMqOnMessageTracingInterceptor.SPAN_CONTEXT_KEY + 0);\n        interceptor.processMessageAfter(methodInfo, context, 0);\n        ReportSpan reportSpan = MockEaseAgent.getLastSpan();\n        assertNotNull(reportSpan);\n        SpanTestUtils.sameId(span, reportSpan);\n\n        String errorInfo = \"test error\";\n        methodInfo = MethodInfo.builder().args(new Object[]{message}).throwable(new RuntimeException(errorInfo)).build();\n        interceptor.before4Single(methodInfo, context);\n        span = context.get(RabbitMqOnMessageTracingInterceptor.SPAN_CONTEXT_KEY + 0);\n        interceptor.processMessageAfter(methodInfo, context, 0);\n        reportSpan = MockEaseAgent.getLastSpan();\n        assertNotNull(reportSpan);\n        SpanTestUtils.sameId(span, reportSpan);\n        assertTrue(reportSpan.hasError());\n        assertEquals(errorInfo, reportSpan.errorInfo());\n    }\n\n\n    @Test\n    public void getType() {\n        RabbitMqOnMessageTracingInterceptor interceptor = new RabbitMqOnMessageTracingInterceptor();\n        assertEquals(ConfigConst.PluginID.TRACING, interceptor.getType());\n    }\n\n    @Test\n    public void testRabbitConsumerRequest() {\n        MessageProperties messageProperties = new MessageProperties();\n\n        String queue = \"testConsumerQueue\";\n        messageProperties.setConsumerQueue(queue);\n        String testMqUri = \"testMqUri\";\n        messageProperties.setHeader(ContextCons.MQ_URI, testMqUri);\n\n        String body = \"testBody\";\n        Message message = new Message(body.getBytes(), messageProperties);\n        RabbitMqOnMessageTracingInterceptor.RabbitConsumerRequest request = new RabbitMqOnMessageTracingInterceptor.RabbitConsumerRequest(message);\n        assertEquals(\"receive\", request.operation());\n        assertEquals(\"queue\", request.channelKind());\n        assertEquals(queue, request.channelName());\n        assertEquals(Span.Kind.CONSUMER, request.kind());\n        assertEquals(\"on-message\", request.name());\n        assertEquals(false, request.cacheScope());\n        String testKey = \"headerKey\";\n        String testValue = \"headerValue\";\n        request.setHeader(testKey, testValue);\n        assertEquals(testValue, request.header(testKey));\n    }\n}\n"
  },
  {
    "path": "plugins/rabbitmq/src/test/java/com/megaease/easeagent/plugin/rabbitmq/v5/interceptor/MockConsumer.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.rabbitmq.v5.interceptor;\n\nimport com.megaease.easeagent.plugin.field.DynamicFieldAccessor;\nimport com.rabbitmq.client.Channel;\nimport com.rabbitmq.client.DefaultConsumer;\n\npublic class MockConsumer extends DefaultConsumer implements DynamicFieldAccessor {\n    Object data;\n\n    public MockConsumer(Channel channel) {\n        super(channel);\n    }\n\n    @Override\n    public void setEaseAgent$$DynamicField$$Data(Object data) {\n        this.data = data;\n    }\n\n    @Override\n    public Object getEaseAgent$$DynamicField$$Data() {\n        return this.data;\n    }\n}\n"
  },
  {
    "path": "plugins/rabbitmq/src/test/java/com/megaease/easeagent/plugin/rabbitmq/v5/interceptor/RabbitMqChannelConsumeInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.rabbitmq.v5.interceptor;\n\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.field.DynamicFieldAccessor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.rabbitmq.client.Channel;\nimport com.rabbitmq.client.Connection;\nimport com.rabbitmq.client.Consumer;\nimport com.rabbitmq.client.DefaultConsumer;\nimport org.junit.Test;\n\nimport java.net.InetAddress;\nimport java.net.UnknownHostException;\n\nimport static org.junit.Assert.*;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\npublic class RabbitMqChannelConsumeInterceptorTest {\n\n    @Test\n    public void before() throws UnknownHostException {\n        RabbitMqChannelConsumeInterceptor interceptor = new RabbitMqChannelConsumeInterceptor();\n        Channel channel = mock(Channel.class);\n        Connection connection = mock(Connection.class);\n        when(channel.getConnection()).thenReturn(connection);\n        String host = \"127.0.0.1\";\n        int port = 1111;\n        InetAddress inetAddress = InetAddress.getByName(host);\n        when(connection.getAddress()).thenReturn(inetAddress);\n        when(connection.getPort()).thenReturn(port);\n        MockConsumer mockConsumer = new MockConsumer(channel);\n        interceptor.before(MethodInfo.builder().invoker(channel).args(new Object[]{null, null, null, null, null, null, mockConsumer}).build(), null);\n        assertEquals(host + \":\" + port, mockConsumer.getEaseAgent$$DynamicField$$Data());\n    }\n\n\n    @Test\n    public void order() {\n        RabbitMqChannelConsumeInterceptor interceptor = new RabbitMqChannelConsumeInterceptor();\n        assertEquals(Order.HIGHEST.getOrder(), interceptor.order());\n    }\n}\n"
  },
  {
    "path": "plugins/rabbitmq/src/test/java/com/megaease/easeagent/plugin/rabbitmq/v5/interceptor/RabbitMqChannelConsumerDeliveryInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.rabbitmq.v5.interceptor;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.api.context.ContextCons;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.rabbitmq.client.AMQP;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class RabbitMqChannelConsumerDeliveryInterceptorTest {\n\n    @Test\n    public void before() {\n        RabbitMqChannelConsumerDeliveryInterceptor interceptor = new RabbitMqChannelConsumerDeliveryInterceptor();\n        AMQP.BasicProperties properties = new AMQP.BasicProperties();\n        MockConsumer mockConsumer = new MockConsumer(null);\n        String data = \"192.168.0.13:2222\";\n        mockConsumer.setEaseAgent$$DynamicField$$Data(data);\n        MethodInfo methodInfo = MethodInfo.builder().invoker(mockConsumer).args(new Object[]{null, null, properties}).build();\n        Context context = EaseAgent.getContext();\n        interceptor.before(methodInfo, context);\n        assertEquals(data, context.get(ContextCons.MQ_URI));\n        assertEquals(data, properties.getHeaders().get(ContextCons.MQ_URI));\n    }\n\n    @Test\n    public void order() {\n        RabbitMqChannelConsumerDeliveryInterceptor interceptor = new RabbitMqChannelConsumerDeliveryInterceptor();\n        assertEquals(Order.HIGHEST.getOrder(), interceptor.order());\n    }\n\n    @Test\n    public void getType() {\n        RabbitMqChannelConsumerDeliveryInterceptor interceptor = new RabbitMqChannelConsumerDeliveryInterceptor();\n        assertEquals(ConfigConst.PluginID.TRACING, interceptor.getType());\n    }\n}\n"
  },
  {
    "path": "plugins/rabbitmq/src/test/java/com/megaease/easeagent/plugin/rabbitmq/v5/interceptor/RabbitMqChannelPublishInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.rabbitmq.v5.interceptor;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.ContextCons;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.rabbitmq.client.Channel;\nimport com.rabbitmq.client.Connection;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport java.net.InetAddress;\nimport java.net.UnknownHostException;\n\nimport static org.junit.Assert.*;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class RabbitMqChannelPublishInterceptorTest {\n\n    @Test\n    public void before() throws UnknownHostException {\n        RabbitMqChannelPublishInterceptor interceptor = new RabbitMqChannelPublishInterceptor();\n        Channel channel = mock(Channel.class);\n        Connection connection = mock(Connection.class);\n        when(channel.getConnection()).thenReturn(connection);\n        String host = \"127.0.0.1\";\n        int port = 1111;\n        InetAddress inetAddress = InetAddress.getByName(host);\n        when(connection.getAddress()).thenReturn(inetAddress);\n        when(connection.getPort()).thenReturn(port);\n        MethodInfo methodInfo = MethodInfo.builder().invoker(channel).args(new Object[]{null, null, null, null, null}).build();\n        Context context = EaseAgent.getContext();\n        interceptor.before(methodInfo, context);\n        assertNotNull(methodInfo.getArgs()[4]);\n        assertEquals(host + \":\" + port, context.get(ContextCons.MQ_URI));\n    }\n\n    @Test\n    public void order() {\n        RabbitMqChannelPublishInterceptor interceptor = new RabbitMqChannelPublishInterceptor();\n        assertEquals(Order.HIGHEST.getOrder(), interceptor.order());\n    }\n}\n"
  },
  {
    "path": "plugins/rabbitmq/src/test/java/com/megaease/easeagent/plugin/rabbitmq/v5/interceptor/RabbitMqConsumerHandleDeliveryInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.rabbitmq.v5.interceptor;\n\nimport com.megaease.easeagent.plugin.api.context.ContextCons;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.rabbitmq.client.AMQP;\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\npublic class RabbitMqConsumerHandleDeliveryInterceptorTest {\n\n    @Test\n    public void before() {\n        RabbitMqConsumerHandleDeliveryInterceptor interceptor = new RabbitMqConsumerHandleDeliveryInterceptor();\n        AMQP.BasicProperties properties = new AMQP.BasicProperties();\n        MockConsumer mockConsumer = new MockConsumer(null);\n        String data = \"192.168.0.13:2222\";\n        mockConsumer.setEaseAgent$$DynamicField$$Data(data);\n        MethodInfo methodInfo = MethodInfo.builder().invoker(mockConsumer).args(new Object[]{null, null, properties}).build();\n        interceptor.before(methodInfo, EaseAgent.getContext());\n        assertEquals(data, properties.getHeaders().get(ContextCons.MQ_URI));\n\n    }\n\n    @Test\n    public void order() {\n        RabbitMqConsumerHandleDeliveryInterceptor interceptor = new RabbitMqConsumerHandleDeliveryInterceptor();\n        assertEquals(Order.HIGHEST.getOrder(), interceptor.order());\n    }\n}\n"
  },
  {
    "path": "plugins/rabbitmq/src/test/java/com/megaease/easeagent/plugin/rabbitmq/v5/interceptor/metirc/RabbitMqConsumerMetricInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.rabbitmq.v5.interceptor.metirc;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.mock.plugin.api.utils.InterceptorTestUtils;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.api.metric.Meter;\nimport com.megaease.easeagent.plugin.api.metric.name.MetricSubType;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.rabbitmq.RabbitMqConsumerMetric;\nimport com.megaease.easeagent.plugin.rabbitmq.RabbitMqPlugin;\nimport com.rabbitmq.client.Envelope;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotNull;\n\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class RabbitMqConsumerMetricInterceptorTest {\n    private static final Object START_KEY = AgentFieldReflectAccessor.getStaticFieldValue(RabbitMqConsumerMetricInterceptor.class, \"START\");\n\n    @Test\n    public void init() {\n        RabbitMqConsumerMetricInterceptor interceptor = new RabbitMqConsumerMetricInterceptor();\n        InterceptorTestUtils.init(interceptor, new RabbitMqPlugin());\n        assertNotNull(AgentFieldReflectAccessor.getStaticFieldValue(RabbitMqConsumerMetricInterceptor.class, \"metric\"));\n\n    }\n\n    @Test\n    public void before() {\n        RabbitMqConsumerMetricInterceptor interceptor = new RabbitMqConsumerMetricInterceptor();\n        interceptor.before(null, EaseAgent.getContext());\n        assertNotNull(EaseAgent.getContext().get(START_KEY));\n\n    }\n\n    @Test\n    public void after() {\n        RabbitMqConsumerMetricInterceptor interceptor = new RabbitMqConsumerMetricInterceptor();\n        InterceptorTestUtils.init(interceptor, new RabbitMqPlugin());\n\n        String queue = \"testRabbitMqConsumerMetricInterceptorConsumerQueue\";\n        Envelope envelope = new Envelope(0, false, \"\", queue);\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{null, envelope}).build();\n        Context context = EaseAgent.getContext();\n        context.put(START_KEY, System.currentTimeMillis() - 100);\n        interceptor.after(methodInfo, context);\n\n        RabbitMqConsumerMetric metric = AgentFieldReflectAccessor.getStaticFieldValue(RabbitMqConsumerMetricInterceptor.class, \"metric\");\n        Meter meter = metric.meter(queue, MetricSubType.CONSUMER);\n        Meter meterError = metric.meter(queue, MetricSubType.CONSUMER_ERROR);\n        assertEquals(1, meter.getCount());\n        assertEquals(0, meterError.getCount());\n\n\n        methodInfo = MethodInfo.builder().args(new Object[]{null, envelope}).throwable(new RuntimeException(\"testError\")).build();\n        interceptor.after(methodInfo, context);\n        assertEquals(2, meter.getCount());\n        assertEquals(1, meterError.getCount());\n    }\n\n    @Test\n    public void getType() {\n        RabbitMqConsumerMetricInterceptor interceptor = new RabbitMqConsumerMetricInterceptor();\n        assertEquals(ConfigConst.PluginID.METRIC, interceptor.getType());\n    }\n\n    @Test\n    public void order() {\n        RabbitMqConsumerMetricInterceptor interceptor = new RabbitMqConsumerMetricInterceptor();\n        assertEquals(Order.METRIC.getOrder(), interceptor.order());\n    }\n}\n"
  },
  {
    "path": "plugins/rabbitmq/src/test/java/com/megaease/easeagent/plugin/rabbitmq/v5/interceptor/metirc/RabbitMqProducerMetricInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.rabbitmq.v5.interceptor.metirc;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.mock.plugin.api.utils.InterceptorTestUtils;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.api.context.ContextUtils;\nimport com.megaease.easeagent.plugin.api.metric.Meter;\nimport com.megaease.easeagent.plugin.api.metric.name.MetricSubType;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.rabbitmq.RabbitMqPlugin;\nimport com.megaease.easeagent.plugin.rabbitmq.RabbitMqProducerMetric;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class RabbitMqProducerMetricInterceptorTest {\n\n    @Test\n    public void init() {\n        RabbitMqProducerMetricInterceptor interceptor = new RabbitMqProducerMetricInterceptor();\n        InterceptorTestUtils.init(interceptor, new RabbitMqPlugin());\n        assertNotNull(AgentFieldReflectAccessor.getStaticFieldValue(RabbitMqProducerMetricInterceptor.class, \"metric\"));\n    }\n\n    @Test\n    public void before() {\n        RabbitMqProducerMetricInterceptor interceptor = new RabbitMqProducerMetricInterceptor();\n        interceptor.before(null, null);\n        assertTrue(true);\n    }\n\n    @Test\n    public void after() {\n        RabbitMqProducerMetricInterceptor interceptor = new RabbitMqProducerMetricInterceptor();\n        InterceptorTestUtils.init(interceptor, new RabbitMqPlugin());\n        Context context = EaseAgent.getContext();\n        ContextUtils.setBeginTime(context);\n\n        String exchange = \"testExchange\";\n        String routingKey = \"testRoutingKey\";\n        String key = String.join(\"-\", exchange, routingKey);\n\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{exchange, routingKey}).build();\n        interceptor.after(methodInfo, context);\n\n        RabbitMqProducerMetric metric = AgentFieldReflectAccessor.getStaticFieldValue(RabbitMqProducerMetricInterceptor.class, \"metric\");\n        Meter meter = metric.meter(key, MetricSubType.PRODUCER);\n        Meter meterError = metric.meter(key, MetricSubType.PRODUCER_ERROR);\n        assertEquals(1, meter.getCount());\n        assertEquals(0, meterError.getCount());\n\n\n        methodInfo = MethodInfo.builder().args(new Object[]{exchange, routingKey}).throwable(new RuntimeException(\"testError\")).build();\n        interceptor.after(methodInfo, context);\n        assertEquals(2, meter.getCount());\n        assertEquals(1, meterError.getCount());\n\n\n    }\n\n    @Test\n    public void getType() {\n        RabbitMqProducerMetricInterceptor interceptor = new RabbitMqProducerMetricInterceptor();\n        assertEquals(ConfigConst.PluginID.METRIC, interceptor.getType());\n    }\n\n    @Test\n    public void order() {\n        RabbitMqProducerMetricInterceptor interceptor = new RabbitMqProducerMetricInterceptor();\n        assertEquals(Order.METRIC.getOrder(), interceptor.order());\n    }\n}\n"
  },
  {
    "path": "plugins/rabbitmq/src/test/java/com/megaease/easeagent/plugin/rabbitmq/v5/interceptor/redirect/RabbitMqPropertyInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.rabbitmq.v5.interceptor.redirect;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.rabbitmq.TestUtils;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport java.net.URI;\nimport java.net.URISyntaxException;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class RabbitMqPropertyInterceptorTest {\n\n    public void chackAndCleanRedirected() {\n        assertEquals(TestUtils.getRedirectUri(), TestUtils.getRedirectedUri());\n        TestUtils.cleanRedirectedUri();\n    }\n\n    @Test\n    public void before() throws URISyntaxException {\n        RabbitMqPropertyInterceptor interceptor = new RabbitMqPropertyInterceptor();\n        Context context = EaseAgent.getContext();\n\n        MethodInfo methodInfo = MethodInfo.builder().method(\"setHost\").args(new Object[]{null}).build();\n        interceptor.before(methodInfo, context);\n        assertNull(methodInfo.getArgs()[0]);\n        assertFalse(methodInfo.isChanged());\n\n        TestUtils.setRedirect();\n        interceptor.before(methodInfo, context);\n        assertTrue(methodInfo.isChanged());\n        assertEquals(TestUtils.REDIRECT_HOST, methodInfo.getArgs()[0]);\n        chackAndCleanRedirected();\n\n        methodInfo = MethodInfo.builder().method(\"setPort\").args(new Object[]{null}).build();\n        interceptor.before(methodInfo, context);\n        assertTrue(methodInfo.isChanged());\n        assertEquals(TestUtils.REDIRECT_PORT, methodInfo.getArgs()[0]);\n        chackAndCleanRedirected();\n\n        methodInfo = MethodInfo.builder().method(\"setUri\").args(new Object[]{\"http://127.0.0.1:111\"}).build();\n        interceptor.before(methodInfo, context);\n        assertTrue(methodInfo.isChanged());\n        assertEquals(\"http://\" + TestUtils.getRedirectUri(), methodInfo.getArgs()[0]);\n        chackAndCleanRedirected();\n\n        methodInfo = MethodInfo.builder().method(\"setUri\").args(new Object[]{new URI(\"http://127.0.0.1:111\")}).build();\n        interceptor.before(methodInfo, context);\n        assertTrue(methodInfo.isChanged());\n        assertEquals(new URI(\"http://\" + TestUtils.getRedirectUri()), methodInfo.getArgs()[0]);\n        chackAndCleanRedirected();\n\n\n        methodInfo = MethodInfo.builder().method(\"setUsername\").args(new Object[]{\"aaaa\"}).build();\n        interceptor.before(methodInfo, context);\n        assertTrue(methodInfo.isChanged());\n        assertEquals(TestUtils.REDIRECT_USERNAME, methodInfo.getArgs()[0]);\n\n        methodInfo = MethodInfo.builder().method(\"setPassword\").args(new Object[]{\"aaaa\"}).build();\n        interceptor.before(methodInfo, context);\n        assertTrue(methodInfo.isChanged());\n        assertEquals(TestUtils.REDIRECT_PASSWORD, methodInfo.getArgs()[0]);\n\n    }\n\n    @Test\n    public void getType() {\n        RabbitMqPropertyInterceptor interceptor = new RabbitMqPropertyInterceptor();\n        assertEquals(ConfigConst.PluginID.REDIRECT, interceptor.getType());\n    }\n\n    @Test\n    public void order() {\n        RabbitMqPropertyInterceptor interceptor = new RabbitMqPropertyInterceptor();\n        assertEquals(Order.REDIRECT.getOrder(), interceptor.order());\n    }\n}\n"
  },
  {
    "path": "plugins/rabbitmq/src/test/java/com/megaease/easeagent/plugin/rabbitmq/v5/interceptor/tracing/RabbitMqChannelPublishTracingInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.rabbitmq.v5.interceptor.tracing;\n\nimport com.megaease.easeagent.mock.plugin.api.MockEaseAgent;\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.api.context.ContextCons;\nimport com.megaease.easeagent.plugin.api.middleware.MiddlewareConstants;\nimport com.megaease.easeagent.plugin.api.middleware.Redirect;\nimport com.megaease.easeagent.plugin.api.middleware.RedirectProcessor;\nimport com.megaease.easeagent.plugin.api.middleware.Type;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.rabbitmq.TestUtils;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport com.rabbitmq.client.AMQP;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class RabbitMqChannelPublishTracingInterceptorTest {\n\n    @Test\n    public void before() {\n        RabbitMqChannelPublishTracingInterceptor interceptor = new RabbitMqChannelPublishTracingInterceptor();\n        Context context = EaseAgent.getContext();\n        String uri = \"192.168.0.13:2222\";\n        context.put(ContextCons.MQ_URI, uri);\n        String exchange = \"testExchange\";\n        String routingKey = \"testRoutingKey\";\n        AMQP.BasicProperties basicProperties = new AMQP.BasicProperties();\n\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{exchange, routingKey, null, null, basicProperties}).build();\n        interceptor.before(methodInfo, context);\n        interceptor.after(methodInfo, context);\n        ReportSpan reportSpan = MockEaseAgent.getLastSpan();\n        assertNotNull(reportSpan);\n        assertEquals(Span.Kind.PRODUCER.name(), reportSpan.kind());\n        assertEquals(exchange, reportSpan.tag(\"rabbit.exchange\"));\n        assertEquals(routingKey, reportSpan.tag(\"rabbit.routing_key\"));\n        assertEquals(uri, reportSpan.tag(\"rabbit.broker\"));\n        assertEquals(\"rabbitmq\", reportSpan.remoteServiceName());\n        assertEquals(Type.RABBITMQ.getRemoteType(), reportSpan.tag(MiddlewareConstants.TYPE_TAG_NAME));\n        assertNull(reportSpan.tag(MiddlewareConstants.REDIRECTED_LABEL_REMOTE_TAG_NAME));\n        assertFalse(reportSpan.hasError());\n\n        String errorInfo = \"testError\";\n        methodInfo = MethodInfo.builder().args(new Object[]{exchange, routingKey, null, null, basicProperties}).throwable(new RuntimeException(errorInfo)).build();\n        interceptor.before(methodInfo, context);\n        interceptor.after(methodInfo, context);\n        reportSpan = MockEaseAgent.getLastSpan();\n        assertTrue(reportSpan.hasError());\n        assertEquals(errorInfo, reportSpan.errorInfo());\n\n        Span span = context.nextSpan();\n        span.cacheScope();\n        methodInfo = MethodInfo.builder().args(new Object[]{exchange, routingKey, null, null, basicProperties}).build();\n        interceptor.before(methodInfo, context);\n        interceptor.after(methodInfo, context);\n        reportSpan = MockEaseAgent.getLastSpan();\n        assertEquals(span.traceIdString(), reportSpan.traceId());\n        assertEquals(span.spanIdString(), reportSpan.parentId());\n        span.finish();\n\n        TestUtils.setRedirect();\n        RedirectProcessor.redirected(Redirect.RABBITMQ, TestUtils.getRedirectUri());\n        methodInfo = MethodInfo.builder().args(new Object[]{exchange, routingKey, null, null, basicProperties}).build();\n        interceptor.before(methodInfo, context);\n        interceptor.after(methodInfo, context);\n        reportSpan = MockEaseAgent.getLastSpan();\n        assertEquals(TestUtils.getRedirectUri(), reportSpan.tag(MiddlewareConstants.REDIRECTED_LABEL_REMOTE_TAG_NAME));\n    }\n\n    @Test\n    public void after() {\n        before();\n    }\n\n    @Test\n    public void getType() {\n        RabbitMqChannelPublishTracingInterceptor interceptor = new RabbitMqChannelPublishTracingInterceptor();\n        assertEquals(ConfigConst.PluginID.TRACING, interceptor.getType());\n    }\n\n    @Test\n    public void order() {\n        RabbitMqChannelPublishTracingInterceptor interceptor = new RabbitMqChannelPublishTracingInterceptor();\n        assertEquals(Order.TRACING.getOrder(), interceptor.order());\n    }\n\n    @Test\n    public void testRabbitProducerRequest() {\n        String exchange = \"testExchange\";\n        String routingKey = \"testRoutingKey\";\n        AMQP.BasicProperties basicProperties = new AMQP.BasicProperties();\n        RabbitMqChannelPublishTracingInterceptor.RabbitProducerRequest request = new RabbitMqChannelPublishTracingInterceptor.RabbitProducerRequest(exchange, routingKey, basicProperties);\n        assertEquals(\"send\", request.operation());\n        assertEquals(\"queue\", request.channelKind());\n        assertEquals(exchange, request.channelName());\n        assertEquals(null, request.unwrap());\n        assertEquals(Span.Kind.PRODUCER, request.kind());\n        assertNull(request.header(\"aaaaaaaa\"));\n        assertEquals(\"publish\", request.name());\n        assertFalse(request.cacheScope());\n        String testKey = \"headerKey\";\n        String testValue = \"headerValue\";\n        request.setHeader(testKey, testValue);\n        assertEquals(testValue, request.header(testKey));\n    }\n}\n"
  },
  {
    "path": "plugins/rabbitmq/src/test/java/com/megaease/easeagent/plugin/rabbitmq/v5/interceptor/tracing/RabbitMqConsumerTracingInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.rabbitmq.v5.interceptor.tracing;\n\nimport com.megaease.easeagent.mock.plugin.api.MockEaseAgent;\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.api.context.ContextCons;\nimport com.megaease.easeagent.plugin.api.middleware.MiddlewareConstants;\nimport com.megaease.easeagent.plugin.api.middleware.Redirect;\nimport com.megaease.easeagent.plugin.api.middleware.RedirectProcessor;\nimport com.megaease.easeagent.plugin.api.middleware.Type;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.rabbitmq.TestUtils;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport com.rabbitmq.client.AMQP;\nimport com.rabbitmq.client.Envelope;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class RabbitMqConsumerTracingInterceptorTest {\n\n    @Test\n    public void before() {\n        RabbitMqConsumerTracingInterceptor interceptor = new RabbitMqConsumerTracingInterceptor();\n        Context context = EaseAgent.getContext();\n        String uri = \"192.168.0.13:2222\";\n        context.put(ContextCons.MQ_URI, uri);\n        String exchange = \"testExchange\";\n        String routingKey = \"testRoutingKey\";\n        Envelope envelope = new Envelope(0, false, exchange, routingKey);\n        AMQP.BasicProperties basicProperties = new AMQP.BasicProperties();\n\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{null, envelope, basicProperties}).build();\n        interceptor.before(methodInfo, context);\n        interceptor.after(methodInfo, context);\n        ReportSpan reportSpan = MockEaseAgent.getLastSpan();\n        assertNotNull(reportSpan);\n        assertEquals(Span.Kind.CONSUMER.name(), reportSpan.kind());\n        assertEquals(exchange, reportSpan.tag(\"rabbit.exchange\"));\n        assertEquals(routingKey, reportSpan.tag(\"rabbit.routing_key\"));\n        assertEquals(routingKey, reportSpan.tag(\"rabbit.queue\"));\n        assertEquals(uri, reportSpan.tag(\"rabbit.broker\"));\n        assertEquals(\"rabbitmq\", reportSpan.remoteServiceName());\n        assertEquals(Type.RABBITMQ.getRemoteType(), reportSpan.tag(MiddlewareConstants.TYPE_TAG_NAME));\n        assertNull(reportSpan.tag(MiddlewareConstants.REDIRECTED_LABEL_REMOTE_TAG_NAME));\n        assertFalse(reportSpan.hasError());\n\n        String errorInfo = \"testError\";\n        methodInfo = MethodInfo.builder().args(new Object[]{null, envelope, basicProperties}).throwable(new RuntimeException(errorInfo)).build();\n        interceptor.before(methodInfo, context);\n        interceptor.after(methodInfo, context);\n        reportSpan = MockEaseAgent.getLastSpan();\n        assertTrue(reportSpan.hasError());\n        assertEquals(errorInfo, reportSpan.errorInfo());\n\n        Span span = context.nextSpan();\n        span.cacheScope();\n        methodInfo = MethodInfo.builder().args(new Object[]{null, envelope, basicProperties}).build();\n        interceptor.before(methodInfo, context);\n        interceptor.after(methodInfo, context);\n        reportSpan = MockEaseAgent.getLastSpan();\n        assertEquals(span.traceIdString(), reportSpan.traceId());\n        assertEquals(span.spanIdString(), reportSpan.parentId());\n        span.finish();\n\n        TestUtils.setRedirect();\n        RedirectProcessor.redirected(Redirect.RABBITMQ, TestUtils.getRedirectUri());\n        methodInfo = MethodInfo.builder().args(new Object[]{null, envelope, basicProperties}).build();\n        interceptor.before(methodInfo, context);\n        interceptor.after(methodInfo, context);\n        reportSpan = MockEaseAgent.getLastSpan();\n        assertEquals(TestUtils.getRedirectUri(), reportSpan.tag(MiddlewareConstants.REDIRECTED_LABEL_REMOTE_TAG_NAME));\n\n\n    }\n\n    @Test\n    public void after() {\n        before();\n    }\n\n    @Test\n    public void getType() {\n        RabbitMqConsumerTracingInterceptor interceptor = new RabbitMqConsumerTracingInterceptor();\n        assertEquals(ConfigConst.PluginID.TRACING, interceptor.getType());\n    }\n\n    @Test\n    public void order() {\n        RabbitMqConsumerTracingInterceptor interceptor = new RabbitMqConsumerTracingInterceptor();\n        assertEquals(Order.TRACING.getOrder(), interceptor.order());\n    }\n\n    @Test\n    public void testRabbitConsumerRequest() {\n        String queue = \"testRabbitConsumerRequestQueue\";\n        Envelope envelope = new Envelope(0, false, \"\", queue);\n        AMQP.BasicProperties basicProperties = new AMQP.BasicProperties();\n        RabbitMqConsumerTracingInterceptor.RabbitConsumerRequest request = new RabbitMqConsumerTracingInterceptor.RabbitConsumerRequest(envelope, basicProperties);\n        assertEquals(\"receive\", request.operation());\n        assertEquals(\"queue\", request.channelKind());\n        assertEquals(queue, request.channelName());\n        assertEquals(null, request.unwrap());\n        assertEquals(Span.Kind.CONSUMER, request.kind());\n        assertNull(request.header(\"aaaaaaaa\"));\n        assertEquals(\"next-message\", request.name());\n        assertFalse(request.cacheScope());\n        String testKey = \"headerKey\";\n        String testValue = \"headerValue\";\n        request.setHeader(testKey, testValue);\n        assertEquals(testValue, request.header(testKey));\n    }\n}\n"
  },
  {
    "path": "plugins/rabbitmq/src/test/resources/mock_agent.properties",
    "content": "plugin.observability.rabbitmq.metric.interval=200\nplugin.observability.rabbitmq.metric.intervalUnit=MILLISECONDS\n"
  },
  {
    "path": "plugins/redis/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright (c) 2021, MegaEase\n  ~ All rights reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~   http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>plugins</artifactId>\n        <groupId>com.megaease.easeagent</groupId>\n        <version>2.3.0</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>redis</artifactId>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>plugin-api</artifactId>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.google.guava</groupId>\n            <artifactId>guava</artifactId>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>redis.clients</groupId>\n            <artifactId>jedis</artifactId>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>io.lettuce</groupId>\n            <artifactId>lettuce-core</artifactId>\n            <scope>provided</scope>\n        </dependency>\n\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>plugin-api-mock</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "plugins/redis/src/main/java/com/megaease/easeagent/plugin/redis/RedisPlugin.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.redis;\n\nimport com.megaease.easeagent.plugin.AgentPlugin;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\n\npublic class RedisPlugin implements AgentPlugin {\n\n    @Override\n    public String getNamespace() {\n        return ConfigConst.Namespace.REDIS;\n    }\n\n    @Override\n    public String getDomain() {\n        return ConfigConst.OBSERVABILITY;\n    }\n\n}\n"
  },
  {
    "path": "plugins/redis/src/main/java/com/megaease/easeagent/plugin/redis/RedisRedirectPlugin.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.redis;\n\nimport com.megaease.easeagent.plugin.AgentPlugin;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\n\npublic class RedisRedirectPlugin implements AgentPlugin {\n    @Override\n    public String getNamespace() {\n        return ConfigConst.Namespace.REDIS;\n    }\n\n    @Override\n    public String getDomain() {\n        return ConfigConst.INTEGRABILITY;\n    }\n\n}\n"
  },
  {
    "path": "plugins/redis/src/main/java/com/megaease/easeagent/plugin/redis/advice/JedisAdvice.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.redis.advice;\n\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.Set;\n\npublic class JedisAdvice implements Points {\n\n    //return def.type(hasSuperType(named(\"redis.clients.jedis.BinaryJedis\")))\n    //                .transform(doCommand(any()\n    //                        .and(isOverriddenFrom(named(\"redis.clients.jedis.commands.JedisCommands\")\n    //                                .or(named(\"redis.clients.jedis.commands.AdvancedJedisCommands\"))\n    //                                .or(named(\"redis.clients.jedis.commands.BasicCommands\"))\n    //                                .or(named(\"redis.clients.jedis.commands.ClusterCommands\"))\n    //                                .or(named(\"redis.clients.jedis.commands.ModuleCommands\"))\n    //                                .or(named(\"redis.clients.jedis.commands.MultiKeyCommands\"))\n    //                                .or(named(\"redis.clients.jedis.commands.ScriptingCommands\"))\n    //                                .or(named(\"redis.clients.jedis.commands.SentinelCommands\"))\n    //                                .or(named(\"redis.clients.jedis.commands.BinaryJedisCommands\"))\n    //                                .or(named(\"redis.clients.jedis.commands.MultiKeyBinaryCommands\"))\n    //                                .or(named(\"redis.clients.jedis.commands.AdvancedBinaryJedisCommands\"))\n    //                                .or(named(\"redis.clients.jedis.commands.BinaryScriptingCommands\"))\n    //                        ))\n    //                )).end();\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return ClassMatcher.builder().hasSuperClass(\"redis.clients.jedis.BinaryJedis\")\n            .build();\n    }\n\n    private IClassMatcher named(String name) {\n        return ClassMatcher.builder().hasClassName(name).isInterface()\n            .build();\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        IClassMatcher overriddenFrom = named(\"redis.clients.jedis.commands.JedisCommands\")\n            .or(named(\"redis.clients.jedis.JedisCommands\"))\n            .or(named(\"redis.clients.jedis.commands.AdvancedJedisCommands\"))\n            .or(named(\"redis.clients.jedis.commands.BasicCommands\"))\n            .or(named(\"redis.clients.jedis.commands.ClusterCommands\"))\n            .or(named(\"redis.clients.jedis.commands.ModuleCommands\"))\n            .or(named(\"redis.clients.jedis.commands.MultiKeyCommands\"))\n            .or(named(\"redis.clients.jedis.commands.ScriptingCommands\"))\n            .or(named(\"redis.clients.jedis.commands.SentinelCommands\"))\n            .or(named(\"redis.clients.jedis.commands.BinaryJedisCommands\"))\n            .or(named(\"redis.clients.jedis.commands.MultiKeyBinaryCommands\"))\n            .or(named(\"redis.clients.jedis.commands.AdvancedBinaryJedisCommands\"))\n            .or(named(\"redis.clients.jedis.commands.BinaryScriptingCommands\"));\n        return MethodMatcher.multiBuilder()\n            .match(MethodMatcher.builder().isOverriddenFrom(overriddenFrom).build())\n            .build();\n    }\n}\n"
  },
  {
    "path": "plugins/redis/src/main/java/com/megaease/easeagent/plugin/redis/advice/JedisConstructorAdvice.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.redis.advice;\n\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.Set;\n\nimport static com.megaease.easeagent.plugin.tools.matcher.ClassMatcherUtils.name;\nimport static com.megaease.easeagent.plugin.tools.matcher.MethodMatcherUtils.constructor;\n\npublic class JedisConstructorAdvice implements Points {\n\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return name(\"redis.clients.jedis.Jedis\");\n\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.multiBuilder()\n            .match(constructor())\n            .build();\n    }\n}\n"
  },
  {
    "path": "plugins/redis/src/main/java/com/megaease/easeagent/plugin/redis/advice/LettuceRedisClientAdvice.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.redis.advice;\n\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.Set;\n\nimport static com.megaease.easeagent.plugin.tools.matcher.MethodMatcherUtils.constructor;\n\npublic class LettuceRedisClientAdvice implements Points {\n    //return def.type(hasSuperType(named(\"io.lettuce.core.RedisClient\"))\n    //                .or(named(\"io.lettuce.core.RedisClient\"))\n    //        )\n    //                .transform(connectAsync((named(\"connectStandaloneAsync\")\n    //                                .or(named(\"connectPubSubAsync\"))\n    //                                .or(named(\"connectSentinelAsync\"))).and(isPrivate())\n    //                        )\n    //                )\n    //                .transform(objConstruct(isConstructor()))\n    //                .end()\n    //                ;\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return ClassMatcher.builder().hasSuperClass(\"io.lettuce.core.RedisClient\")\n            .build().or(ClassMatcher.builder().hasClassName(\"io.lettuce.core.RedisClient\")\n                .build());\n    }\n\n    private IMethodMatcher named(String name) {\n        return MethodMatcher.builder().named(name).isPrivate().build();\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.multiBuilder()\n            .match(named(\"connectStandaloneAsync\")\n                .or(named(\"connectPubSubAsync\"))\n                .or(named(\"connectSentinelAsync\")))\n            .match(constructor())\n            .build();\n    }\n}\n"
  },
  {
    "path": "plugins/redis/src/main/java/com/megaease/easeagent/plugin/redis/advice/RedisChannelWriterAdvice.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.redis.advice;\n\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.Set;\n\npublic class RedisChannelWriterAdvice implements Points {\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return ClassMatcher.builder().hasSuperClass(\"io.lettuce.core.RedisChannelWriter\").notInterface()\n            .build();\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.multiBuilder()\n            .match(MethodMatcher.builder().named(\"write\").argsLength(1).isPublic().build()\n                .and(MethodMatcher.builder().returnType(\"void\").build().negate()))\n            .build();\n    }\n\n    @Override\n    public boolean isAddDynamicField() {\n        return true;\n    }\n}\n"
  },
  {
    "path": "plugins/redis/src/main/java/com/megaease/easeagent/plugin/redis/advice/RedisClusterClientAdvice.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.redis.advice;\n\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.Set;\n\npublic class RedisClusterClientAdvice implements Points {\n    //return def.type(hasSuperType(named(\"io.lettuce.core.cluster.RedisClusterClient\"))\n    //                .or(named(\"io.lettuce.core.cluster.RedisClusterClient\"))\n    //        )\n    //                .transform(connectAsync((named(\"connectClusterAsync\")\n    //                                .or(named(\"connectClusterPubSubAsync\")))\n    //                                .and(isPrivate())\n    //                        )\n    //                )\n    //                .end()\n    //                ;\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return ClassMatcher.builder().hasSuperClass(\"io.lettuce.core.cluster.RedisClusterClient\")\n            .build().or(ClassMatcher.builder().hasClassName(\"io.lettuce.core.cluster.RedisClusterClient\")\n                .build());\n    }\n\n    private IMethodMatcher named(String name) {\n        return MethodMatcher.builder().named(name).isPrivate().build();\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.multiBuilder()\n            .match(named(\"connectClusterAsync\")\n                .or(named(\"connectClusterPubSubAsync\")))\n            .build();\n    }\n}\n"
  },
  {
    "path": "plugins/redis/src/main/java/com/megaease/easeagent/plugin/redis/advice/RedisPropertiesAdvice.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.redis.advice;\n\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.Set;\n\nimport static com.megaease.easeagent.plugin.tools.matcher.ClassMatcherUtils.name;\n\npublic class RedisPropertiesAdvice implements Points {\n    //return def\n    //            .type(named(\"org.springframework.boot.autoconfigure.data.redis.RedisProperties\"))\n    //            .transform(setProperty(nameStartsWith(\"set\")))\n    //            .end()\n    //            ;\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return name(\"org.springframework.boot.autoconfigure.data.redis.RedisProperties\");\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.multiBuilder()\n            .match(\n                MethodMatcher\n                    .builder()\n                    .nameStartWith(\"set\")\n                    .build()\n            )\n            .build();\n    }\n}\n"
  },
  {
    "path": "plugins/redis/src/main/java/com/megaease/easeagent/plugin/redis/advice/RedisPropertiesClusterAdvice.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.redis.advice;\n\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.Set;\n\nimport static com.megaease.easeagent.plugin.tools.matcher.ClassMatcherUtils.name;\n\npublic class RedisPropertiesClusterAdvice implements Points {\n\n    //return def\n    //            .type(named(\"org.springframework.boot.autoconfigure.data.redis.RedisProperties$Cluster\"))\n    //            .transform(setNodes(named(\"setNodes\")))\n    //            .end()\n    //            ;\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return name(\"org.springframework.boot.autoconfigure.data.redis.RedisProperties$Cluster\");\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.multiBuilder()\n            .match(\n                MethodMatcher\n                    .builder()\n                    .named(\"setNodes\")\n                    .build()\n            )\n            .build();\n    }\n}\n"
  },
  {
    "path": "plugins/redis/src/main/java/com/megaease/easeagent/plugin/redis/advice/StatefulRedisConnectionAdvice.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.redis.advice;\n\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.Set;\n\npublic class StatefulRedisConnectionAdvice implements Points {\n\n    //return def\n    //                .type((hasSuperType(named(\"io.lettuce.core.api.StatefulConnection\")\n    //                        .or(named(\"io.lettuce.core.sentinel.api.StatefulRedisSentinelConnection\"))\n    //                ).and(not(isInterface().or(isAbstract())))))\n    //                .transform(objConstruct(none(), AgentDynamicFieldAccessor.DYNAMIC_FIELD_NAME))\n    //                .end()\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return ClassMatcher.builder().hasSuperClass(\"io.lettuce.core.api.StatefulConnection\").notInterface().notAbstract()\n            .build().or(ClassMatcher.builder().hasSuperClass(\"io.lettuce.core.sentinel.api.StatefulRedisSentinelConnection\").notInterface().notAbstract()\n                .build());\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.multiBuilder()\n            .match(MethodMatcher.builder().named(\"<init>\")\n                .qualifier(\"constructor\")\n                .build())\n            .build();\n    }\n\n    @Override\n    public boolean isAddDynamicField() {\n        return true;\n    }\n}\n"
  },
  {
    "path": "plugins/redis/src/main/java/com/megaease/easeagent/plugin/redis/interceptor/RedisClassUtils.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.redis.interceptor;\n\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\nimport com.megaease.easeagent.plugin.utils.ClassUtils;\n\npublic class RedisClassUtils {\n    public static final HostAndPortTypeChecker HOST_AND_PORT_TYPE_CHECKER = new HostAndPortTypeChecker();\n    public static final JedisShardInfoTypeCheker JEDIS_SHARD_INFO_TYPE_CHEKER = new JedisShardInfoTypeCheker();\n    public static final JedisSocketFactoryTypeCheker JEDIS_SOCKET_FACTORY_TYPE_CHEKER = new JedisSocketFactoryTypeCheker();\n\n    public static class HostAndPortTypeChecker extends ClassUtils.TypeChecker {\n        public HostAndPortTypeChecker() {\n            super(\"redis.clients.jedis.HostAndPort\");\n        }\n\n        @Override\n        protected boolean isType(Object o) {\n            return o instanceof redis.clients.jedis.HostAndPort;\n        }\n\n        public Object newInstance(String host, int port) {\n            return new redis.clients.jedis.HostAndPort(host, port);\n        }\n    }\n\n    public static class JedisShardInfoTypeCheker extends ClassUtils.TypeChecker {\n\n        public JedisShardInfoTypeCheker() {\n            super(\"redis.clients.jedis.JedisShardInfo\");\n        }\n\n        @Override\n        protected boolean isType(Object o) {\n            return o instanceof redis.clients.jedis.JedisShardInfo;\n        }\n\n        public void setInfo(Object o, String host, int port, String password) {\n            AgentFieldReflectAccessor.setFieldValue(o, \"host\", host);\n            AgentFieldReflectAccessor.setFieldValue(o, \"port\", port);\n            if (password != null) {\n                ((redis.clients.jedis.JedisShardInfo) o).setPassword(password);\n            }\n\n        }\n    }\n\n    public static class JedisSocketFactoryTypeCheker extends ClassUtils.TypeChecker {\n\n        public JedisSocketFactoryTypeCheker() {\n            super(\"redis.clients.jedis.JedisSocketFactory\");\n        }\n\n        @Override\n        protected boolean isType(Object o) {\n            return o instanceof redis.clients.jedis.JedisSocketFactory;\n        }\n\n        public void setInfo(Object o, String host, int port) {\n            redis.clients.jedis.JedisSocketFactory jedisSocketFactory = (redis.clients.jedis.JedisSocketFactory) o;\n            jedisSocketFactory.setHost(host);\n            jedisSocketFactory.setPort(port);\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/redis/src/main/java/com/megaease/easeagent/plugin/redis/interceptor/RedisClientUtils.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.redis.interceptor;\n\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\nimport io.lettuce.core.RedisClient;\nimport io.lettuce.core.RedisURI;\nimport io.lettuce.core.cluster.RedisClusterClient;\nimport io.lettuce.core.protocol.RedisCommand;\n\nimport java.io.UnsupportedEncodingException;\nimport java.net.URLDecoder;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Collection;\nimport java.util.stream.Collectors;\n\npublic class RedisClientUtils {\n    private static final String REDIS_URI = \"redisURI\";\n    private static final String REDIS_URIS = \"initialUris\";\n\n    public static RedisURI getRedisURI(RedisClient redisClient, Object[] args) {\n        if (args == null) {\n            return AgentFieldReflectAccessor.getFieldValue(redisClient, REDIS_URI);\n        }\n        RedisURI redisURI = null;\n        for (Object arg : args) {\n            if (arg instanceof RedisURI) {\n                redisURI = (RedisURI) arg;\n                break;\n            }\n        }\n        if (redisURI == null) {\n            redisURI = AgentFieldReflectAccessor.getFieldValue(redisClient, REDIS_URI);\n        }\n        return redisURI;\n    }\n\n    public static Iterable<RedisURI> getRedisURIs(RedisClusterClient redisClusterClient) {\n        return AgentFieldReflectAccessor.getFieldValue(redisClusterClient, REDIS_URIS);\n    }\n\n    public static String toURI(RedisURI redisURI) {\n        try {\n            return URLDecoder.decode(redisURI.toURI().toString(), StandardCharsets.UTF_8.name());\n        } catch (UnsupportedEncodingException e) {\n            return redisURI.toURI().toString();\n        }\n    }\n\n    public static String cmd(Object arg0) {\n        String cmd;\n        if (arg0 instanceof RedisCommand) {\n            RedisCommand<?, ?, ?> redisCommand = (RedisCommand<?, ?, ?>) arg0;\n            cmd = redisCommand.getType().name();\n        } else if (arg0 instanceof Collection) {\n            @SuppressWarnings(\"unchecked\")\n            Collection<RedisCommand<?, ?, ?>> redisCommands = (Collection<RedisCommand<?, ?, ?>>) arg0;\n            cmd = \"[\" + String.join(\",\", redisCommands.stream().map(input -> input.getType().name()).collect(Collectors.toList())) + \"]\";\n        } else {\n            cmd = null;\n        }\n        return cmd;\n    }\n}\n"
  },
  {
    "path": "plugins/redis/src/main/java/com/megaease/easeagent/plugin/redis/interceptor/initialize/CommonRedisClientInterceptor.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.redis.interceptor.initialize;\n\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.field.AgentDynamicFieldAccessor;\nimport com.megaease.easeagent.plugin.interceptor.NonReentrantInterceptor;\nimport com.megaease.easeagent.plugin.redis.interceptor.RedisClientUtils;\nimport io.lettuce.core.ConnectionFuture;\nimport io.lettuce.core.RedisClient;\nimport io.lettuce.core.RedisURI;\nimport io.lettuce.core.cluster.RedisClusterClient;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.CompletableFuture;\n\nimport static com.megaease.easeagent.plugin.redis.interceptor.RedisClientUtils.toURI;\n\npublic class CommonRedisClientInterceptor implements NonReentrantInterceptor {\n    @Override\n    public void doAfter(MethodInfo methodInfo, Context context) {\n        if (!methodInfo.isSuccess()) {\n            return;\n        }\n        Object invoker = methodInfo.getInvoker();\n        if (invoker instanceof RedisClusterClient) {\n            this.processRedisClusterClient(methodInfo, context);\n        } else if (invoker instanceof RedisClient) {\n            this.processRedisClient(methodInfo, context);\n        }\n    }\n\n\n    public void processRedisClient(MethodInfo methodInfo, Context context) {\n        RedisURI redisURI = RedisClientUtils.getRedisURI((RedisClient) methodInfo.getInvoker(), methodInfo.getArgs());\n        if (redisURI != null) {\n            AgentDynamicFieldAccessor.setDynamicFieldValue(methodInfo.getRetValue(), toURI(redisURI));\n        }\n        Object ret = methodInfo.getRetValue();\n        if (ret instanceof ConnectionFuture) {\n            ConnectionFuture<?> future = (ConnectionFuture<?>) ret;\n            methodInfo.setRetValue(new ConnectionFutureWrapper<>(future, redisURI == null ? null : toURI(redisURI)));\n        }\n    }\n\n    public void processRedisClusterClient(MethodInfo methodInfo, Context context) {\n        Iterable<RedisURI> redisURIs = RedisClientUtils.getRedisURIs((RedisClusterClient) methodInfo.getInvoker());\n        Object ret = methodInfo.getRetValue();\n        if (redisURIs == null) {\n            return;\n        }\n        List<String> uriList = new ArrayList<>();\n        redisURIs.forEach(redisURI -> uriList.add(toURI(redisURI)));\n        String uriStr = String.join(\",\", uriList);\n        AgentDynamicFieldAccessor.setDynamicFieldValue(methodInfo.getRetValue(), uriStr);\n        if (ret instanceof CompletableFuture) {\n            CompletableFuture<?> future = (CompletableFuture<?>) ret;\n            methodInfo.setRetValue(new CompletableFutureWrapper<>(future, uriStr));\n        }\n    }\n\n    @Override\n    public int order() {\n        return Order.TRACING_INIT.getOrder();\n    }\n\n    @Override\n    public String getType() {\n        return Order.TRACING.getName();\n    }\n}\n"
  },
  {
    "path": "plugins/redis/src/main/java/com/megaease/easeagent/plugin/redis/interceptor/initialize/CompletableFutureWrapper.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.redis.interceptor.initialize;\n\nimport com.megaease.easeagent.plugin.field.AgentDynamicFieldAccessor;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\n\nimport java.util.concurrent.*;\nimport java.util.function.BiConsumer;\nimport java.util.function.BiFunction;\nimport java.util.function.Consumer;\nimport java.util.function.Function;\n\npublic class CompletableFutureWrapper<T> extends CompletableFuture<T> {\n\n    private final CompletableFuture<T> source;\n    private final Object attach;\n    private volatile boolean processed;\n\n    public CompletableFutureWrapper(CompletableFuture<T> source, Object attach) {\n        this.source = source;\n        this.attach = attach;\n    }\n\n    private T processResult(T t) {\n        return processResult(t, null, this.attach);\n    }\n\n    private T processResult(T t, Throwable throwable) {\n        return processResult(t, throwable, this.attach);\n    }\n\n    private void processException(Throwable throwable) {\n        processResult(null, throwable, this.attach);\n    }\n\n    private T processResult(T t, Throwable throwable, Object dynamicFieldValue) {\n        if (this.processed || t == null) {\n            return t;\n        }\n        obtrudeProcessResult(t, dynamicFieldValue);\n        this.processed = true;\n        return t;\n    }\n\n    private void obtrudeProcessResult(T t, Object dynamicFieldValue) {\n        AgentDynamicFieldAccessor.setDynamicFieldValue(t, dynamicFieldValue);\n        Object channelWriter = AgentFieldReflectAccessor.getFieldValue(t, \"channelWriter\");\n        if (channelWriter != null) {\n            AgentDynamicFieldAccessor.setDynamicFieldValue(channelWriter, dynamicFieldValue);\n        }\n    }\n\n    @Override\n    public boolean isDone() {\n        return source.isDone();\n    }\n\n    @Override\n    public T get() throws InterruptedException, ExecutionException {\n        T t = source.get();\n        return processResult(t);\n    }\n\n    @Override\n    public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {\n        try {\n            T t = source.get(timeout, unit);\n            return this.processResult(t);\n        } catch (InterruptedException | TimeoutException | ExecutionException e) {\n            this.processException(e);\n            throw e;\n        }\n    }\n\n    @Override\n    public T join() {\n        T t = source.join();\n        return processResult(t);\n    }\n\n    @Override\n    public T getNow(T valueIfAbsent) {\n        T t = source.getNow(valueIfAbsent);\n        return processResult(t);\n    }\n\n    @Override\n    public boolean complete(T value) {\n        boolean b = source.complete(value);\n        this.processResult(value);\n        return b;\n    }\n\n    @Override\n    public boolean completeExceptionally(Throwable ex) {\n        boolean b = source.completeExceptionally(ex);\n        this.processException(ex);\n        return b;\n    }\n\n    @Override\n    public <U> CompletableFuture<U> thenApply(Function<? super T, ? extends U> fn) {\n        return source.thenApply(t -> fn.apply(processResult(t)));\n    }\n\n    @Override\n    public <U> CompletableFuture<U> thenApplyAsync(Function<? super T, ? extends U> fn) {\n        return source.thenApplyAsync(t -> fn.apply(processResult(t)));\n    }\n\n    @Override\n    public <U> CompletableFuture<U> thenApplyAsync(Function<? super T, ? extends U> fn, Executor executor) {\n        return source.thenApplyAsync(t -> fn.apply(processResult(t)), executor);\n    }\n\n    @Override\n    public CompletableFuture<Void> thenAccept(Consumer<? super T> action) {\n        return source.thenAccept(t -> action.accept(processResult(t)));\n    }\n\n    @Override\n    public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action) {\n        return source.thenAcceptAsync(t -> action.accept(processResult(t)));\n    }\n\n    @Override\n    public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action, Executor executor) {\n        return source.thenAcceptAsync(t -> action.accept(processResult(t)), executor);\n    }\n\n    @Override\n    public CompletableFuture<Void> thenRun(Runnable action) {\n\n        return source.thenRun(() -> {\n            processResult(null);\n            action.run();\n        });\n    }\n\n    @Override\n    public CompletableFuture<Void> thenRunAsync(Runnable action) {\n        return source.thenRunAsync(() -> {\n            processResult(null);\n            action.run();\n        });\n    }\n\n    @Override\n    public CompletableFuture<Void> thenRunAsync(Runnable action, Executor executor) {\n        return source.thenRunAsync(() -> {\n            processResult(null);\n            action.run();\n        }, executor);\n    }\n\n    @Override\n    public <U, V> CompletableFuture<V> thenCombine(CompletionStage<? extends U> other, BiFunction<? super T, ? super U, ? extends V> fn) {\n        return source.thenCombine(other, (BiFunction<T, U, V>) (t, u) -> fn.apply(processResult(t), u));\n    }\n\n    @Override\n    public <U, V> CompletableFuture<V> thenCombineAsync(CompletionStage<? extends U> other, BiFunction<? super T, ? super U, ? extends V> fn) {\n        return source.thenCombineAsync(other, (BiFunction<T, U, V>) (t, u) -> fn.apply(processResult(t), u));\n    }\n\n    @Override\n    public <U, V> CompletableFuture<V> thenCombineAsync(CompletionStage<? extends U> other, BiFunction<? super T, ? super U, ? extends V> fn, Executor executor) {\n        return source.thenCombineAsync(other, (BiFunction<T, U, V>) (t, u) -> fn.apply(processResult(t), u), executor);\n    }\n\n    @Override\n    public <U> CompletableFuture<Void> thenAcceptBoth(CompletionStage<? extends U> other, BiConsumer<? super T, ? super U> action) {\n        return source.thenAcceptBoth(other, (BiConsumer<T, U>) (t, u) -> action.accept(processResult(t), u));\n    }\n\n    @Override\n    public <U> CompletableFuture<Void> thenAcceptBothAsync(CompletionStage<? extends U> other, BiConsumer<? super T, ? super U> action) {\n        return source.thenAcceptBothAsync(other, (BiConsumer<T, U>) (t, u) -> action.accept(processResult(t), u));\n    }\n\n    @Override\n    public <U> CompletableFuture<Void> thenAcceptBothAsync(CompletionStage<? extends U> other, BiConsumer<? super T, ? super U> action, Executor executor) {\n        return source.thenAcceptBothAsync(other, (BiConsumer<T, U>) (t, u) -> action.accept(processResult(t), u), executor);\n    }\n\n    @Override\n    public CompletableFuture<Void> runAfterBoth(CompletionStage<?> other, Runnable action) {\n        return source.runAfterBoth(other, () -> {\n            action.run();\n            processResult(null);\n        });\n    }\n\n    @Override\n    public CompletableFuture<Void> runAfterBothAsync(CompletionStage<?> other, Runnable action) {\n        return source.runAfterBothAsync(other, () -> {\n            action.run();\n            processResult(null);\n        });\n    }\n\n    @Override\n    public CompletableFuture<Void> runAfterBothAsync(CompletionStage<?> other, Runnable action, Executor executor) {\n        return source.runAfterBothAsync(other, () -> {\n            action.run();\n            processResult(null);\n        }, executor);\n    }\n\n    @Override\n    public <U> CompletableFuture<U> applyToEither(CompletionStage<? extends T> other, Function<? super T, U> fn) {\n\n        return source.applyToEither(other, t -> fn.apply(processResult(t)));\n    }\n\n    @Override\n    public <U> CompletableFuture<U> applyToEitherAsync(CompletionStage<? extends T> other, Function<? super T, U> fn) {\n        return source.applyToEitherAsync(other, t -> fn.apply(processResult(t)));\n    }\n\n    @Override\n    public <U> CompletableFuture<U> applyToEitherAsync(CompletionStage<? extends T> other, Function<? super T, U> fn, Executor executor) {\n        return source.applyToEitherAsync(other, t -> fn.apply(processResult(t)), executor);\n    }\n\n    @Override\n    public CompletableFuture<Void> acceptEither(CompletionStage<? extends T> other, Consumer<? super T> action) {\n        return source.acceptEither(other, t -> action.accept(processResult(t)));\n    }\n\n    @Override\n    public CompletableFuture<Void> acceptEitherAsync(CompletionStage<? extends T> other, Consumer<? super T> action) {\n        return source.acceptEitherAsync(other, t -> action.accept(processResult(t)));\n    }\n\n    @Override\n    public CompletableFuture<Void> acceptEitherAsync(CompletionStage<? extends T> other, Consumer<? super T> action, Executor executor) {\n        return source.acceptEitherAsync(other, t -> action.accept(processResult(t)), executor);\n    }\n\n    @Override\n    public CompletableFuture<Void> runAfterEither(CompletionStage<?> other, Runnable action) {\n        return source.runAfterEither(other, () -> {\n            processResult(null);\n            action.run();\n        });\n    }\n\n    @Override\n    public CompletableFuture<Void> runAfterEitherAsync(CompletionStage<?> other, Runnable action) {\n\n        return source.runAfterEitherAsync(other, () -> {\n            processResult(null);\n            action.run();\n        });\n    }\n\n    @Override\n    public CompletableFuture<Void> runAfterEitherAsync(CompletionStage<?> other, Runnable action, Executor executor) {\n\n        return source.runAfterEitherAsync(other, () -> {\n            processResult(null);\n            action.run();\n        }, executor);\n    }\n\n    @Override\n    public <U> CompletableFuture<U> thenCompose(Function<? super T, ? extends CompletionStage<U>> fn) {\n        CompletableFuture<U> u = source.thenCompose(t -> fn.apply(processResult(t)));\n        return new CompletableFutureWrapper<>(u, attach);\n    }\n\n    @Override\n    public <U> CompletableFuture<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn) {\n        return new CompletableFutureWrapper<>(source.thenComposeAsync(t -> fn.apply(processResult(t))), attach);\n    }\n\n    @Override\n    public <U> CompletableFuture<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn, Executor executor) {\n        return new CompletableFutureWrapper<>(source.thenComposeAsync(t -> fn.apply(processResult(t)), executor), attach);\n    }\n\n    @Override\n    public CompletableFuture<T> whenComplete(BiConsumer<? super T, ? super Throwable> action) {\n        return source.whenComplete((t, throwable) -> action.accept(processResult(t, throwable), throwable));\n    }\n\n    @Override\n    public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action) {\n        return source.whenCompleteAsync((t, throwable) -> action.accept(processResult(t, throwable), throwable));\n    }\n\n    @Override\n    public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action, Executor executor) {\n        return source.whenCompleteAsync((t, throwable) -> action.accept(processResult(t, throwable), throwable), executor);\n    }\n\n    @Override\n    public <U> CompletableFuture<U> handle(BiFunction<? super T, Throwable, ? extends U> fn) {\n        return source.handle((t, throwable) -> fn.apply(processResult(t, throwable), throwable));\n    }\n\n    @Override\n    public <U> CompletableFuture<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn) {\n\n        return source.handleAsync((t, throwable) -> fn.apply(processResult(t, throwable), throwable));\n    }\n\n    @Override\n    public <U> CompletableFuture<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn, Executor executor) {\n        return source.handleAsync((t, throwable) -> fn.apply(processResult(t, throwable), throwable), executor);\n    }\n\n    @Override\n    public CompletableFuture<T> toCompletableFuture() {\n        return source.toCompletableFuture();\n    }\n\n    @Override\n    public CompletableFuture<T> exceptionally(Function<Throwable, ? extends T> fn) {\n        return new CompletableFutureWrapper<>(source.exceptionally(throwable -> {\n            processException(throwable);\n            return fn.apply(throwable);\n        }), attach);\n    }\n\n    @Override\n    public boolean cancel(boolean mayInterruptIfRunning) {\n        boolean cancel = source.cancel(mayInterruptIfRunning);\n        this.processException(null);\n        return cancel;\n    }\n\n    @Override\n    public boolean isCancelled() {\n        return source.isCancelled();\n    }\n\n    @Override\n    public boolean isCompletedExceptionally() {\n        return source.isCompletedExceptionally();\n    }\n\n    @Override\n    public void obtrudeValue(T value) {\n        source.obtrudeValue(value);\n        this.obtrudeProcessResult(value, attach);\n    }\n\n    @Override\n    public void obtrudeException(Throwable ex) {\n        source.obtrudeException(ex);\n        this.processException(ex);\n    }\n\n    @Override\n    public int getNumberOfDependents() {\n        return source.getNumberOfDependents();\n    }\n\n    @Override\n    public String toString() {\n        return source.toString();\n    }\n}\n"
  },
  {
    "path": "plugins/redis/src/main/java/com/megaease/easeagent/plugin/redis/interceptor/initialize/ConnectionFutureWrapper.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.redis.interceptor.initialize;\n\nimport com.megaease.easeagent.plugin.field.AgentDynamicFieldAccessor;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\nimport io.lettuce.core.ConnectionFuture;\n\nimport javax.annotation.Nonnull;\nimport java.net.SocketAddress;\nimport java.util.concurrent.*;\nimport java.util.function.BiConsumer;\nimport java.util.function.BiFunction;\nimport java.util.function.Consumer;\nimport java.util.function.Function;\n\npublic class ConnectionFutureWrapper<T> implements ConnectionFuture<T> {\n\n    private final ConnectionFuture<T> source;\n    private final Object attach;\n    private volatile boolean processed;\n\n    public ConnectionFutureWrapper(ConnectionFuture<T> source, Object attach) {\n        this.source = source;\n        this.attach = attach;\n    }\n\n    private T processResult(T t) {\n        return processResult(t, null, this.attach);\n    }\n\n    private T processResult(T t, Throwable throwable) {\n        return processResult(t, throwable, this.attach);\n    }\n\n    private void processException(Throwable throwable) {\n        processResult(null, throwable, this.attach);\n    }\n\n    private T processResult(T t, Throwable throwable, Object dynamicFieldValue) {\n        if (this.processed || t == null) {\n            return t;\n        }\n        AgentDynamicFieldAccessor.setDynamicFieldValue(t, dynamicFieldValue);\n        Object channelWriter = AgentFieldReflectAccessor.getFieldValue(t, \"channelWriter\");\n        if (channelWriter != null) {\n            AgentDynamicFieldAccessor.setDynamicFieldValue(channelWriter, dynamicFieldValue);\n        }\n        this.processed = true;\n        return t;\n    }\n\n\n    @Override\n    public boolean cancel(boolean mayInterruptIfRunning) {\n        return source.cancel(mayInterruptIfRunning);\n    }\n\n    @Override\n    public boolean isCancelled() {\n        return source.isCancelled();\n    }\n\n    @Override\n    public boolean isDone() {\n        return source.isDone();\n    }\n\n    @Override\n    public T get() throws InterruptedException, ExecutionException {\n        T t = source.get();\n        return this.processResult(t);\n    }\n\n\n    @Override\n    public T get(long timeout, @Nonnull TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {\n        try {\n            T t = source.get(timeout, unit);\n            return this.processResult(t);\n        } catch (InterruptedException | TimeoutException | ExecutionException e) {\n            this.processException(e);\n            throw e;\n        }\n    }\n\n    @Override\n    public SocketAddress getRemoteAddress() {\n        return source.getRemoteAddress();\n    }\n\n    @Override\n    public T join() {\n        T t = source.join();\n        return this.processResult(t);\n    }\n\n    @Override\n    public <U> ConnectionFuture<U> thenApply(Function<? super T, ? extends U> fn) {\n        return source.thenApply(t -> fn.apply(processResult(t)));\n    }\n\n    @Override\n    public <U> ConnectionFuture<U> thenApplyAsync(Function<? super T, ? extends U> fn) {\n        return source.thenApplyAsync(t -> fn.apply(processResult(t)));\n    }\n\n    @Override\n    public <U> ConnectionFuture<U> thenApplyAsync(Function<? super T, ? extends U> fn, Executor executor) {\n        return source.thenApplyAsync(t -> fn.apply(processResult(t)), executor);\n    }\n\n    @Override\n    public ConnectionFuture<Void> thenAccept(Consumer<? super T> action) {\n        return source.thenAccept(t -> action.accept(processResult(t)));\n    }\n\n    @Override\n    public ConnectionFuture<Void> thenAcceptAsync(Consumer<? super T> action) {\n        return source.thenAcceptAsync(t -> action.accept(processResult(t)));\n    }\n\n    @Override\n    public ConnectionFuture<Void> thenAcceptAsync(Consumer<? super T> action, Executor executor) {\n        return source.thenAcceptAsync(t -> action.accept(processResult(t)), executor);\n    }\n\n    @Override\n    public ConnectionFuture<Void> thenRun(Runnable action) {\n        return source.thenRun(() -> {\n            processResult(null);\n            action.run();\n        });\n    }\n\n    @Override\n    public ConnectionFuture<Void> thenRunAsync(Runnable action) {\n        return source.thenRunAsync(() -> {\n            processResult(null);\n            action.run();\n        });\n    }\n\n    @Override\n    public ConnectionFuture<Void> thenRunAsync(Runnable action, Executor executor) {\n        return source.thenRunAsync(() -> {\n            processResult(null);\n            action.run();\n        }, executor);\n    }\n\n    @Override\n    public <U, V> ConnectionFuture<V> thenCombine(CompletionStage<? extends U> other, BiFunction<? super T, ? super U, ? extends V> fn) {\n        return source.thenCombine(other, (BiFunction<T, U, V>) (t, u) -> fn.apply(processResult(t), u));\n    }\n\n    @Override\n    public <U, V> ConnectionFuture<V> thenCombineAsync(CompletionStage<? extends U> other, BiFunction<? super T, ? super U, ? extends V> fn) {\n        return source.thenCombineAsync(other, (BiFunction<T, U, V>) (t, u) -> fn.apply(processResult(t), u));\n    }\n\n    @Override\n    public <U, V> ConnectionFuture<V> thenCombineAsync(CompletionStage<? extends U> other, BiFunction<? super T, ? super U, ? extends V> fn, Executor executor) {\n        return source.thenCombineAsync(other, (BiFunction<T, U, V>) (t, u) -> fn.apply(processResult(t), u), executor);\n    }\n\n    @Override\n    public <U> ConnectionFuture<Void> thenAcceptBoth(CompletionStage<? extends U> other, BiConsumer<? super T, ? super U> action) {\n        return source.thenAcceptBoth(other, (BiConsumer<T, U>) (t, u) -> action.accept(processResult(t), u));\n    }\n\n    @Override\n    public <U> ConnectionFuture<Void> thenAcceptBothAsync(CompletionStage<? extends U> other, BiConsumer<? super T, ? super U> action) {\n        return source.thenAcceptBothAsync(other, (BiConsumer<T, U>) (t, u) -> action.accept(processResult(t), u));\n    }\n\n    @Override\n    public <U> ConnectionFuture<Void> thenAcceptBothAsync(CompletionStage<? extends U> other, BiConsumer<? super T, ? super U> action, Executor executor) {\n        return source.thenAcceptBothAsync(other, (BiConsumer<T, U>) (t, u) -> action.accept(processResult(t), u), executor);\n    }\n\n    @Override\n    public ConnectionFuture<Void> runAfterBoth(CompletionStage<?> other, Runnable action) {\n        return source.runAfterBoth(other, () -> {\n            processResult(null);\n            action.run();\n        });\n    }\n\n    @Override\n    public ConnectionFuture<Void> runAfterBothAsync(CompletionStage<?> other, Runnable action) {\n        return source.runAfterBothAsync(other, () -> {\n            processResult(null);\n            action.run();\n        });\n    }\n\n    @Override\n    public ConnectionFuture<Void> runAfterBothAsync(CompletionStage<?> other, Runnable action, Executor executor) {\n        return source.runAfterBothAsync(other, () -> {\n            processResult(null);\n            action.run();\n        }, executor);\n    }\n\n    @Override\n    public <U> ConnectionFuture<U> applyToEither(CompletionStage<? extends T> other, Function<? super T, U> fn) {\n        return source.applyToEither(other, t -> fn.apply(processResult(t)));\n    }\n\n    @Override\n    public <U> ConnectionFuture<U> applyToEitherAsync(CompletionStage<? extends T> other, Function<? super T, U> fn) {\n        return source.applyToEitherAsync(other, t -> fn.apply(processResult(t)));\n    }\n\n    @Override\n    public <U> ConnectionFuture<U> applyToEitherAsync(CompletionStage<? extends T> other, Function<? super T, U> fn, Executor executor) {\n        return source.applyToEitherAsync(other, t -> fn.apply(processResult(t)), executor);\n    }\n\n    @Override\n    public ConnectionFuture<Void> acceptEither(CompletionStage<? extends T> other, Consumer<? super T> action) {\n        return source.acceptEither(other, t -> action.accept(processResult(t)));\n    }\n\n    @Override\n    public ConnectionFuture<Void> acceptEitherAsync(CompletionStage<? extends T> other, Consumer<? super T> action) {\n        return source.acceptEitherAsync(other, t -> action.accept(processResult(t)));\n    }\n\n    @Override\n    public ConnectionFuture<Void> acceptEitherAsync(CompletionStage<? extends T> other, Consumer<? super T> action, Executor executor) {\n        return source.acceptEitherAsync(other, t -> action.accept(processResult(t)), executor);\n    }\n\n    @Override\n    public ConnectionFuture<Void> runAfterEither(CompletionStage<?> other, Runnable action) {\n        return source.runAfterEither(other, () -> {\n            processResult(null);\n            action.run();\n        });\n    }\n\n    @Override\n    public ConnectionFuture<Void> runAfterEitherAsync(CompletionStage<?> other, Runnable action) {\n        return source.runAfterEitherAsync(other, () -> {\n            processResult(null);\n            action.run();\n        });\n    }\n\n    @Override\n    public ConnectionFuture<Void> runAfterEitherAsync(CompletionStage<?> other, Runnable action, Executor executor) {\n        return source.runAfterEitherAsync(other, () -> {\n            processResult(null);\n            action.run();\n        }, executor);\n    }\n\n    @Override\n    public <U> ConnectionFuture<U> thenCompose(Function<? super T, ? extends CompletionStage<U>> fn) {\n        return source.thenCompose(t -> fn.apply(processResult(t)));\n    }\n\n    @Override\n    public <U> ConnectionFuture<U> thenCompose(BiFunction<? super T, ? super Throwable, ? extends CompletionStage<U>> fn) {\n        return source.thenCompose((t, throwable) -> fn.apply(processResult(t), throwable));\n    }\n\n    @Override\n    public <U> ConnectionFuture<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn) {\n        return source.thenComposeAsync(t -> fn.apply(processResult(t)));\n    }\n\n    @Override\n    public <U> ConnectionFuture<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn, Executor executor) {\n        return source.thenComposeAsync(t -> fn.apply(processResult(t)), executor);\n    }\n\n    @Override\n    public ConnectionFuture<T> exceptionally(Function<Throwable, ? extends T> fn) {\n        return source.exceptionally(throwable -> {\n            processException(throwable);\n            return fn.apply(throwable);\n        });\n    }\n\n\n    @Override\n    public ConnectionFuture<T> whenComplete(BiConsumer<? super T, ? super Throwable> action) {\n        return source.whenComplete((t, throwable) -> action.accept(processResult(t, throwable), throwable));\n    }\n\n    @Override\n    public ConnectionFuture<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action) {\n        return source.whenCompleteAsync((t, throwable) -> action.accept(processResult(t, throwable), throwable));\n    }\n\n    @Override\n    public ConnectionFuture<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action, Executor executor) {\n        return source.whenCompleteAsync((t, throwable) -> action.accept(processResult(t, throwable), throwable), executor);\n    }\n\n    @Override\n    public <U> ConnectionFuture<U> handle(BiFunction<? super T, Throwable, ? extends U> fn) {\n        return source.handle((t, throwable) -> fn.apply(processResult(t, throwable), throwable));\n    }\n\n    @Override\n    public <U> ConnectionFuture<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn) {\n        return source.handleAsync((t, throwable) -> fn.apply(processResult(t, throwable), throwable));\n    }\n\n    @Override\n    public <U> ConnectionFuture<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn, Executor executor) {\n        return source.handleAsync((t, throwable) -> fn.apply(processResult(t, throwable), throwable), executor);\n    }\n\n    @Override\n    public CompletableFuture<T> toCompletableFuture() {\n        return source.toCompletableFuture();\n    }\n}\n"
  },
  {
    "path": "plugins/redis/src/main/java/com/megaease/easeagent/plugin/redis/interceptor/initialize/RedisClientInterceptor.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.redis.interceptor.initialize;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.redis.RedisPlugin;\nimport com.megaease.easeagent.plugin.redis.advice.LettuceRedisClientAdvice;\n\n@AdviceTo(value = LettuceRedisClientAdvice.class, qualifier = \"default\", plugin = RedisPlugin.class)\npublic class RedisClientInterceptor extends CommonRedisClientInterceptor {\n}\n"
  },
  {
    "path": "plugins/redis/src/main/java/com/megaease/easeagent/plugin/redis/interceptor/initialize/RedisClusterClientInterceptor.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.redis.interceptor.initialize;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.redis.RedisPlugin;\nimport com.megaease.easeagent.plugin.redis.advice.RedisClusterClientAdvice;\n\n@AdviceTo(value = RedisClusterClientAdvice.class, qualifier = \"default\", plugin = RedisPlugin.class)\npublic class RedisClusterClientInterceptor extends CommonRedisClientInterceptor {\n}\n"
  },
  {
    "path": "plugins/redis/src/main/java/com/megaease/easeagent/plugin/redis/interceptor/metric/CommonRedisMetricInterceptor.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.redis.interceptor.metric;\n\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.metric.ServiceMetricRegistry;\nimport com.megaease.easeagent.plugin.api.metric.name.Tags;\nimport com.megaease.easeagent.plugin.api.middleware.Redirect;\nimport com.megaease.easeagent.plugin.api.middleware.RedirectProcessor;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.NonReentrantInterceptor;\nimport com.megaease.easeagent.plugin.tools.metrics.RedisMetric;\n\npublic abstract class CommonRedisMetricInterceptor implements NonReentrantInterceptor {\n    private static volatile RedisMetric REDIS_METRIC = null;\n    private static final Object ENTER = new Object();\n    private static final Object START = new Object();\n\n    @Override\n    public void init(IPluginConfig config, String className, String methodName, String methodDescriptor) {\n        Tags tags = new Tags(\"application\", \"cache-redis\", \"signature\");\n        RedirectProcessor.setTagsIfRedirected(Redirect.REDIS, tags);\n        REDIS_METRIC = ServiceMetricRegistry.getOrCreate(config, tags, RedisMetric.REDIS_METRIC_SUPPLIER);\n    }\n\n    @Override\n    public void doBefore(MethodInfo methodInfo, Context context) {\n        context.put(START, System.currentTimeMillis());\n    }\n\n    @Override\n    public void doAfter(MethodInfo methodInfo, Context context) {\n        String key = this.getKey(methodInfo, context);\n        long start = context.remove(START);\n        REDIS_METRIC.collect(key, System.currentTimeMillis() - start, methodInfo.isSuccess());\n    }\n\n    @Override\n    public String getType() {\n        return Order.METRIC.getName();\n    }\n\n    @Override\n    public int order() {\n        return Order.METRIC.getOrder();\n    }\n\n    @Override\n    public Object getEnterKey(MethodInfo methodInfo, Context context) {\n        return ENTER;\n    }\n\n    public abstract String getKey(MethodInfo methodInfo, Context context);\n\n}\n"
  },
  {
    "path": "plugins/redis/src/main/java/com/megaease/easeagent/plugin/redis/interceptor/metric/JedisMetricInterceptor.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.redis.interceptor.metric;\n\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.redis.RedisPlugin;\nimport com.megaease.easeagent.plugin.redis.advice.JedisAdvice;\n\n@AdviceTo(value = JedisAdvice.class, qualifier = \"default\", plugin = RedisPlugin.class)\npublic class JedisMetricInterceptor extends CommonRedisMetricInterceptor {\n    @Override\n    public String getKey(MethodInfo methodInfo, Context context) {\n        return methodInfo.getMethod();\n    }\n}\n"
  },
  {
    "path": "plugins/redis/src/main/java/com/megaease/easeagent/plugin/redis/interceptor/metric/LettuceMetricInterceptor.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.redis.interceptor.metric;\n\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.redis.RedisPlugin;\nimport com.megaease.easeagent.plugin.redis.advice.RedisChannelWriterAdvice;\nimport com.megaease.easeagent.plugin.redis.interceptor.RedisClientUtils;\n\n@AdviceTo(value = RedisChannelWriterAdvice.class, qualifier = \"default\", plugin = RedisPlugin.class)\npublic class LettuceMetricInterceptor extends CommonRedisMetricInterceptor {\n    @Override\n    public String getKey(MethodInfo methodInfo, Context context) {\n        return RedisClientUtils.cmd(methodInfo.getArgs()[0]);\n    }\n}\n"
  },
  {
    "path": "plugins/redis/src/main/java/com/megaease/easeagent/plugin/redis/interceptor/redirect/JedisConstructorInterceptor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.redis.interceptor.redirect;\n\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.logging.Logger;\nimport com.megaease.easeagent.plugin.api.middleware.Redirect;\nimport com.megaease.easeagent.plugin.api.middleware.RedirectProcessor;\nimport com.megaease.easeagent.plugin.api.middleware.ResourceConfig;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\nimport com.megaease.easeagent.plugin.redis.RedisRedirectPlugin;\nimport com.megaease.easeagent.plugin.redis.advice.JedisConstructorAdvice;\nimport com.megaease.easeagent.plugin.redis.interceptor.RedisClassUtils;\n\nimport java.net.URI;\n\n@AdviceTo(value = JedisConstructorAdvice.class, qualifier = \"constructor\", plugin = RedisRedirectPlugin.class)\npublic class JedisConstructorInterceptor implements Interceptor {\n    private static final Logger LOGGER = EaseAgent.getLogger(JedisConstructorInterceptor.class);\n\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n        if (methodInfo.argSize() == 0) {\n            return;\n        }\n        ResourceConfig cnf = Redirect.REDIS.getConfig();\n        if (cnf == null) {\n            return;\n        }\n        Object arg0 = methodInfo.getArgs()[0];\n        ResourceConfig.HostAndPort hostAndPort = cnf.getFirstHostAndPort();\n        String host = hostAndPort.getHost();\n        Integer port = hostAndPort.getPort();\n        String password = cnf.getPassword();\n        if (host == null || port == null) {\n            return;\n        }\n        if (arg0 instanceof String) {\n            LOGGER.info(\"Redirect Redis host: {} to {}\", arg0, host);\n            methodInfo.changeArg(0, host);\n            RedirectProcessor.redirected(Redirect.REDIS, hostAndPort.uri());\n        } else if (arg0 instanceof URI) {\n            LOGGER.info(\"Redirect Redis URI {} to {}:{}\", arg0, host, port);\n            AgentFieldReflectAccessor.setFieldValue(arg0, \"host\", host);\n            AgentFieldReflectAccessor.setFieldValue(arg0, \"port\", port);\n            methodInfo.changeArg(0, arg0);\n            RedirectProcessor.redirected(Redirect.REDIS, hostAndPort.uri());\n            return;\n        } else if (RedisClassUtils.HOST_AND_PORT_TYPE_CHECKER.hasClassAndIsType(arg0)) {\n            Object newHostAndPort = RedisClassUtils.HOST_AND_PORT_TYPE_CHECKER.newInstance(host, port);\n            LOGGER.info(\"Redirect Redis HostAndPort {} to {}\", arg0, newHostAndPort);\n            methodInfo.changeArg(0, newHostAndPort);\n            RedirectProcessor.redirected(Redirect.REDIS, hostAndPort.uri());\n            return;\n        } else if (RedisClassUtils.JEDIS_SHARD_INFO_TYPE_CHEKER.hasClassAndIsType(arg0)) {\n            RedisClassUtils.JEDIS_SHARD_INFO_TYPE_CHEKER.setInfo(arg0, host, port, password);\n            LOGGER.info(\"Redirect Redis JedisShardInfo to {}\", arg0);\n            methodInfo.changeArg(0, arg0);\n            RedirectProcessor.redirected(Redirect.REDIS, hostAndPort.uri());\n            return;\n        } else if (RedisClassUtils.JEDIS_SOCKET_FACTORY_TYPE_CHEKER.hasClassAndIsType(arg0)) {\n            RedisClassUtils.JEDIS_SOCKET_FACTORY_TYPE_CHEKER.setInfo(arg0, host, port);\n            LOGGER.info(\"Redirect Redis JedisSocketFactory to {}\", arg0);\n            methodInfo.changeArg(0, arg0);\n            RedirectProcessor.redirected(Redirect.REDIS, hostAndPort.uri());\n            return;\n        }\n        if (methodInfo.argSize() > 1 && methodInfo.getArgs()[1] instanceof Integer) {\n            LOGGER.info(\"Redirect Redis port {} to {}\", methodInfo.getArgs()[1], port);\n            methodInfo.changeArg(1, port);\n        }\n    }\n\n\n    @Override\n    public String getType() {\n        return Order.REDIRECT.getName();\n    }\n\n    @Override\n    public int order() {\n        return Order.REDIRECT.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/redis/src/main/java/com/megaease/easeagent/plugin/redis/interceptor/redirect/LettuceRedisClientConstructInterceptor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.redis.interceptor.redirect;\n\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.logging.Logger;\nimport com.megaease.easeagent.plugin.api.middleware.Redirect;\nimport com.megaease.easeagent.plugin.api.middleware.RedirectProcessor;\nimport com.megaease.easeagent.plugin.api.middleware.ResourceConfig;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.NonReentrantInterceptor;\nimport com.megaease.easeagent.plugin.redis.RedisRedirectPlugin;\nimport com.megaease.easeagent.plugin.redis.advice.LettuceRedisClientAdvice;\nimport com.megaease.easeagent.plugin.redis.interceptor.RedisClientUtils;\nimport io.lettuce.core.RedisClient;\nimport io.lettuce.core.RedisURI;\n\n@AdviceTo(value = LettuceRedisClientAdvice.class, qualifier = \"constructor\", plugin = RedisRedirectPlugin.class)\npublic class LettuceRedisClientConstructInterceptor implements NonReentrantInterceptor {\n    private static final Logger LOGGER = EaseAgent.getLogger(LettuceRedisClientConstructInterceptor.class);\n\n    @Override\n    public void doAfter(MethodInfo methodInfo, Context context) {\n        ResourceConfig cnf = Redirect.REDIS.getConfig();\n        if (cnf == null) {\n            return;\n        }\n        RedisClient redisClient = (RedisClient) methodInfo.getInvoker();\n        RedisURI redisURI = RedisClientUtils.getRedisURI(redisClient, null);\n        ResourceConfig.HostAndPort hostAndPort = cnf.getFirstHostAndPort();\n        String host = hostAndPort.getHost();\n        Integer port = hostAndPort.getPort();\n        if (host != null && port != null) {\n            LOGGER.info(\"Redirect Redis RedisURI: {} to {}:{}\", redisURI, host, port);\n            redisURI.setHost(host);\n            redisURI.setPort(port);\n            if (cnf.getPassword() != null) {\n                redisURI.setPassword(cnf.getPassword());\n            }\n            RedirectProcessor.redirected(Redirect.REDIS, hostAndPort.uri());\n        }\n    }\n\n    @Override\n    public String getType() {\n        return Order.REDIRECT.getName();\n    }\n\n    @Override\n    public int order() {\n        return Order.REDIRECT.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/redis/src/main/java/com/megaease/easeagent/plugin/redis/interceptor/redirect/RedisPropertiesClusterSetNodesInterceptor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.redis.interceptor.redirect;\n\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.logging.Logger;\nimport com.megaease.easeagent.plugin.api.middleware.Redirect;\nimport com.megaease.easeagent.plugin.api.middleware.RedirectProcessor;\nimport com.megaease.easeagent.plugin.api.middleware.ResourceConfig;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.redis.RedisRedirectPlugin;\nimport com.megaease.easeagent.plugin.redis.advice.RedisPropertiesClusterAdvice;\n\n@AdviceTo(value = RedisPropertiesClusterAdvice.class, plugin = RedisRedirectPlugin.class)\npublic class RedisPropertiesClusterSetNodesInterceptor implements Interceptor {\n    private static final Logger LOGGER = EaseAgent.getLogger(RedisPropertiesClusterSetNodesInterceptor.class);\n\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n        ResourceConfig cnf = Redirect.REDIS.getConfig();\n        if (cnf == null) {\n            return;\n        }\n        LOGGER.info(\"Redirect Redis uris {} to {}\", methodInfo.getArgs()[0], cnf.getUris());\n        methodInfo.changeArg(0, cnf.getUris());\n        RedirectProcessor.redirected(Redirect.REDIS, cnf.getUris());\n    }\n\n    @Override\n    public String getType() {\n        return Order.REDIRECT.getName();\n    }\n\n    @Override\n    public int order() {\n        return Order.REDIRECT.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/redis/src/main/java/com/megaease/easeagent/plugin/redis/interceptor/redirect/RedisPropertiesSetPropertyInterceptor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.redis.interceptor.redirect;\n\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.logging.Logger;\nimport com.megaease.easeagent.plugin.api.middleware.Redirect;\nimport com.megaease.easeagent.plugin.api.middleware.RedirectProcessor;\nimport com.megaease.easeagent.plugin.api.middleware.ResourceConfig;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.redis.RedisRedirectPlugin;\nimport com.megaease.easeagent.plugin.redis.advice.RedisPropertiesAdvice;\nimport com.megaease.easeagent.plugin.utils.common.StringUtils;\n\n@AdviceTo(value = RedisPropertiesAdvice.class, plugin = RedisRedirectPlugin.class)\npublic class RedisPropertiesSetPropertyInterceptor implements Interceptor {\n    private static final Logger LOGGER = EaseAgent.getLogger(RedisPropertiesSetPropertyInterceptor.class);\n\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n        ResourceConfig cnf = Redirect.REDIS.getConfig();\n        if (cnf == null) {\n            return;\n        }\n        String method = methodInfo.getMethod();\n        ResourceConfig.HostAndPort hostAndPort = cnf.getFirstHostAndPort();\n        String host = hostAndPort.getHost();\n        Integer port = hostAndPort.getPort();\n        if (method.equals(\"setHost\") && host != null) {\n            LOGGER.info(\"Redirect Redis host {} to {}\", methodInfo.getArgs()[0], host);\n            methodInfo.changeArg(0, host);\n            RedirectProcessor.redirected(Redirect.REDIS, hostAndPort.uri());\n        } else if (method.equals(\"setPort\") && port != null) {\n            LOGGER.info(\"Redirect Redis port {} to {}\", methodInfo.getArgs()[0], port);\n            methodInfo.changeArg(0, port);\n        } else if (method.equals(\"setPassword\") && StringUtils.isNotEmpty(cnf.getPassword())) {\n            LOGGER.info(\"Redirect Redis Password *** to ***\");\n            methodInfo.changeArg(0, cnf.getPassword());\n        }\n    }\n\n    @Override\n    public String getType() {\n        return Order.REDIRECT.getName();\n    }\n\n    @Override\n    public int order() {\n        return Order.REDIRECT.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/redis/src/main/java/com/megaease/easeagent/plugin/redis/interceptor/tracing/CommonRedisTracingInterceptor.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.redis.interceptor.tracing;\n\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.middleware.MiddlewareConstants;\nimport com.megaease.easeagent.plugin.api.middleware.Redirect;\nimport com.megaease.easeagent.plugin.api.middleware.RedirectProcessor;\nimport com.megaease.easeagent.plugin.api.middleware.Type;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.interceptor.NonReentrantInterceptor;\n\npublic abstract class CommonRedisTracingInterceptor implements NonReentrantInterceptor {\n    private static final Object ENTER = new Object();\n    private static final Object SPAN_KEY = new Object();\n\n\n    @Override\n    public void doBefore(MethodInfo methodInfo, Context context) {\n        Span currentSpan = context.currentTracing().currentSpan();\n        if (currentSpan.isNoop()) {\n            return;\n        }\n        doTraceBefore(methodInfo, context);\n    }\n\n    @Override\n    public Object getEnterKey(MethodInfo methodInfo, Context context) {\n        return ENTER;\n    }\n\n    @Override\n    public void doAfter(MethodInfo methodInfo, Context context) {\n        this.finishTracing(methodInfo.getThrowable(), context);\n    }\n\n    @Override\n    public int order() {\n        return Order.TRACING.getOrder();\n    }\n\n    public abstract void doTraceBefore(MethodInfo methodInfo, Context context);\n\n    protected void startTracing(Context context, String name, String uri, String cmd) {\n        Span span = context.nextSpan().name(name).start();\n        span.kind(Span.Kind.CLIENT);\n        span.remoteServiceName(\"redis\");\n        context.put(SPAN_KEY, span);\n        if (cmd != null) {\n            span.tag(\"redis.method\", cmd);\n        }\n        span.tag(MiddlewareConstants.TYPE_TAG_NAME, Type.REDIS.getRemoteType());\n        RedirectProcessor.setTagsIfRedirected(Redirect.REDIS, span);\n    }\n\n    protected void finishTracing(Throwable throwable, Context context) {\n        try {\n            Span span = context.get(SPAN_KEY);\n            if (span == null) {\n                return;\n            }\n            if (throwable != null) {\n                span.error(throwable);\n            }\n            span.finish();\n            context.remove(SPAN_KEY);\n        } catch (Exception ignored) {\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/redis/src/main/java/com/megaease/easeagent/plugin/redis/interceptor/tracing/JedisTracingInterceptor.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.redis.interceptor.tracing;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.redis.RedisPlugin;\nimport com.megaease.easeagent.plugin.redis.advice.JedisAdvice;\n\n@AdviceTo(value = JedisAdvice.class, qualifier = \"default\", plugin = RedisPlugin.class)\npublic class JedisTracingInterceptor extends CommonRedisTracingInterceptor {\n\n    @Override\n    public void doTraceBefore(MethodInfo methodInfo, Context context) {\n        Object invoker = methodInfo.getInvoker();\n        String name = invoker.getClass().getSimpleName() + \".\" + methodInfo.getMethod();\n        String cmd = methodInfo.getMethod();\n        this.startTracing(context, name, null, cmd);\n    }\n}\n"
  },
  {
    "path": "plugins/redis/src/main/java/com/megaease/easeagent/plugin/redis/interceptor/tracing/LettuceTracingInterceptor.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.redis.interceptor.tracing;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.redis.RedisPlugin;\nimport com.megaease.easeagent.plugin.redis.advice.RedisChannelWriterAdvice;\nimport com.megaease.easeagent.plugin.redis.interceptor.RedisClientUtils;\n\n@AdviceTo(value = RedisChannelWriterAdvice.class, qualifier = \"default\", plugin = RedisPlugin.class)\npublic class LettuceTracingInterceptor extends CommonRedisTracingInterceptor {\n    @Override\n    public void doTraceBefore(MethodInfo methodInfo, Context context) {\n        String cmd = RedisClientUtils.cmd(methodInfo.getArgs()[0]);\n        this.startTracing(context, cmd, null, cmd);\n    }\n}\n"
  },
  {
    "path": "plugins/redis/src/main/java/com/megaease/easeagent/plugin/redis/interceptor/tracing/StatefulRedisConnectionInterceptor.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.redis.interceptor.tracing;\n\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.redis.RedisPlugin;\nimport com.megaease.easeagent.plugin.redis.advice.StatefulRedisConnectionAdvice;\n\n@AdviceTo(value = StatefulRedisConnectionAdvice.class, qualifier = \"constructor\", plugin = RedisPlugin.class)\npublic class StatefulRedisConnectionInterceptor implements Interceptor {\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n    }\n\n    @Override\n    public int order() {\n        return Order.TRACING.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/redis/src/test/java/com/megaease/easeagent/plugin/redis/interceptor/RedisUtils.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.redis.interceptor;\n\nimport com.megaease.easeagent.mock.utils.MockSystemEnv;\nimport com.megaease.easeagent.plugin.api.middleware.MiddlewareConstants;\nimport com.megaease.easeagent.plugin.api.middleware.Redirect;\nimport com.megaease.easeagent.plugin.api.middleware.ResourceConfig;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\n\npublic class RedisUtils {\n    public static void mockRedirect(Runnable r) {\n        ResourceConfig oldConfig = Redirect.REDIS.getConfig();\n        try {\n            MockSystemEnv.set(MiddlewareConstants.ENV_REDIS, String.format(\"{\\\"uris\\\":\\\"%s:%s\\\", \\\"password\\\": \\\"%s\\\"}\", TestConst.REDIRECT_HOST, TestConst.REDIRECT_PORT, TestConst.REDIRECT_PASSWORD));\n            AgentFieldReflectAccessor.setFieldValue(Redirect.REDIS, \"config\", ResourceConfig.getResourceConfig(Redirect.REDIS.getEnv(), Redirect.REDIS.isNeedParse()));\n            r.run();\n        } finally {\n            AgentFieldReflectAccessor.setFieldValue(Redirect.REDIS, \"config\", oldConfig);\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/redis/src/test/java/com/megaease/easeagent/plugin/redis/interceptor/TestConst.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.redis.interceptor;\n\npublic class TestConst {\n    public static final String REDIRECT_HOST = \"192.168.0.12\";\n    public static final int REDIRECT_PORT = 16379;\n    public static final String REDIRECT_PASSWORD = \"abcdef\";\n}\n"
  },
  {
    "path": "plugins/redis/src/test/java/com/megaease/easeagent/plugin/redis/interceptor/initialize/CommonRedisClientInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.redis.interceptor.initialize;\n\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport io.lettuce.core.ConnectionFuture;\nimport io.lettuce.core.RedisClient;\nimport io.lettuce.core.RedisURI;\nimport io.lettuce.core.cluster.RedisClusterClient;\nimport org.junit.Test;\n\nimport java.net.DatagramSocket;\nimport java.net.InetSocketAddress;\nimport java.net.SocketException;\nimport java.util.concurrent.CompletableFuture;\n\nimport static org.junit.Assert.*;\n\npublic class CommonRedisClientInterceptorTest {\n\n    @Test\n    public void doAfter() throws SocketException {\n        CommonRedisClientInterceptor commonRedisClientInterceptor = new CommonRedisClientInterceptor();\n        RedisClusterClient redisClusterClient = RedisClusterClient.create(RedisURI.create(\"127.0.0.1\", 111));\n        MethodInfo methodInfo = MethodInfo.builder().invoker(redisClusterClient).retValue(null).throwable(new RuntimeException()).build();\n        commonRedisClientInterceptor.doAfter(methodInfo, EaseAgent.getContext());\n        assertNull(methodInfo.getRetValue());\n\n        CompletableFuture completableFuture = CompletableFuture.completedFuture(\"testCompletableFuture\");\n        methodInfo = MethodInfo.builder().invoker(redisClusterClient).retValue(completableFuture).build();\n        commonRedisClientInterceptor.doAfter(methodInfo, EaseAgent.getContext());\n        assertTrue(methodInfo.getRetValue() instanceof CompletableFutureWrapper);\n\n        RedisClient redisClient = RedisClient.create(RedisURI.create(\"127.0.0.1\", 111));\n        DatagramSocket s = new DatagramSocket(0);\n        ConnectionFuture connectionFuture = ConnectionFuture.completed(new InetSocketAddress(s.getLocalPort()), \"test\");\n        methodInfo = MethodInfo.builder().invoker(redisClient).retValue(connectionFuture).build();\n        commonRedisClientInterceptor.doAfter(methodInfo, EaseAgent.getContext());\n        assertTrue(methodInfo.getRetValue() instanceof ConnectionFutureWrapper);\n        redisClient.shutdown();\n        redisClusterClient.shutdown();\n    }\n\n    @Test\n    public void processRedisClient() throws SocketException {\n        CommonRedisClientInterceptor commonRedisClientInterceptor = new CommonRedisClientInterceptor();\n        RedisClient redisClient = RedisClient.create(RedisURI.create(\"127.0.0.1\", 111));\n        DatagramSocket s = new DatagramSocket(0);\n        ConnectionFuture connectionFuture = ConnectionFuture.completed(new InetSocketAddress(s.getLocalPort()), \"test\");\n        MethodInfo methodInfo = MethodInfo.builder().invoker(redisClient).retValue(connectionFuture).build();\n        commonRedisClientInterceptor.processRedisClient(methodInfo, EaseAgent.getContext());\n        assertTrue(methodInfo.getRetValue() instanceof ConnectionFutureWrapper);\n        redisClient.shutdown();\n    }\n\n    @Test\n    public void processRedisClusterClient() {\n        CommonRedisClientInterceptor commonRedisClientInterceptor = new CommonRedisClientInterceptor();\n        RedisClusterClient redisClusterClient = RedisClusterClient.create(RedisURI.create(\"127.0.0.1\", 111));\n        CompletableFuture completableFuture = CompletableFuture.completedFuture(\"testCompletableFuture\");\n        MethodInfo methodInfo = MethodInfo.builder().invoker(redisClusterClient).retValue(completableFuture).build();\n        commonRedisClientInterceptor.processRedisClusterClient(methodInfo, EaseAgent.getContext());\n        assertTrue(methodInfo.getRetValue() instanceof CompletableFutureWrapper);\n        redisClusterClient.shutdown();\n    }\n\n    @Test\n    public void order() {\n        CommonRedisClientInterceptor commonRedisClientInterceptor = new CommonRedisClientInterceptor();\n        assertTrue(commonRedisClientInterceptor.order() < Order.TRACING.getOrder());\n    }\n\n    @Test\n    public void getType() {\n        CommonRedisClientInterceptor commonRedisClientInterceptor = new CommonRedisClientInterceptor();\n        assertEquals(ConfigConst.PluginID.TRACING, commonRedisClientInterceptor.getType());\n    }\n}\n"
  },
  {
    "path": "plugins/redis/src/test/java/com/megaease/easeagent/plugin/redis/interceptor/initialize/CompletableFutureWrapperTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.redis.interceptor.initialize;\n\nimport org.junit.Test;\n\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.atomic.AtomicReference;\n\nimport static org.junit.Assert.*;\n\npublic class CompletableFutureWrapperTest {\n    private String attach = \"127.0.0.1:9090\";\n\n\n    private CompletableFutureWrapper<DynamicFieldAccessorObj> createOne() {\n        DynamicFieldAccessorObj dynamicFieldAccessorObj = new DynamicFieldAccessorObj();\n        dynamicFieldAccessorObj.setChannelWriter(new DynamicFieldAccessorObj());\n        CompletableFuture<DynamicFieldAccessorObj> connectionFuture = CompletableFuture.completedFuture(dynamicFieldAccessorObj);\n        return new CompletableFutureWrapper(connectionFuture, attach);\n    }\n\n    @Test\n    public void isDone() {\n        CompletableFutureWrapper<DynamicFieldAccessorObj> completableFutureWrapper = createOne();\n        assertTrue(completableFutureWrapper.isDone());\n\n    }\n\n    private void checkDynamicFieldAccessorObj(DynamicFieldAccessorObj dynamicFieldAccessorObj) {\n        assertEquals(attach, dynamicFieldAccessorObj.getEaseAgent$$DynamicField$$Data());\n        assertTrue(dynamicFieldAccessorObj.getChannelWriter() instanceof DynamicFieldAccessorObj);\n        assertEquals(attach, ((DynamicFieldAccessorObj) dynamicFieldAccessorObj.getChannelWriter()).getEaseAgent$$DynamicField$$Data());\n    }\n\n    @Test\n    public void get() throws ExecutionException, InterruptedException {\n        CompletableFutureWrapper<DynamicFieldAccessorObj> completableFutureWrapper = createOne();\n        checkDynamicFieldAccessorObj(completableFutureWrapper.get());\n\n    }\n\n    @Test\n    public void get1() throws InterruptedException, ExecutionException, TimeoutException {\n        CompletableFutureWrapper<DynamicFieldAccessorObj> completableFutureWrapper = createOne();\n        checkDynamicFieldAccessorObj(completableFutureWrapper.get(1, TimeUnit.SECONDS));\n    }\n\n    @Test\n    public void join() {\n        CompletableFutureWrapper<DynamicFieldAccessorObj> completableFutureWrapper = createOne();\n        checkDynamicFieldAccessorObj(completableFutureWrapper.join());\n    }\n\n    @Test\n    public void getNow() {\n        CompletableFutureWrapper<DynamicFieldAccessorObj> completableFutureWrapper = createOne();\n        DynamicFieldAccessorObj dynamicFieldAccessorObj = new DynamicFieldAccessorObj();\n        checkDynamicFieldAccessorObj(completableFutureWrapper.getNow(dynamicFieldAccessorObj));\n    }\n\n    @Test\n    public void complete() throws ExecutionException, InterruptedException {\n        CompletableFutureWrapper<DynamicFieldAccessorObj> completableFutureWrapper = createOne();\n        DynamicFieldAccessorObj dynamicFieldAccessorObj = new DynamicFieldAccessorObj();\n        dynamicFieldAccessorObj.setChannelWriter(new DynamicFieldAccessorObj());\n        completableFutureWrapper.complete(dynamicFieldAccessorObj);\n        checkDynamicFieldAccessorObj(dynamicFieldAccessorObj);\n        assertNotSame(dynamicFieldAccessorObj, completableFutureWrapper.get());\n    }\n\n    @Test\n    public void completeExceptionally() throws ExecutionException, InterruptedException {\n        CompletableFutureWrapper<DynamicFieldAccessorObj> completableFutureWrapper = createOne();\n        completableFutureWrapper.completeExceptionally(new RuntimeException(\"test error\"));\n        checkDynamicFieldAccessorObj(completableFutureWrapper.get());\n    }\n\n    @Test\n    public void thenApply() throws ExecutionException, InterruptedException {\n        CompletableFutureWrapper<DynamicFieldAccessorObj> completableFutureWrapper = createOne();\n        CompletableFuture<Boolean> booleanCompletableFuture = completableFutureWrapper.thenApply(dynamicFieldAccessorObj -> {\n            checkDynamicFieldAccessorObj(dynamicFieldAccessorObj);\n            return true;\n        });\n        assertTrue(booleanCompletableFuture.get());\n    }\n\n    @Test\n    public void thenApplyAsync() throws ExecutionException, InterruptedException {\n        CompletableFutureWrapper<DynamicFieldAccessorObj> completableFutureWrapper = createOne();\n        CompletableFuture<Boolean> booleanCompletableFuture = completableFutureWrapper.thenApplyAsync(dynamicFieldAccessorObj -> {\n            checkDynamicFieldAccessorObj(dynamicFieldAccessorObj);\n            return true;\n        });\n        assertTrue(booleanCompletableFuture.get());\n\n    }\n\n    @Test\n    public void thenApplyAsync1() throws ExecutionException, InterruptedException {\n        CompletableFutureWrapper<DynamicFieldAccessorObj> completableFutureWrapper = createOne();\n        CompletableFuture<Boolean> booleanCompletableFuture = completableFutureWrapper.thenApplyAsync(dynamicFieldAccessorObj -> {\n            checkDynamicFieldAccessorObj(dynamicFieldAccessorObj);\n            return true;\n        }, Runnable::run);\n        assertTrue(booleanCompletableFuture.get());\n    }\n\n    class TestRunTwo {\n        final CompletableFutureWrapper<DynamicFieldAccessorObj> one = createOne();\n        final CompletableFutureWrapper<DynamicFieldAccessorObj> other = createOne();\n        final AtomicBoolean ran = new AtomicBoolean(false);\n        final AtomicReference<DynamicFieldAccessorObj> result = new AtomicReference<>();\n\n        private void check() throws Exception {\n            assertTrue(ran.get());\n            if (result.get() != null) {\n                checkDynamicFieldAccessorObj(result.get());\n            }\n        }\n\n        protected void setRan() {\n            this.ran.set(true);\n        }\n\n        public void consumer(TestRunTwoConsumer consumer) throws Exception {\n            consumer.accept(this);\n            check();\n        }\n    }\n\n    interface TestRunTwoConsumer {\n        void accept(TestRunTwo t) throws Exception;\n    }\n\n\n    @Test\n    public void thenAccept() throws Exception {\n        new TestRunTwo().consumer(t -> {\n            t.one.thenAccept(dynamicFieldAccessorObj -> {\n                checkDynamicFieldAccessorObj(dynamicFieldAccessorObj);\n                t.setRan();\n            });\n        });\n    }\n\n    @Test\n    public void thenAcceptAsync() throws Exception {\n        new TestRunTwo().consumer(t -> {\n            CompletableFuture<Void> c = t.one.thenAcceptAsync(dynamicFieldAccessorObj -> {\n                checkDynamicFieldAccessorObj(dynamicFieldAccessorObj);\n                t.setRan();\n            });\n            c.get();\n        });\n    }\n\n    @Test\n    public void thenAcceptAsync1() throws Exception {\n        new TestRunTwo().consumer(t -> {\n            CompletableFuture<Void> c = t.one.thenAcceptAsync(dynamicFieldAccessorObj -> {\n                checkDynamicFieldAccessorObj(dynamicFieldAccessorObj);\n                t.setRan();\n            }, Runnable::run);\n            c.get();\n        });\n\n    }\n\n    @Test\n    public void thenRun() throws Exception {\n        new TestRunTwo().consumer(t -> {\n            t.one.thenRun(t::setRan);\n        });\n    }\n\n    @Test\n    public void thenRunAsync() throws Exception {\n        new TestRunTwo().consumer(t -> {\n            CompletableFuture<Void> c = t.one.thenRunAsync(t::setRan);\n            c.get();\n        });\n    }\n\n    @Test\n    public void thenRunAsync1() throws Exception {\n        new TestRunTwo().consumer(t -> {\n            CompletableFuture<Void> c = t.one.thenRunAsync(t::setRan, Runnable::run);\n            c.get();\n        });\n    }\n\n    @Test\n    public void thenCombine() throws Exception {\n        new TestRunTwo().consumer(t -> {\n            CompletableFuture<DynamicFieldAccessorObj> c = t.one.thenCombine(t.other, (dynamicFieldAccessorObj, u) -> {\n                t.setRan();\n                checkDynamicFieldAccessorObj(dynamicFieldAccessorObj);\n                return dynamicFieldAccessorObj;\n            });\n            checkDynamicFieldAccessorObj(c.get());\n        });\n    }\n\n    @Test\n    public void thenCombineAsync() throws Exception {\n        new TestRunTwo().consumer(t -> {\n            CompletableFuture<DynamicFieldAccessorObj> c = t.one.thenCombineAsync(t.other, (dynamicFieldAccessorObj, u) -> {\n                t.setRan();\n                checkDynamicFieldAccessorObj(dynamicFieldAccessorObj);\n                return dynamicFieldAccessorObj;\n            });\n            checkDynamicFieldAccessorObj(c.get());\n        });\n    }\n\n    @Test\n    public void thenCombineAsync1() throws Exception {\n        new TestRunTwo().consumer(t -> {\n            CompletableFuture<DynamicFieldAccessorObj> c = t.one.thenCombineAsync(t.other, (dynamicFieldAccessorObj, u) -> {\n                t.setRan();\n                checkDynamicFieldAccessorObj(dynamicFieldAccessorObj);\n                return dynamicFieldAccessorObj;\n            }, Runnable::run);\n            checkDynamicFieldAccessorObj(c.get());\n        });\n    }\n\n    @Test\n    public void thenAcceptBoth() throws Exception {\n        new TestRunTwo().consumer(t -> {\n            t.one.thenAcceptBoth(t.other, (dynamicFieldAccessorObj, dynamicFieldAccessorObj2) -> {\n                checkDynamicFieldAccessorObj(dynamicFieldAccessorObj);\n                t.setRan();\n            });\n        });\n    }\n\n    @Test\n    public void thenAcceptBothAsync() throws Exception {\n        new TestRunTwo().consumer(t -> {\n            CompletableFuture<Void> c = t.one.thenAcceptBothAsync(t.other, (dynamicFieldAccessorObj, dynamicFieldAccessorObj2) -> {\n                checkDynamicFieldAccessorObj(dynamicFieldAccessorObj);\n                t.setRan();\n            });\n            c.get();\n        });\n    }\n\n    @Test\n    public void thenAcceptBothAsync1() throws Exception {\n        new TestRunTwo().consumer(t -> {\n            CompletableFuture<Void> c = t.one.thenAcceptBothAsync(t.other, (dynamicFieldAccessorObj, dynamicFieldAccessorObj2) -> {\n                checkDynamicFieldAccessorObj(dynamicFieldAccessorObj);\n                t.setRan();\n            }, Runnable::run);\n            c.get();\n        });\n    }\n\n    @Test\n    public void runAfterBoth() throws Exception {\n        new TestRunTwo().consumer(t -> {\n            t.one.runAfterBoth(t.other, t::setRan);\n        });\n    }\n\n    @Test\n    public void runAfterBothAsync() throws Exception {\n        new TestRunTwo().consumer(t -> {\n            CompletableFuture<Void> c = t.one.runAfterBothAsync(t.other, t::setRan);\n            c.get();\n        });\n    }\n\n    @Test\n    public void runAfterBothAsync1() throws Exception {\n        new TestRunTwo().consumer(t -> {\n            CompletableFuture<Void> c = t.one.runAfterBothAsync(t.other, t::setRan, Runnable::run);\n            c.get();\n        });\n    }\n\n    @Test\n    public void applyToEither() throws Exception {\n        new TestRunTwo().consumer(t -> {\n            CompletableFuture<DynamicFieldAccessorObj> c = t.one.applyToEither(t.other,\n                dynamicFieldAccessorObj -> {\n                    checkDynamicFieldAccessorObj(dynamicFieldAccessorObj);\n                    t.setRan();\n                    return dynamicFieldAccessorObj;\n                });\n            t.result.set(c.get());\n        });\n    }\n\n    @Test\n    public void applyToEitherAsync() throws Exception {\n        new TestRunTwo().consumer(t -> {\n            CompletableFuture<DynamicFieldAccessorObj> c = t.one.applyToEitherAsync(t.other,\n                dynamicFieldAccessorObj -> {\n                    checkDynamicFieldAccessorObj(dynamicFieldAccessorObj);\n                    t.setRan();\n                    return dynamicFieldAccessorObj;\n                });\n            t.result.set(c.get());\n        });\n    }\n\n    @Test\n    public void applyToEitherAsync1() throws Exception {\n        new TestRunTwo().consumer(t -> {\n            CompletableFuture<DynamicFieldAccessorObj> c = t.one.applyToEitherAsync(t.other,\n                dynamicFieldAccessorObj -> {\n                    checkDynamicFieldAccessorObj(dynamicFieldAccessorObj);\n                    t.setRan();\n                    return dynamicFieldAccessorObj;\n                }, Runnable::run);\n            t.result.set(c.get());\n        });\n    }\n\n    @Test\n    public void acceptEither() throws Exception {\n        new TestRunTwo().consumer(t -> {\n            t.one.acceptEither(t.other,\n                dynamicFieldAccessorObj -> {\n                    checkDynamicFieldAccessorObj(dynamicFieldAccessorObj);\n                    t.setRan();\n                });\n        });\n    }\n\n    @Test\n    public void acceptEitherAsync() throws Exception {\n        new TestRunTwo().consumer(t -> {\n            CompletableFuture<Void> c = t.one.acceptEitherAsync(t.other,\n                dynamicFieldAccessorObj -> {\n                    checkDynamicFieldAccessorObj(dynamicFieldAccessorObj);\n                    t.setRan();\n                });\n\n            c.get();\n        });\n    }\n\n    @Test\n    public void acceptEitherAsync1() throws Exception {\n        new TestRunTwo().consumer(t -> {\n            CompletableFuture<Void> c = t.one.acceptEitherAsync(t.other,\n                dynamicFieldAccessorObj -> {\n                    checkDynamicFieldAccessorObj(dynamicFieldAccessorObj);\n                    t.setRan();\n                }, Runnable::run);\n            c.get();\n        });\n    }\n\n    @Test\n    public void runAfterEither() throws Exception {\n        new TestRunTwo().consumer(t -> {\n            t.one.runAfterEither(t.other,\n                t::setRan);\n        });\n    }\n\n    @Test\n    public void runAfterEitherAsync() throws Exception {\n        new TestRunTwo().consumer(t -> {\n            CompletableFuture<Void> c = t.one.runAfterEitherAsync(t.other,\n                t::setRan);\n            c.get();\n        });\n\n    }\n\n    @Test\n    public void runAfterEitherAsync1() throws Exception {\n        new TestRunTwo().consumer(t -> {\n            CompletableFuture<Void> c = t.one.runAfterEitherAsync(t.other,\n                t::setRan, Runnable::run);\n            c.get();\n        });\n    }\n\n    @Test\n    public void thenCompose() throws Exception {\n        new TestRunTwo().consumer(t -> {\n            CompletableFuture<DynamicFieldAccessorObj> c = t.one.thenCompose(dynamicFieldAccessorObj -> {\n                checkDynamicFieldAccessorObj(dynamicFieldAccessorObj);\n                t.setRan();\n                return t.other;\n            });\n            checkDynamicFieldAccessorObj(c.get());\n        });\n    }\n\n    @Test\n    public void thenComposeAsync() throws Exception {\n        new TestRunTwo().consumer(t -> {\n            CompletableFuture<DynamicFieldAccessorObj> c = t.one.thenComposeAsync(dynamicFieldAccessorObj -> {\n                checkDynamicFieldAccessorObj(dynamicFieldAccessorObj);\n                t.setRan();\n                return t.other;\n            });\n            checkDynamicFieldAccessorObj(c.get());\n        });\n    }\n\n    @Test\n    public void thenComposeAsync1() throws Exception {\n        new TestRunTwo().consumer(t -> {\n            CompletableFuture<DynamicFieldAccessorObj> c = t.one.thenComposeAsync(dynamicFieldAccessorObj -> {\n                checkDynamicFieldAccessorObj(dynamicFieldAccessorObj);\n                t.setRan();\n                return t.other;\n            }, Runnable::run);\n            checkDynamicFieldAccessorObj(c.get());\n        });\n\n    }\n\n    @Test\n    public void whenComplete() throws Exception {\n        new TestRunTwo().consumer(t -> {\n            CompletableFuture<DynamicFieldAccessorObj> connectionFuture = t.one.whenComplete((dynamicFieldAccessorObj, throwable) -> {\n                checkDynamicFieldAccessorObj(dynamicFieldAccessorObj);\n                t.setRan();\n            });\n        });\n    }\n\n    @Test\n    public void whenCompleteAsync() throws Exception {\n        new TestRunTwo().consumer(t -> {\n            CompletableFuture<DynamicFieldAccessorObj> connectionFuture = t.one.whenCompleteAsync((dynamicFieldAccessorObj, throwable) -> {\n                checkDynamicFieldAccessorObj(dynamicFieldAccessorObj);\n                t.setRan();\n            });\n            connectionFuture.get();\n        });\n    }\n\n    @Test\n    public void whenCompleteAsync1() throws Exception {\n        new TestRunTwo().consumer(t -> {\n            CompletableFuture<DynamicFieldAccessorObj> connectionFuture = t.one.whenCompleteAsync((dynamicFieldAccessorObj, throwable) -> {\n                checkDynamicFieldAccessorObj(dynamicFieldAccessorObj);\n                t.setRan();\n            }, Runnable::run);\n            connectionFuture.get();\n        });\n    }\n\n    @Test\n    public void handle() throws Exception {\n        new TestRunTwo().consumer(t -> {\n            CompletableFuture<DynamicFieldAccessorObj> connectionFuture = t.one.handle((dynamicFieldAccessorObj, throwable) -> {\n                checkDynamicFieldAccessorObj(dynamicFieldAccessorObj);\n                t.setRan();\n                return null;\n            });\n        });\n    }\n\n    @Test\n    public void handleAsync() throws Exception {\n        new TestRunTwo().consumer(t -> {\n            CompletableFuture<DynamicFieldAccessorObj> connectionFuture = t.one.handleAsync((dynamicFieldAccessorObj, throwable) -> {\n                checkDynamicFieldAccessorObj(dynamicFieldAccessorObj);\n                t.setRan();\n                return null;\n            });\n            connectionFuture.get();\n        });\n    }\n\n    @Test\n    public void handleAsync1() throws Exception {\n        new TestRunTwo().consumer(t -> {\n            CompletableFuture<DynamicFieldAccessorObj> connectionFuture = t.one.handleAsync((dynamicFieldAccessorObj, throwable) -> {\n                checkDynamicFieldAccessorObj(dynamicFieldAccessorObj);\n                t.setRan();\n                return null;\n            }, Runnable::run);\n            connectionFuture.get();\n        });\n\n    }\n\n    @Test\n    public void toCompletableFuture() throws ExecutionException, InterruptedException {\n        CompletableFutureWrapper<DynamicFieldAccessorObj> completableFutureWrapper = createOne();\n        CompletableFuture<DynamicFieldAccessorObj> c = completableFutureWrapper.toCompletableFuture();\n        assertNull(c.get().getValue());\n    }\n\n    @Test\n    public void exceptionally() throws ExecutionException, InterruptedException {\n        CompletableFutureWrapper<DynamicFieldAccessorObj> completableFutureWrapper = createOne();\n        CompletableFuture<DynamicFieldAccessorObj> a = completableFutureWrapper.exceptionally(throwable -> null);\n        checkDynamicFieldAccessorObj(a.get());\n    }\n\n    @Test\n    public void cancel() {\n        CompletableFutureWrapper<DynamicFieldAccessorObj> completableFutureWrapper = createOne();\n        assertFalse(completableFutureWrapper.cancel(true));\n    }\n\n    @Test\n    public void isCancelled() {\n        CompletableFutureWrapper<DynamicFieldAccessorObj> completableFutureWrapper = createOne();\n        assertFalse(completableFutureWrapper.isCancelled());\n    }\n\n    @Test\n    public void isCompletedExceptionally() {\n        CompletableFutureWrapper<DynamicFieldAccessorObj> completableFutureWrapper = createOne();\n        assertFalse(completableFutureWrapper.isCompletedExceptionally());\n    }\n\n    @Test\n    public void obtrudeValue() throws ExecutionException, InterruptedException {\n        CompletableFutureWrapper<DynamicFieldAccessorObj> completableFutureWrapper = createOne();\n        String value = \"newValue\";\n        completableFutureWrapper.obtrudeValue(new DynamicFieldAccessorObj().setValue(value).setChannelWriter(new DynamicFieldAccessorObj()));\n        DynamicFieldAccessorObj obj = completableFutureWrapper.get();\n        checkDynamicFieldAccessorObj(obj);\n        assertEquals(value, obj.getValue());\n    }\n\n    @Test\n    public void obtrudeException() throws ExecutionException, InterruptedException {\n        CompletableFutureWrapper<DynamicFieldAccessorObj> completableFutureWrapper = createOne();\n        completableFutureWrapper.obtrudeException(new RuntimeException(\"error\"));\n        assertTrue(completableFutureWrapper.isCompletedExceptionally());\n    }\n\n    @Test\n    public void getNumberOfDependents() throws ExecutionException, InterruptedException {\n        CompletableFutureWrapper<DynamicFieldAccessorObj> completableFutureWrapper = createOne();\n        assertEquals(0, completableFutureWrapper.getNumberOfDependents());\n    }\n\n    @Test\n    public void testToString() {\n        CompletableFutureWrapper<DynamicFieldAccessorObj> completableFutureWrapper = createOne();\n        completableFutureWrapper.toString();\n        assertTrue(completableFutureWrapper.isDone());\n    }\n}\n"
  },
  {
    "path": "plugins/redis/src/test/java/com/megaease/easeagent/plugin/redis/interceptor/initialize/ConnectionFutureWrapperTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.redis.interceptor.initialize;\n\nimport io.lettuce.core.ConnectionFuture;\nimport lombok.SneakyThrows;\nimport org.jetbrains.annotations.NotNull;\nimport org.junit.Before;\nimport org.junit.Test;\n\nimport java.net.DatagramSocket;\nimport java.net.InetSocketAddress;\nimport java.net.SocketException;\nimport java.util.concurrent.*;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.function.BiConsumer;\nimport java.util.function.BiFunction;\nimport java.util.function.Consumer;\nimport java.util.function.Function;\n\nimport static org.junit.Assert.*;\n\npublic class ConnectionFutureWrapperTest {\n    private String attach;\n    private InetSocketAddress socketAddress;\n\n    @Before\n    public void before() throws SocketException {\n        DatagramSocket s = new DatagramSocket(0);\n        attach = \"127.0.0.1:\" + s.getLocalPort();\n        socketAddress = new InetSocketAddress(\"127.0.0.1\", s.getLocalPort());\n\n    }\n\n    private ConnectionFutureWrapper<DynamicFieldAccessorObj> createOne() {\n        DynamicFieldAccessorObj dynamicFieldAccessorObj = new DynamicFieldAccessorObj();\n        dynamicFieldAccessorObj.setChannelWriter(new DynamicFieldAccessorObj());\n        ConnectionFuture<DynamicFieldAccessorObj> connectionFuture = ConnectionFuture.completed(socketAddress, dynamicFieldAccessorObj);\n        return new ConnectionFutureWrapper(connectionFuture, attach);\n    }\n\n    @Test\n    public void cancel() {\n        ConnectionFutureWrapper<DynamicFieldAccessorObj> connectionFutureWrapper = createOne();\n        assertFalse(connectionFutureWrapper.cancel(true));\n    }\n\n    @Test\n    public void isCancelled() {\n        ConnectionFutureWrapper<DynamicFieldAccessorObj> connectionFutureWrapper = createOne();\n        assertFalse(connectionFutureWrapper.isCancelled());\n\n    }\n\n    @Test\n    public void isDone() {\n        ConnectionFutureWrapper<DynamicFieldAccessorObj> connectionFutureWrapper = createOne();\n        assertTrue(connectionFutureWrapper.isDone());\n\n    }\n\n    private void checkDynamicFieldAccessorObj(DynamicFieldAccessorObj dynamicFieldAccessorObj) {\n        assertEquals(attach, dynamicFieldAccessorObj.getEaseAgent$$DynamicField$$Data());\n        assertTrue(dynamicFieldAccessorObj.getChannelWriter() instanceof DynamicFieldAccessorObj);\n        assertEquals(attach, ((DynamicFieldAccessorObj) dynamicFieldAccessorObj.getChannelWriter()).getEaseAgent$$DynamicField$$Data());\n    }\n\n    @Test\n    public void get() throws ExecutionException, InterruptedException {\n        ConnectionFutureWrapper<DynamicFieldAccessorObj> connectionFutureWrapper = createOne();\n        checkDynamicFieldAccessorObj(connectionFutureWrapper.get());\n    }\n\n    @Test\n    public void get1() throws InterruptedException, ExecutionException, TimeoutException {\n        ConnectionFutureWrapper<DynamicFieldAccessorObj> connectionFutureWrapper = createOne();\n        checkDynamicFieldAccessorObj(connectionFutureWrapper.get(1, TimeUnit.SECONDS));\n    }\n\n    @Test\n    public void getRemoteAddress() throws SocketException {\n        ConnectionFutureWrapper<DynamicFieldAccessorObj> connectionFutureWrapper = createOne();\n        assertEquals(socketAddress, connectionFutureWrapper.getRemoteAddress());\n    }\n\n    @Test\n    public void join() {\n        ConnectionFutureWrapper<DynamicFieldAccessorObj> connectionFutureWrapper = createOne();\n        checkDynamicFieldAccessorObj(connectionFutureWrapper.join());\n    }\n\n    @Test\n    public void thenApply() {\n        ConnectionFutureWrapper<DynamicFieldAccessorObj> connectionFutureWrapper = createOne();\n        connectionFutureWrapper.thenApply(dynamicFieldAccessorObj -> {\n            checkDynamicFieldAccessorObj(dynamicFieldAccessorObj);\n            return null;\n        });\n    }\n\n    @Test\n    public void thenApplyAsync() {\n        ConnectionFutureWrapper<DynamicFieldAccessorObj> connectionFutureWrapper = createOne();\n        connectionFutureWrapper.thenApplyAsync(dynamicFieldAccessorObj -> {\n            checkDynamicFieldAccessorObj(dynamicFieldAccessorObj);\n            return null;\n        });\n    }\n\n    @Test\n    public void thenApplyAsync1() {\n        ConnectionFutureWrapper<DynamicFieldAccessorObj> connectionFutureWrapper = createOne();\n        connectionFutureWrapper.thenApplyAsync(dynamicFieldAccessorObj -> {\n            checkDynamicFieldAccessorObj(dynamicFieldAccessorObj);\n            return null;\n        }, command -> {\n        });\n    }\n\n    @Test\n    public void thenAccept() {\n        ConnectionFutureWrapper<DynamicFieldAccessorObj> connectionFutureWrapper = createOne();\n        connectionFutureWrapper.thenAccept(this::checkDynamicFieldAccessorObj);\n    }\n\n    @Test\n    public void thenAcceptAsync() {\n        ConnectionFutureWrapper<DynamicFieldAccessorObj> connectionFutureWrapper = createOne();\n        connectionFutureWrapper.thenAcceptAsync(this::checkDynamicFieldAccessorObj);\n    }\n\n    @Test\n    public void thenAcceptAsync1() {\n        ConnectionFutureWrapper<DynamicFieldAccessorObj> connectionFutureWrapper = createOne();\n        connectionFutureWrapper.thenAcceptAsync(this::checkDynamicFieldAccessorObj, command -> {\n        });\n    }\n\n    @Test\n    public void thenRun() {\n        ConnectionFutureWrapper<DynamicFieldAccessorObj> connectionFutureWrapper = createOne();\n        AtomicBoolean ran = new AtomicBoolean(false);\n        connectionFutureWrapper.thenRun(() -> ran.set(true));\n        assertTrue(ran.get());\n    }\n\n    @Test\n    public void thenRunAsync() throws ExecutionException, InterruptedException {\n        ConnectionFutureWrapper<DynamicFieldAccessorObj> connectionFutureWrapper = createOne();\n        AtomicBoolean ran = new AtomicBoolean(false);\n        ConnectionFuture connectionFuture = connectionFutureWrapper.thenRunAsync(() -> ran.set(true));\n        connectionFuture.get();\n        assertTrue(ran.get());\n\n    }\n\n    @Test\n    public void thenRunAsync1() {\n        ConnectionFutureWrapper<DynamicFieldAccessorObj> connectionFutureWrapper = createOne();\n        AtomicBoolean ran = new AtomicBoolean(false);\n        connectionFutureWrapper.thenRunAsync(() -> ran.set(true), Runnable::run);\n        assertTrue(ran.get());\n    }\n\n    class TestRunTwo {\n        final ConnectionFutureWrapper<DynamicFieldAccessorObj> one = createOne();\n        final ConnectionFutureWrapper<DynamicFieldAccessorObj> other = createOne();\n        final AtomicBoolean ran = new AtomicBoolean(false);\n        final AtomicReference<DynamicFieldAccessorObj> result = new AtomicReference<>();\n\n        public void check() throws Exception {\n            assertTrue(ran.get());\n            if (result.get() != null) {\n                checkDynamicFieldAccessorObj(result.get());\n            }\n        }\n    }\n\n    interface TestRunTwoConsumer {\n        void accept(TestRunTwo t) throws Exception;\n    }\n\n    public void consumerTestRunTwo(TestRunTwoConsumer consumer) throws Exception {\n        TestRunTwo testRunTwo = new TestRunTwo();\n        consumer.accept(testRunTwo);\n        testRunTwo.check();\n    }\n\n    @Test\n    public void thenCombine() throws Exception {\n        consumerTestRunTwo(testRunTwo -> {\n            ConnectionFuture<DynamicFieldAccessorObj> connectionFuture = testRunTwo.one.thenCombine(testRunTwo.other, (dynamicFieldAccessorObj, u) -> {\n                testRunTwo.ran.set(true);\n                return dynamicFieldAccessorObj;\n            });\n            testRunTwo.result.set(connectionFuture.get());\n        });\n    }\n\n    @Test\n    public void thenCombineAsync() throws Exception {\n        consumerTestRunTwo(testRunTwo -> {\n            ConnectionFuture<DynamicFieldAccessorObj> connectionFuture = testRunTwo.one.thenCombineAsync(testRunTwo.other, (dynamicFieldAccessorObj, u) -> {\n                testRunTwo.ran.set(true);\n                return dynamicFieldAccessorObj;\n            });\n            testRunTwo.result.set(connectionFuture.get());\n        });\n    }\n\n    @Test\n    public void thenCombineAsync1() throws Exception {\n        consumerTestRunTwo(testRunTwo -> {\n            ConnectionFuture<DynamicFieldAccessorObj> connectionFuture = testRunTwo.one.thenCombineAsync(testRunTwo.other, (dynamicFieldAccessorObj, u) -> {\n                testRunTwo.ran.set(true);\n                return dynamicFieldAccessorObj;\n            }, Runnable::run);\n            testRunTwo.result.set(connectionFuture.get());\n        });\n    }\n\n    @Test\n    public void thenAcceptBoth() throws Exception {\n        consumerTestRunTwo(testRunTwo -> {\n            testRunTwo.one.thenAcceptBoth(testRunTwo.other, (dynamicFieldAccessorObj, dynamicFieldAccessorObj2) -> {\n                checkDynamicFieldAccessorObj(dynamicFieldAccessorObj);\n                testRunTwo.ran.set(true);\n            });\n        });\n    }\n\n    @Test\n    public void thenAcceptBothAsync() throws Exception {\n        consumerTestRunTwo(testRunTwo -> {\n            ConnectionFuture<Void> connectionFuture = testRunTwo.one.thenAcceptBothAsync(testRunTwo.other, (dynamicFieldAccessorObj, dynamicFieldAccessorObj2) -> {\n                checkDynamicFieldAccessorObj(dynamicFieldAccessorObj);\n                testRunTwo.ran.set(true);\n            });\n            connectionFuture.get();\n        });\n    }\n\n    @Test\n    public void thenAcceptBothAsync1() throws Exception {\n        consumerTestRunTwo(testRunTwo -> {\n            ConnectionFuture<Void> connectionFuture = testRunTwo.one.thenAcceptBothAsync(testRunTwo.other, (dynamicFieldAccessorObj, dynamicFieldAccessorObj2) -> {\n                checkDynamicFieldAccessorObj(dynamicFieldAccessorObj);\n                testRunTwo.ran.set(true);\n            }, Runnable::run);\n            connectionFuture.get();\n        });\n    }\n\n    @Test\n    public void runAfterBoth() throws Exception {\n        consumerTestRunTwo(testRunTwo -> {\n            testRunTwo.one.runAfterBoth(testRunTwo.other, () -> testRunTwo.ran.set(true));\n        });\n    }\n\n    @Test\n    public void runAfterBothAsync() throws Exception {\n        consumerTestRunTwo(testRunTwo -> {\n            ConnectionFuture<Void> connectionFuture = testRunTwo.one.runAfterBothAsync(testRunTwo.other, () -> testRunTwo.ran.set(true));\n            connectionFuture.get();\n        });\n    }\n\n    @Test\n    public void runAfterBothAsync1() throws Exception {\n        consumerTestRunTwo(testRunTwo -> {\n            ConnectionFuture<Void> connectionFuture = testRunTwo.one.runAfterBothAsync(testRunTwo.other,\n                () -> testRunTwo.ran.set(true), Runnable::run);\n            connectionFuture.get();\n        });\n    }\n\n    @Test\n    public void applyToEither() throws Exception {\n        consumerTestRunTwo(testRunTwo -> {\n            ConnectionFuture<DynamicFieldAccessorObj> connectionFuture = testRunTwo.one.applyToEither(testRunTwo.other,\n                dynamicFieldAccessorObj -> {\n                    checkDynamicFieldAccessorObj(dynamicFieldAccessorObj);\n                    testRunTwo.ran.set(true);\n                    return dynamicFieldAccessorObj;\n                });\n            testRunTwo.result.set(connectionFuture.get());\n        });\n    }\n\n    @Test\n    public void applyToEitherAsync() throws Exception {\n        consumerTestRunTwo(testRunTwo -> {\n            ConnectionFuture<DynamicFieldAccessorObj> connectionFuture = testRunTwo.one.applyToEitherAsync(testRunTwo.other,\n                dynamicFieldAccessorObj -> {\n                    checkDynamicFieldAccessorObj(dynamicFieldAccessorObj);\n                    testRunTwo.ran.set(true);\n                    return dynamicFieldAccessorObj;\n                });\n            testRunTwo.result.set(connectionFuture.get());\n        });\n    }\n\n    @Test\n    public void applyToEitherAsync1() throws Exception {\n        consumerTestRunTwo(testRunTwo -> {\n            ConnectionFuture<DynamicFieldAccessorObj> connectionFuture = testRunTwo.one.applyToEitherAsync(testRunTwo.other,\n                dynamicFieldAccessorObj -> {\n                    checkDynamicFieldAccessorObj(dynamicFieldAccessorObj);\n                    testRunTwo.ran.set(true);\n                    return dynamicFieldAccessorObj;\n                }, Runnable::run);\n            testRunTwo.result.set(connectionFuture.get());\n        });\n    }\n\n    @Test\n    public void acceptEither() throws Exception {\n        consumerTestRunTwo(testRunTwo -> {\n            testRunTwo.one.acceptEither(testRunTwo.other,\n                dynamicFieldAccessorObj -> {\n                    checkDynamicFieldAccessorObj(dynamicFieldAccessorObj);\n                    testRunTwo.ran.set(true);\n                });\n        });\n\n    }\n\n    @Test\n    public void acceptEitherAsync() throws Exception {\n        consumerTestRunTwo(testRunTwo -> {\n            ConnectionFuture<Void> connectionFuture = testRunTwo.one.acceptEitherAsync(testRunTwo.other,\n                dynamicFieldAccessorObj -> {\n                    checkDynamicFieldAccessorObj(dynamicFieldAccessorObj);\n                    testRunTwo.ran.set(true);\n                });\n            connectionFuture.get();\n        });\n    }\n\n    @Test\n    public void acceptEitherAsync1() throws Exception {\n        consumerTestRunTwo(testRunTwo -> {\n            ConnectionFuture<Void> connectionFuture = testRunTwo.one.acceptEitherAsync(testRunTwo.other,\n                dynamicFieldAccessorObj -> {\n                    checkDynamicFieldAccessorObj(dynamicFieldAccessorObj);\n                    testRunTwo.ran.set(true);\n                }, Runnable::run);\n            connectionFuture.get();\n        });\n    }\n\n    @Test\n    public void runAfterEither() throws Exception {\n        consumerTestRunTwo(testRunTwo -> {\n            testRunTwo.one.runAfterEither(testRunTwo.other,\n                () -> testRunTwo.ran.set(true));\n        });\n    }\n\n    @Test\n    public void runAfterEitherAsync() throws Exception {\n        consumerTestRunTwo(testRunTwo -> {\n            ConnectionFuture<Void> connectionFuture = testRunTwo.one.runAfterEitherAsync(testRunTwo.other,\n                () -> testRunTwo.ran.set(true));\n            connectionFuture.get();\n        });\n    }\n\n    @Test\n    public void runAfterEitherAsync1() throws Exception {\n        consumerTestRunTwo(testRunTwo -> {\n            ConnectionFuture<Void> connectionFuture = testRunTwo.one.runAfterEitherAsync(testRunTwo.other,\n                () -> testRunTwo.ran.set(true), Runnable::run);\n            connectionFuture.get();\n        });\n    }\n\n    @Test\n    public void thenCompose() throws Exception {\n        consumerTestRunTwo(testRunTwo -> {\n            testRunTwo.one.thenCompose(dynamicFieldAccessorObj -> {\n                checkDynamicFieldAccessorObj(dynamicFieldAccessorObj);\n                testRunTwo.ran.set(true);\n                return testRunTwo.other;\n            });\n        });\n    }\n\n    @Test\n    public void thenCompose1() throws Exception {\n        consumerTestRunTwo(testRunTwo -> {\n            testRunTwo.one.thenCompose((dynamicFieldAccessorObj, throwable) -> {\n                checkDynamicFieldAccessorObj(dynamicFieldAccessorObj);\n                testRunTwo.ran.set(true);\n                assertNull(throwable);\n                return testRunTwo.other;\n            });\n        });\n\n    }\n\n    @Test\n    public void thenComposeAsync() throws Exception {\n        consumerTestRunTwo(testRunTwo -> {\n            ConnectionFuture<DynamicFieldAccessorObj> connectionFuture = testRunTwo.one.thenComposeAsync(dynamicFieldAccessorObj -> {\n                checkDynamicFieldAccessorObj(dynamicFieldAccessorObj);\n                testRunTwo.ran.set(true);\n                return testRunTwo.other;\n            });\n            connectionFuture.get();\n        });\n    }\n\n    @Test\n    public void thenComposeAsync1() throws Exception {\n        consumerTestRunTwo(testRunTwo -> {\n            ConnectionFuture<DynamicFieldAccessorObj> connectionFuture = testRunTwo.one.thenComposeAsync(dynamicFieldAccessorObj -> {\n                checkDynamicFieldAccessorObj(dynamicFieldAccessorObj);\n                testRunTwo.ran.set(true);\n                return testRunTwo.other;\n            }, Runnable::run);\n            connectionFuture.get();\n        });\n    }\n\n    @Test\n    public void exceptionally() throws Exception {\n        consumerTestRunTwo(testRunTwo -> {\n            ConnectionFuture<DynamicFieldAccessorObj> connectionFuture = testRunTwo.one.exceptionally(dynamicFieldAccessorObj -> {\n                return null;\n            });\n            testRunTwo.ran.set(true);\n        });\n    }\n\n    @Test\n    public void whenComplete() throws Exception {\n        consumerTestRunTwo(testRunTwo -> {\n            ConnectionFuture<DynamicFieldAccessorObj> connectionFuture = testRunTwo.one.whenComplete((dynamicFieldAccessorObj, throwable) -> {\n                checkDynamicFieldAccessorObj(dynamicFieldAccessorObj);\n                testRunTwo.ran.set(true);\n            });\n        });\n    }\n\n    @Test\n    public void whenCompleteAsync() throws Exception {\n        consumerTestRunTwo(testRunTwo -> {\n            ConnectionFuture<DynamicFieldAccessorObj> connectionFuture = testRunTwo.one.whenCompleteAsync((dynamicFieldAccessorObj, throwable) -> {\n                checkDynamicFieldAccessorObj(dynamicFieldAccessorObj);\n                testRunTwo.ran.set(true);\n            });\n            connectionFuture.get();\n        });\n    }\n\n    @Test\n    public void whenCompleteAsync1() throws Exception {\n        consumerTestRunTwo(testRunTwo -> {\n            ConnectionFuture<DynamicFieldAccessorObj> connectionFuture = testRunTwo.one.whenCompleteAsync((dynamicFieldAccessorObj, throwable) -> {\n                checkDynamicFieldAccessorObj(dynamicFieldAccessorObj);\n                testRunTwo.ran.set(true);\n            }, Runnable::run);\n            connectionFuture.get();\n        });\n    }\n\n    @Test\n    public void handle() throws Exception {\n        consumerTestRunTwo(testRunTwo -> {\n            ConnectionFuture<DynamicFieldAccessorObj> connectionFuture = testRunTwo.one.handle((dynamicFieldAccessorObj, throwable) -> {\n                checkDynamicFieldAccessorObj(dynamicFieldAccessorObj);\n                testRunTwo.ran.set(true);\n                return null;\n            });\n        });\n    }\n\n    @Test\n    public void handleAsync() throws Exception {\n        consumerTestRunTwo(testRunTwo -> {\n            ConnectionFuture<DynamicFieldAccessorObj> connectionFuture = testRunTwo.one.handleAsync((dynamicFieldAccessorObj, throwable) -> {\n                checkDynamicFieldAccessorObj(dynamicFieldAccessorObj);\n                testRunTwo.ran.set(true);\n                return null;\n            });\n            connectionFuture.get();\n        });\n    }\n\n    @Test\n    public void handleAsync1() throws Exception {\n        consumerTestRunTwo(testRunTwo -> {\n            ConnectionFuture<DynamicFieldAccessorObj> connectionFuture = testRunTwo.one.handleAsync((dynamicFieldAccessorObj, throwable) -> {\n                checkDynamicFieldAccessorObj(dynamicFieldAccessorObj);\n                testRunTwo.ran.set(true);\n                return null;\n            }, Runnable::run);\n            connectionFuture.get();\n        });\n    }\n\n    @Test\n    public void toCompletableFuture() throws Exception {\n        consumerTestRunTwo(testRunTwo -> {\n            CompletableFuture<DynamicFieldAccessorObj> connectionFuture = testRunTwo.one.toCompletableFuture();\n            testRunTwo.ran.set(true);\n            connectionFuture.get();\n        });\n    }\n}\n"
  },
  {
    "path": "plugins/redis/src/test/java/com/megaease/easeagent/plugin/redis/interceptor/initialize/DynamicFieldAccessorObj.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.redis.interceptor.initialize;\n\nimport com.megaease.easeagent.plugin.field.DynamicFieldAccessor;\n\npublic class DynamicFieldAccessorObj implements DynamicFieldAccessor {\n    private String value;\n    private Object data;\n    private Object channelWriter;\n\n    @Override\n    public void setEaseAgent$$DynamicField$$Data(Object data) {\n        this.data = data;\n    }\n\n    @Override\n    public Object getEaseAgent$$DynamicField$$Data() {\n        return data;\n    }\n\n    public String getValue() {\n        return value;\n    }\n\n    public DynamicFieldAccessorObj setValue(String value) {\n        this.value = value;\n        return this;\n    }\n\n    public Object getChannelWriter() {\n        return channelWriter;\n    }\n\n    public DynamicFieldAccessorObj setChannelWriter(Object channelWriter) {\n        this.channelWriter = channelWriter;\n        return this;\n    }\n}\n"
  },
  {
    "path": "plugins/redis/src/test/java/com/megaease/easeagent/plugin/redis/interceptor/metric/CommonRedisMetricInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.redis.interceptor.metric;\n\nimport com.megaease.easeagent.mock.plugin.api.MockEaseAgent;\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.mock.plugin.api.utils.TagVerifier;\nimport com.megaease.easeagent.mock.report.impl.LastJsonReporter;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.redis.RedisPlugin;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport java.util.Map;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class CommonRedisMetricInterceptorTest {\n    protected static final String key = \"test_redis_metric\";\n    protected static final Object START = AgentFieldReflectAccessor.getStaticFieldValue(MockCommonRedisMetricInterceptor.class, \"START\");\n\n    @Test\n    public void init() {\n        MockCommonRedisMetricInterceptor commonRedisMetricInterceptor = new MockCommonRedisMetricInterceptor();\n        RedisPlugin redisPlugin = new RedisPlugin();\n        IPluginConfig iPluginConfig = EaseAgent.getConfig(redisPlugin.getDomain(), redisPlugin.getNamespace(), commonRedisMetricInterceptor.getType());\n        commonRedisMetricInterceptor.init(iPluginConfig, \"\", \"\", \"\");\n        assertNotNull(AgentFieldReflectAccessor.getStaticFieldValue(MockCommonRedisMetricInterceptor.class, \"REDIS_METRIC\"));\n\n    }\n\n    @Test\n    public void doBefore() {\n        MockCommonRedisMetricInterceptor commonRedisMetricInterceptor = new MockCommonRedisMetricInterceptor();\n        Context context = EaseAgent.getContext();\n        commonRedisMetricInterceptor.doBefore(null, context);\n        assertNotNull(context.get(START));\n        context.remove(START);\n    }\n\n    public Map<String, Object> getMetric(LastJsonReporter lastJsonReporter) throws InterruptedException {\n        return lastJsonReporter.flushAndOnlyOne();\n    }\n\n    @Test\n    public void doAfter() throws InterruptedException {\n        MockCommonRedisMetricInterceptor commonRedisMetricInterceptor = new MockCommonRedisMetricInterceptor();\n        Context context = EaseAgent.getContext();\n        RedisPlugin redisPlugin = new RedisPlugin();\n        IPluginConfig iPluginConfig = EaseAgent.getConfig(redisPlugin.getDomain(), redisPlugin.getNamespace(), commonRedisMetricInterceptor.getType());\n        commonRedisMetricInterceptor.init(iPluginConfig, \"\", \"\", \"\");\n\n        MethodInfo methodInfo = MethodInfo.builder().invoker(\"tttt\").method(\"get\").build();\n\n        TagVerifier tagVerifier = new TagVerifier()\n            .add(\"category\", \"application\")\n            .add(\"type\", \"cache-redis\")\n            .add(\"signature\", \"test_redis_metric\");\n        LastJsonReporter lastJsonReporter = MockEaseAgent.lastMetricJsonReporter(tagVerifier::verifyAnd);\n\n        commonRedisMetricInterceptor.doBefore(methodInfo, context);\n        commonRedisMetricInterceptor.doAfter(methodInfo, context);\n\n\n        Map<String, Object> metric = getMetric(lastJsonReporter);\n\n        assertEquals(1, (int) metric.get(\"cnt\"));\n        assertEquals(0, (int) metric.get(\"errcnt\"));\n\n        methodInfo = MethodInfo.builder().invoker(\"tttt\").method(\"get\").throwable(new RuntimeException(\"test error\")).build();\n        commonRedisMetricInterceptor.doBefore(methodInfo, context);\n        commonRedisMetricInterceptor.doAfter(methodInfo, context);\n\n        lastJsonReporter.clean();\n        metric = getMetric(lastJsonReporter);\n        assertEquals(2, (int) metric.get(\"cnt\"));\n        assertEquals(1, (int) metric.get(\"errcnt\"));\n    }\n\n    @Test\n    public void getType() {\n        MockCommonRedisMetricInterceptor commonRedisMetricInterceptor = new MockCommonRedisMetricInterceptor();\n        assertEquals(ConfigConst.PluginID.METRIC, commonRedisMetricInterceptor.getType());\n    }\n\n    @Test\n    public void getEnterKey() {\n        MockCommonRedisMetricInterceptor commonRedisMetricInterceptor = new MockCommonRedisMetricInterceptor();\n        Object enterKey = commonRedisMetricInterceptor.getEnterKey(null, null);\n        assertSame(AgentFieldReflectAccessor.getStaticFieldValue(MockCommonRedisMetricInterceptor.class, \"ENTER\"), enterKey);\n    }\n\n    class MockCommonRedisMetricInterceptor extends CommonRedisMetricInterceptor {\n\n        @Override\n        public String getKey(MethodInfo methodInfo, Context context) {\n            return key;\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/redis/src/test/java/com/megaease/easeagent/plugin/redis/interceptor/metric/JedisMetricInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.redis.interceptor.metric;\n\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\npublic class JedisMetricInterceptorTest {\n\n    @Test\n    public void getKey() {\n        JedisMetricInterceptor jedisMetricInterceptor = new JedisMetricInterceptor();\n        MethodInfo methodInfo = MethodInfo.builder().invoker(\"tttt\").method(\"get\").build();\n        String key = jedisMetricInterceptor.getKey(methodInfo, null);\n        assertEquals(\"get\", key);\n    }\n}\n"
  },
  {
    "path": "plugins/redis/src/test/java/com/megaease/easeagent/plugin/redis/interceptor/metric/LettuceMetricInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.redis.interceptor.metric;\n\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport io.lettuce.core.protocol.Command;\nimport io.lettuce.core.protocol.CommandKeyword;\nimport io.lettuce.core.protocol.RedisCommand;\nimport org.junit.Test;\n\nimport java.util.Arrays;\n\nimport static org.junit.Assert.*;\n\npublic class LettuceMetricInterceptorTest {\n\n    @Test\n    public void getKey() {\n        LettuceMetricInterceptor lettuceMetricInterceptor = new LettuceMetricInterceptor();\n        MethodInfo methodInfo = MethodInfo.builder().invoker(\"tttt\").args(new Object[]{new Command(CommandKeyword.ADDR, null)}).build();\n        String key = lettuceMetricInterceptor.getKey(methodInfo, null);\n        assertEquals(CommandKeyword.ADDR.name(), key);\n\n        methodInfo = MethodInfo.builder().invoker(\"tttt\").args(new Object[]{Arrays.asList(new Command(CommandKeyword.ADDR, null), new Command(CommandKeyword.ADDR, null))}).build();\n        key = lettuceMetricInterceptor.getKey(methodInfo, null);\n        assertEquals(\"[\" + CommandKeyword.ADDR.name() + \",\" + CommandKeyword.ADDR.name() + \"]\", key);\n    }\n}\n"
  },
  {
    "path": "plugins/redis/src/test/java/com/megaease/easeagent/plugin/redis/interceptor/redirect/JedisConstructorInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.redis.interceptor.redirect;\n\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.api.middleware.Redirect;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.redis.interceptor.RedisUtils;\nimport com.megaease.easeagent.plugin.redis.interceptor.TestConst;\nimport org.junit.Test;\nimport redis.clients.jedis.*;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNull;\nimport static org.junit.Assert.assertTrue;\n\npublic class JedisConstructorInterceptorTest {\n\n    @Test\n    public void before() {\n        JedisConstructorInterceptor jedisConstructorInterceptor = new JedisConstructorInterceptor();\n        RedisUtils.mockRedirect(() -> {\n            MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{\"192.10.0.1\"}).build();\n            jedisConstructorInterceptor.before(methodInfo, EaseAgent.getContext());\n            assertEquals(TestConst.REDIRECT_HOST, methodInfo.getArgs()[0]);\n\n            methodInfo = MethodInfo.builder().args(new Object[]{\"192.10.0.1\", 100}).build();\n            jedisConstructorInterceptor.before(methodInfo, EaseAgent.getContext());\n            assertEquals(TestConst.REDIRECT_HOST, methodInfo.getArgs()[0]);\n            assertEquals(TestConst.REDIRECT_PORT, methodInfo.getArgs()[1]);\n\n            methodInfo = MethodInfo.builder().args(new Object[]{new HostAndPort(\"12.0.0.0\", 10)}).build();\n            jedisConstructorInterceptor.before(methodInfo, EaseAgent.getContext());\n            Object o = methodInfo.getArgs()[0];\n            assertTrue(o instanceof HostAndPort);\n            HostAndPort hostAndPort = (HostAndPort) o;\n            assertEquals(TestConst.REDIRECT_HOST, hostAndPort.getHost());\n            assertEquals(TestConst.REDIRECT_PORT, hostAndPort.getPort());\n\n            methodInfo = MethodInfo.builder().args(new Object[]{new JedisShardInfo(\"12.0.0.0\", 10)}).build();\n            jedisConstructorInterceptor.before(methodInfo, EaseAgent.getContext());\n            o = methodInfo.getArgs()[0];\n            assertTrue(o instanceof JedisShardInfo);\n            JedisShardInfo jedisShardInfo = (JedisShardInfo) o;\n            assertEquals(TestConst.REDIRECT_HOST, jedisShardInfo.getHost());\n            assertEquals(TestConst.REDIRECT_PORT, jedisShardInfo.getPort());\n\n            methodInfo = MethodInfo.builder().args(new Object[]{new DefaultJedisSocketFactory(\"12.0.0.0\", 10, 5000, 5000, false, null, null, null)}).build();\n            jedisConstructorInterceptor.before(methodInfo, EaseAgent.getContext());\n            o = methodInfo.getArgs()[0];\n            assertTrue(o instanceof JedisSocketFactory);\n            JedisSocketFactory jedisSocketFactory = (JedisSocketFactory) o;\n            assertEquals(TestConst.REDIRECT_HOST, jedisSocketFactory.getHost());\n            assertEquals(TestConst.REDIRECT_PORT, jedisSocketFactory.getPort());\n        });\n        assertNull(Redirect.REDIS.getConfig());\n    }\n\n    @Test\n    public void getType() {\n        JedisConstructorInterceptor jedisConstructorInterceptor = new JedisConstructorInterceptor();\n        assertEquals(ConfigConst.PluginID.REDIRECT, jedisConstructorInterceptor.getType());\n    }\n}\n"
  },
  {
    "path": "plugins/redis/src/test/java/com/megaease/easeagent/plugin/redis/interceptor/redirect/LettuceRedisClientConstructInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.redis.interceptor.redirect;\n\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.redis.interceptor.RedisClientUtils;\nimport com.megaease.easeagent.plugin.redis.interceptor.RedisUtils;\nimport com.megaease.easeagent.plugin.redis.interceptor.TestConst;\nimport io.lettuce.core.RedisClient;\nimport io.lettuce.core.RedisURI;\nimport org.junit.Test;\n\nimport static org.junit.Assert.assertEquals;\n\npublic class LettuceRedisClientConstructInterceptorTest {\n\n    @Test\n    public void doAfter() {\n        LettuceRedisClientConstructInterceptor lettuceRedisClientConstructInterceptor = new LettuceRedisClientConstructInterceptor();\n        RedisURI r = RedisURI.create(\"12.0.0.1\", 1010);\n        r.setPassword(\"bbbb\");\n        RedisClient redisClient = RedisClient.create(r);\n        RedisUtils.mockRedirect(() -> {\n            MethodInfo methodInfo = MethodInfo.builder().invoker(redisClient).build();\n            lettuceRedisClientConstructInterceptor.doAfter(methodInfo, EaseAgent.getContext());\n            RedisURI redisURI = RedisClientUtils.getRedisURI((RedisClient) methodInfo.getInvoker(), null);\n            assertEquals(TestConst.REDIRECT_HOST, redisURI.getHost());\n            assertEquals(TestConst.REDIRECT_PORT, redisURI.getPort());\n            assertEquals(TestConst.REDIRECT_PASSWORD, new String(redisURI.getPassword()));\n        });\n    }\n\n    @Test\n    public void getType() {\n        LettuceRedisClientConstructInterceptor lettuceRedisClientConstructInterceptor = new LettuceRedisClientConstructInterceptor();\n        assertEquals(ConfigConst.PluginID.REDIRECT, lettuceRedisClientConstructInterceptor.getType());\n    }\n}\n"
  },
  {
    "path": "plugins/redis/src/test/java/com/megaease/easeagent/plugin/redis/interceptor/redirect/RedisPropertiesClusterSetNodesInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.redis.interceptor.redirect;\n\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.redis.interceptor.RedisUtils;\nimport com.megaease.easeagent.plugin.redis.interceptor.TestConst;\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\npublic class RedisPropertiesClusterSetNodesInterceptorTest {\n\n    @Test\n    public void before() {\n        RedisPropertiesClusterSetNodesInterceptor redisPropertiesClusterSetNodesInterceptor = new RedisPropertiesClusterSetNodesInterceptor();\n        RedisUtils.mockRedirect(() -> {\n            MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{\"12.0.0.1:9999\"}).build();\n            redisPropertiesClusterSetNodesInterceptor.before(methodInfo, EaseAgent.getContext());\n            assertEquals(String.format(\"%s:%s\", TestConst.REDIRECT_HOST, TestConst.REDIRECT_PORT), methodInfo.getArgs()[0]);\n        });\n    }\n\n    @Test\n    public void getType() {\n        RedisPropertiesClusterSetNodesInterceptor redisPropertiesClusterSetNodesInterceptor = new RedisPropertiesClusterSetNodesInterceptor();\n        assertEquals(ConfigConst.PluginID.REDIRECT, redisPropertiesClusterSetNodesInterceptor.getType());\n    }\n}\n"
  },
  {
    "path": "plugins/redis/src/test/java/com/megaease/easeagent/plugin/redis/interceptor/redirect/RedisPropertiesSetPropertyInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.redis.interceptor.redirect;\n\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.redis.interceptor.RedisUtils;\nimport com.megaease.easeagent.plugin.redis.interceptor.TestConst;\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\npublic class RedisPropertiesSetPropertyInterceptorTest {\n\n    @Test\n    public void before() {\n        RedisPropertiesSetPropertyInterceptor redisPropertiesSetPropertyInterceptor = new RedisPropertiesSetPropertyInterceptor();\n        RedisUtils.mockRedirect(() -> {\n            MethodInfo methodInfo = MethodInfo.builder().method(\"setHost\").args(new Object[]{\"12.0.0.1\"}).build();\n            redisPropertiesSetPropertyInterceptor.before(methodInfo, EaseAgent.getContext());\n            assertEquals(TestConst.REDIRECT_HOST, methodInfo.getArgs()[0]);\n\n            methodInfo = MethodInfo.builder().method(\"setPort\").args(new Object[]{8080}).build();\n            redisPropertiesSetPropertyInterceptor.before(methodInfo, EaseAgent.getContext());\n            assertEquals(TestConst.REDIRECT_PORT, methodInfo.getArgs()[0]);\n\n            methodInfo = MethodInfo.builder().method(\"setPassword\").args(new Object[]{\"aaaa\"}).build();\n            redisPropertiesSetPropertyInterceptor.before(methodInfo, EaseAgent.getContext());\n            assertEquals(TestConst.REDIRECT_PASSWORD, methodInfo.getArgs()[0]);\n        });\n\n    }\n\n    @Test\n    public void getType() {\n        RedisPropertiesSetPropertyInterceptor redisPropertiesSetPropertyInterceptor = new RedisPropertiesSetPropertyInterceptor();\n        assertEquals(ConfigConst.PluginID.REDIRECT, redisPropertiesSetPropertyInterceptor.getType());\n    }\n}\n"
  },
  {
    "path": "plugins/redis/src/test/java/com/megaease/easeagent/plugin/redis/interceptor/tracing/CommonRedisTracingInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.redis.interceptor.tracing;\n\nimport com.megaease.easeagent.mock.plugin.api.MockEaseAgent;\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.middleware.MiddlewareConstants;\nimport com.megaease.easeagent.plugin.api.middleware.Type;\nimport com.megaease.easeagent.plugin.api.trace.Scope;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport java.util.Objects;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class CommonRedisTracingInterceptorTest {\n    protected static final Object ENTER = AgentFieldReflectAccessor.getStaticFieldValue(CommonRedisTracingInterceptor.class, \"ENTER\");\n    protected static final Object SPAN_KEY = AgentFieldReflectAccessor.getStaticFieldValue(CommonRedisTracingInterceptor.class, \"SPAN_KEY\");\n    String name = \"test_redis_span\";\n\n    @Test\n    public void doBefore() {\n        MockCommonRedisTracingInterceptor commonRedisTracingInterceptor = new MockCommonRedisTracingInterceptor();\n        Context context = EaseAgent.getContext();\n        commonRedisTracingInterceptor.doBefore(null, context);\n        assertFalse(commonRedisTracingInterceptor.ran.get());\n        Span span = context.nextSpan().start();\n        try (Scope ignored = span.maybeScope()) {\n            commonRedisTracingInterceptor.doBefore(null, context);\n            assertTrue(commonRedisTracingInterceptor.ran.get());\n        } finally {\n            span.finish();\n        }\n\n    }\n\n    @Test\n    public void getEnterKey() {\n        CommonRedisTracingInterceptor commonRedisTracingInterceptor = new MockCommonRedisTracingInterceptor();\n        assertSame(ENTER, commonRedisTracingInterceptor.getEnterKey(null, EaseAgent.getContext()));\n    }\n\n    @Test\n    public void doAfter() {\n        finishTracing();\n    }\n\n    private void checkMockSpanInfo(ReportSpan mockSpan) {\n        assertEquals(name, mockSpan.name());\n        assertEquals(Span.Kind.CLIENT.name(), mockSpan.kind());\n        assertEquals(\"redis\", mockSpan.remoteServiceName());\n        assertEquals(Type.REDIS.getRemoteType(), mockSpan.tag(MiddlewareConstants.TYPE_TAG_NAME));\n    }\n\n    @Test\n    public void startTracing() {\n        CommonRedisTracingInterceptor commonRedisTracingInterceptor = new MockCommonRedisTracingInterceptor();\n        Context context = EaseAgent.getContext();\n\n        commonRedisTracingInterceptor.startTracing(context, name, null, null);\n        Span span = context.get(SPAN_KEY);\n        span.finish();\n        checkMockSpanInfo(Objects.requireNonNull(MockEaseAgent.getLastSpan()));\n\n        String cmd = \"testCmd\";\n        commonRedisTracingInterceptor.startTracing(context, name, null, cmd);\n        span = context.get(SPAN_KEY);\n        span.finish();\n        ReportSpan mockSpan = Objects.requireNonNull(MockEaseAgent.getLastSpan());\n        checkMockSpanInfo(mockSpan);\n        assertEquals(cmd, mockSpan.tag(\"redis.method\"));\n\n\n        Span pSpan = context.nextSpan().start();\n        ReportSpan child;\n        try (Scope ignored = pSpan.maybeScope()) {\n            commonRedisTracingInterceptor.startTracing(context, name, null, null);\n            span = context.get(SPAN_KEY);\n            span.finish();\n            child = Objects.requireNonNull(MockEaseAgent.getLastSpan());\n        } finally {\n            pSpan.finish();\n        }\n        ReportSpan parent = Objects.requireNonNull(MockEaseAgent.getLastSpan());\n        assertEquals(parent.traceId(), child.traceId());\n        assertEquals(parent.id(), child.parentId());\n        assertNull(parent.parentId());\n    }\n\n    @Test\n    public void finishTracing() {\n        CommonRedisTracingInterceptor commonRedisTracingInterceptor = new MockCommonRedisTracingInterceptor();\n        Context context = EaseAgent.getContext();\n        commonRedisTracingInterceptor.finishTracing(null, context);\n        Span span = context.nextSpan().start();\n        String name = \"test_redis_span\";\n        span.name(name);\n        context.put(SPAN_KEY, span);\n        commonRedisTracingInterceptor.finishTracing(null, context);\n        ReportSpan mockSpan = MockEaseAgent.getLastSpan();\n        assertEquals(name, mockSpan.name());\n        assertEquals(span.traceIdString(), mockSpan.traceId());\n        assertNull(context.get(SPAN_KEY));\n\n        span = context.nextSpan().start();\n        span.name(name);\n        String errorInfo = \"test error\";\n        context.put(SPAN_KEY, span);\n        commonRedisTracingInterceptor.finishTracing(new RuntimeException(errorInfo), context);\n        mockSpan = MockEaseAgent.getLastSpan();\n        assertEquals(name, mockSpan.name());\n        assertEquals(span.traceIdString(), mockSpan.traceId());\n        assertNull(context.get(SPAN_KEY));\n        assertEquals(errorInfo, mockSpan.tag(\"error\"));\n    }\n\n    private class MockCommonRedisTracingInterceptor extends CommonRedisTracingInterceptor {\n        AtomicBoolean ran = new AtomicBoolean(false);\n\n        @Override\n        public void doTraceBefore(MethodInfo methodInfo, Context context) {\n            ran.set(true);\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/redis/src/test/java/com/megaease/easeagent/plugin/redis/interceptor/tracing/JedisTracingInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.redis.interceptor.tracing;\n\nimport com.megaease.easeagent.mock.plugin.api.MockEaseAgent;\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.middleware.MiddlewareConstants;\nimport com.megaease.easeagent.plugin.api.middleware.Type;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport java.util.Objects;\n\nimport static org.junit.Assert.assertEquals;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class JedisTracingInterceptorTest {\n\n    @Test\n    public void doTraceBefore() {\n        JedisTracingInterceptor jedisTracingInterceptor = new JedisTracingInterceptor();\n        Context context = EaseAgent.getContext();\n        MethodInfo methodInfo = MethodInfo.builder().invoker(\"tttt\").method(\"get\").build();\n        jedisTracingInterceptor.doTraceBefore(methodInfo, context);\n        Span span = context.remove(CommonRedisTracingInterceptorTest.SPAN_KEY);\n        span.finish();\n        ReportSpan mockSpan = Objects.requireNonNull(MockEaseAgent.getLastSpan());\n        assertEquals(\"string.get\", mockSpan.name());\n        assertEquals(Span.Kind.CLIENT.name(), mockSpan.kind());\n        assertEquals(\"redis\", mockSpan.remoteServiceName());\n        assertEquals(Type.REDIS.getRemoteType(), mockSpan.tag(MiddlewareConstants.TYPE_TAG_NAME));\n\n    }\n}\n"
  },
  {
    "path": "plugins/redis/src/test/java/com/megaease/easeagent/plugin/redis/interceptor/tracing/LettuceTracingInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.redis.interceptor.tracing;\n\nimport com.megaease.easeagent.mock.plugin.api.MockEaseAgent;\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.middleware.MiddlewareConstants;\nimport com.megaease.easeagent.plugin.api.middleware.Type;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport java.util.Objects;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNull;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class LettuceTracingInterceptorTest {\n\n    @Test\n    public void doTraceBefore() {\n        LettuceTracingInterceptor lettuceTracingInterceptor = new LettuceTracingInterceptor();\n        Context context = EaseAgent.getContext();\n        MethodInfo methodInfo = MethodInfo.builder().invoker(\"tttt\").args(new Object[]{\"get\"}).build();\n        lettuceTracingInterceptor.doTraceBefore(methodInfo, context);\n        Span span = context.remove(CommonRedisTracingInterceptorTest.SPAN_KEY);\n        span.finish();\n        ReportSpan mockSpan = Objects.requireNonNull(MockEaseAgent.getLastSpan());\n        assertNull(mockSpan.name());\n        assertEquals(Span.Kind.CLIENT.name(), mockSpan.kind());\n        assertEquals(\"redis\", mockSpan.remoteServiceName());\n        assertEquals(Type.REDIS.getRemoteType(), mockSpan.tag(MiddlewareConstants.TYPE_TAG_NAME));\n    }\n}\n"
  },
  {
    "path": "plugins/redis/src/test/resources/mock_agent.properties",
    "content": "plugin.observability.redis.metric.interval=200\nplugin.observability.redis.metric.intervalUnit=MILLISECONDS\n"
  },
  {
    "path": "plugins/servicename/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright (c) 2017, MegaEase\n  ~ All rights reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>plugins</artifactId>\n        <groupId>com.megaease.easeagent</groupId>\n        <version>2.3.0</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>servicename</artifactId>\n    <dependencies>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>plugin-api</artifactId>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.cloud</groupId>\n            <artifactId>spring-cloud-starter-openfeign</artifactId>\n            <version>3.0.4</version>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.cloud</groupId>\n            <artifactId>spring-cloud-gateway-core</artifactId>\n            <version>2.2.3.RELEASE</version>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.netflix.ribbon</groupId>\n            <artifactId>ribbon-core</artifactId>\n            <version>2.2.4</version>\n            <scope>provided</scope>\n        </dependency>\n\n\n        <dependency>\n            <groupId>org.springframework.cloud</groupId>\n            <artifactId>spring-cloud-openfeign-core</artifactId>\n            <version>2.2.3.RELEASE</version>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.fasterxml.jackson.core</groupId>\n            <artifactId>jackson-core</artifactId>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.fasterxml.jackson.core</groupId>\n            <artifactId>jackson-databind</artifactId>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>plugin-api-mock</artifactId>\n            <version>${project.version}</version>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework</groupId>\n            <artifactId>spring-test</artifactId>\n            <version>5.3.9</version>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>io.netty</groupId>\n            <artifactId>netty-buffer</artifactId>\n            <version>4.1.70.Final</version>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework</groupId>\n            <artifactId>spring-webflux</artifactId>\n            <version>5.3.18</version>\n            <scope>test</scope>\n        </dependency>\n\n    </dependencies>\n\n\n</project>\n"
  },
  {
    "path": "plugins/servicename/src/main/java/com/megaease/easeagent/plugin/servicename/Const.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.servicename;\n\nimport com.megaease.easeagent.plugin.CodeVersion;\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\n\npublic interface Const {\n    //----------------- FeignClient begin ---------------\n    /**\n     * The new version has been Deprecated\n     */\n    String FeignLoadBalancer = \"org.springframework.cloud.openfeign.ribbon.FeignLoadBalancer\";\n    /**\n     * The new version has been Deprecated\n     */\n    String RetryableFeignLoadBalancer = \"org.springframework.cloud.openfeign.ribbon.RetryableFeignLoadBalancer\";\n\n    /**\n     * The new version has been Deprecated\n     */\n    String LoadBalancerFeignClient = \"org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient\";\n    String FeignBlockingLoadBalancerClient = \"org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalancerClient\";\n    //----------------- FeignClient end ---------------\n\n    //----------------- RestTemplate begin ---------------\n    String RetryLoadBalancerInterceptor = \"org.springframework.cloud.client.loadbalancer.RetryLoadBalancerInterceptor\";\n\n    /**\n     * The new version has been Deprecated\n     */\n    @Deprecated\n    String AsyncLoadBalancerInterceptor = \"org.springframework.cloud.client.loadbalancer.AsyncLoadBalancerInterceptor\";\n    String LoadBalancerInterceptor = \"org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor\";\n    //----------------- RestTemplate end ---------------\n\n    //----------------- web client begin ---------------\n    String ReactorLoadBalancerExchangeFilterFunction = \"org.springframework.cloud.client.loadbalancer.reactive.ReactorLoadBalancerExchangeFilterFunction\";\n\n    /**\n     * The new version has been Deprecated\n     */\n    @Deprecated\n    String LoadBalancerExchangeFilterFunction = \"org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerExchangeFilterFunction\";\n    //----------------- web client end ---------------\n\n    //----------------- spring gateway begin ---------------\n    String FilteringWebHandler = \"org.springframework.cloud.gateway.handler.FilteringWebHandler\";\n\n    String SERVER_WEB_EXCHANGE_ROUTE_ATTRIBUTE = \"org.springframework.cloud.gateway.support.ServerWebExchangeUtils.gatewayRoute\";\n    //----------------- spring gateway end ---------------\n    String DEFAULT_PROPAGATE_HEAD = \"X-Mesh-RPC-Service\";\n    String PROPAGATE_HEAD_CONFIG = \"propagate.head\";\n\n    CodeVersion VERSIONS = CodeVersion.builder()\n        .key(ConfigConst.CodeVersion.KEY_SPRING_BOOT)\n        .add(Points.DEFAULT_VERSION)\n        .add(ConfigConst.CodeVersion.VERSION_SPRING_BOOT2).build();\n}\n"
  },
  {
    "path": "plugins/servicename/src/main/java/com/megaease/easeagent/plugin/servicename/ReflectionTool.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.servicename;\n\nimport org.springframework.util.ReflectionUtils;\n\nimport java.lang.reflect.Field;\nimport java.lang.reflect.Method;\n\npublic class ReflectionTool {\n    public static Object invokeMethod(Object own, String method, Object... args) throws ReflectiveOperationException {\n        Class<?>[] types = new Class[args.length];\n        for (int i = 0; i < args.length; i++) {\n            types[i] = args[i].getClass();\n        }\n        Method md = ReflectionUtils.findMethod(own.getClass(), method, types);\n        ReflectionUtils.makeAccessible(md);\n        return ReflectionUtils.invokeMethod(md, own, args);\n    }\n\n    public static Object extractField(Object own, String field) throws ReflectiveOperationException {\n        Field fd = ReflectionUtils.findField(own.getClass(), field);\n        ReflectionUtils.makeAccessible(fd);\n        return ReflectionUtils.getField(fd, own);\n    }\n\n\n    public static boolean hasText(String val) {\n        return val != null && val.trim().length() > 0;\n    }\n}\n"
  },
  {
    "path": "plugins/servicename/src/main/java/com/megaease/easeagent/plugin/servicename/ServiceNamePlugin.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.servicename;\n\nimport com.megaease.easeagent.plugin.AgentPlugin;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\n\npublic class ServiceNamePlugin implements AgentPlugin {\n    @Override\n    public String getNamespace() {\n        return ConfigConst.Namespace.SERVICE_NAME;\n    }\n\n    @Override\n    public String getDomain() {\n        return ConfigConst.INTEGRABILITY;\n\n    }\n}\n"
  },
  {
    "path": "plugins/servicename/src/main/java/com/megaease/easeagent/plugin/servicename/ServiceNamePluginConfig.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.servicename;\n\nimport com.megaease.easeagent.plugin.api.config.AutoRefreshConfigSupplier;\nimport com.megaease.easeagent.plugin.api.config.AutoRefreshPluginConfig;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.utils.common.StringUtils;\n\nimport static com.megaease.easeagent.plugin.servicename.Const.DEFAULT_PROPAGATE_HEAD;\nimport static com.megaease.easeagent.plugin.servicename.Const.PROPAGATE_HEAD_CONFIG;\n\npublic class ServiceNamePluginConfig implements AutoRefreshPluginConfig {\n    public static final AutoRefreshConfigSupplier<ServiceNamePluginConfig> SUPPLIER = new AutoRefreshConfigSupplier<ServiceNamePluginConfig>() {\n        @Override\n        public ServiceNamePluginConfig newInstance() {\n            return new ServiceNamePluginConfig();\n        }\n    };\n\n    private volatile String propagateHead = DEFAULT_PROPAGATE_HEAD;\n\n    public String getPropagateHead() {\n        return propagateHead;\n    }\n\n    @Override\n    public void onChange(IPluginConfig oldConfig, IPluginConfig newConfig) {\n        String propagateHead = newConfig.getString(PROPAGATE_HEAD_CONFIG);\n        if (StringUtils.isEmpty(propagateHead) || StringUtils.isEmpty(propagateHead.trim())) {\n            return;\n        }\n        this.propagateHead = propagateHead.trim();\n    }\n}\n"
  },
  {
    "path": "plugins/servicename/src/main/java/com/megaease/easeagent/plugin/servicename/advice/FeignBlockingLoadBalancerClientAdvice.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.servicename.advice;\n\nimport com.megaease.easeagent.plugin.CodeVersion;\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\nimport com.megaease.easeagent.plugin.servicename.Const;\n\nimport java.util.Set;\n\nimport static com.megaease.easeagent.plugin.tools.matcher.ClassMatcherUtils.name;\n\n// OpenFeign\npublic class FeignBlockingLoadBalancerClientAdvice implements Points {\n    @Override\n    public CodeVersion codeVersions() {\n        return Const.VERSIONS;\n    }\n\n    //.type(named(FeignBlockingLoadBalancerClient))\n    //            .transform(feignBlockingLoadBalancerClientExecute(named(\"execute\").and(takesArguments(2))\n    //                .and(takesArgument(0, named(\"feign.Request\")))\n    //            ))\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return name(Const.FeignBlockingLoadBalancerClient);\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.multiBuilder()\n            .match(MethodMatcher.builder().named(\"execute\")\n                .argsLength(2)\n                .arg(0, \"feign.Request\")\n                .qualifier(\"servicename\")\n                .build())\n            .build();\n    }\n}\n"
  },
  {
    "path": "plugins/servicename/src/main/java/com/megaease/easeagent/plugin/servicename/advice/FeignLoadBalancerAdvice.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.servicename.advice;\n\nimport com.megaease.easeagent.plugin.CodeVersion;\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\nimport com.megaease.easeagent.plugin.servicename.Const;\n\nimport java.util.Set;\n\nimport static com.megaease.easeagent.plugin.tools.matcher.ClassMatcherUtils.name;\n\n// OpenFeign\npublic class FeignLoadBalancerAdvice implements Points {\n    @Override\n    public CodeVersion codeVersions() {\n        return Const.VERSIONS;\n    }\n\n    //.type(named(FeignLoadBalancer).or(named(RetryableFeignLoadBalancer)))\n    //            .transform(feignLoadBalancerExecute(named(\"execute\")\n    //                .and(takesArguments(2))\n    //                .and(takesArgument(0, named(FeignLoadBalancer + \"$RibbonRequest\")))\n    //                .and(takesArgument(1, named(\"com.netflix.client.config.IClientConfig\")))\n    //            ))\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return name(Const.FeignLoadBalancer).or(name(Const.RetryableFeignLoadBalancer));\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.multiBuilder()\n            .match(MethodMatcher.builder().named(\"execute\")\n                .argsLength(2)\n                .arg(0, Const.FeignLoadBalancer + \"$RibbonRequest\")\n                .arg(1, \"com.netflix.client.config.IClientConfig\")\n                .qualifier(\"servicename\")\n                .build())\n            .build();\n    }\n}\n"
  },
  {
    "path": "plugins/servicename/src/main/java/com/megaease/easeagent/plugin/servicename/advice/FilteringWebHandlerAdvice.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.servicename.advice;\n\nimport com.megaease.easeagent.plugin.CodeVersion;\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\nimport com.megaease.easeagent.plugin.servicename.Const;\n\nimport java.util.Set;\n\nimport static com.megaease.easeagent.plugin.tools.matcher.ClassMatcherUtils.name;\n\n// Spring Cloud Gateway\npublic class FilteringWebHandlerAdvice implements Points {//FilteringWebHandlerInterceptor\n    //            .type(named(FilteringWebHandler))\n    //            .transform(filteringWebHandlerHandle(named(\"handle\").and(takesArguments(1))\n    //                .and(takesArgument(0, named(\"org.springframework.web.server.ServerWebExchange\")))\n    //            ))\n\n    @Override\n    public CodeVersion codeVersions() {\n        return Const.VERSIONS;\n    }\n\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return name(Const.FilteringWebHandler);\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.multiBuilder()\n            .match(MethodMatcher.builder().named(\"handle\")\n                .argsLength(1)\n                .arg(0, \"org.springframework.web.server.ServerWebExchange\")\n                .qualifier(\"servicename\")\n                .build())\n            .build();\n    }\n}\n"
  },
  {
    "path": "plugins/servicename/src/main/java/com/megaease/easeagent/plugin/servicename/advice/LoadBalancerFeignClientAdvice.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.servicename.advice;\n\nimport com.megaease.easeagent.plugin.CodeVersion;\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\nimport com.megaease.easeagent.plugin.servicename.Const;\n\nimport java.util.Set;\n\nimport static com.megaease.easeagent.plugin.tools.matcher.ClassMatcherUtils.name;\n\n// OpenFeign\npublic class LoadBalancerFeignClientAdvice implements Points {\n    @Override\n    public CodeVersion codeVersions() {\n        return Const.VERSIONS;\n    }\n\n    //// OpenFeign\n    //            .type(named(LoadBalancerFeignClient))\n    //            .transform(loadBalancerFeignClientExecute(named(\"getClientConfig\")\n    //                .and(takesArguments(2))\n    //                .and(takesArgument(0,named(\"feign.Request$Options\")))\n    //                .and(takesArgument(1, named(\"java.lang.String\")))\n    //            ))\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return name(Const.LoadBalancerFeignClient);\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.multiBuilder()\n            .match(MethodMatcher.builder().named(\"getClientConfig\")\n                .argsLength(2)\n                .arg(0, \"feign.Request$Options\")\n                .arg(1, \"java.lang.String\")\n                .returnType(\"com.netflix.client.config.IClientConfig\")\n                .qualifier(\"servicename\")\n                .build())\n            .build();\n    }\n}\n"
  },
  {
    "path": "plugins/servicename/src/main/java/com/megaease/easeagent/plugin/servicename/advice/RestTemplateInterceptAdvice.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.servicename.advice;\n\nimport com.megaease.easeagent.plugin.CodeVersion;\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\nimport com.megaease.easeagent.plugin.servicename.Const;\n\nimport java.util.Set;\n\nimport static com.megaease.easeagent.plugin.tools.matcher.ClassMatcherUtils.name;\n\npublic class RestTemplateInterceptAdvice implements Points {\n    @Override\n    public CodeVersion codeVersions() {\n        return Const.VERSIONS;\n    }\n\n    //.type(namedOneOf(RetryLoadBalancerInterceptor, AsyncLoadBalancerInterceptor, LoadBalancerInterceptor))//\n    //            .transform(restTemplateIntercept(named(\"intercept\").and(takesArguments(3))\n    //                .and(takesArgument(0, named(\"org.springframework.http.HttpRequest\")))\n    //            ))\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return name(Const.RetryLoadBalancerInterceptor).or(name(Const.AsyncLoadBalancerInterceptor)).or(name(Const.LoadBalancerInterceptor));\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.multiBuilder()\n            .match(MethodMatcher.builder().named(\"intercept\")\n                .argsLength(3)\n                .arg(0, \"org.springframework.http.HttpRequest\")\n                .qualifier(\"servicename\")\n                .build())\n            .build();\n    }\n\n\n}\n"
  },
  {
    "path": "plugins/servicename/src/main/java/com/megaease/easeagent/plugin/servicename/advice/WebClientFilterAdvice.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.servicename.advice;\n\nimport com.megaease.easeagent.plugin.CodeVersion;\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\nimport com.megaease.easeagent.plugin.servicename.Const;\n\nimport java.util.Set;\n\nimport static com.megaease.easeagent.plugin.tools.matcher.ClassMatcherUtils.name;\n\npublic class WebClientFilterAdvice implements Points {\n    @Override\n    public CodeVersion codeVersions() {\n        return Const.VERSIONS;\n    }\n\n    //// WebClient\n    //            .type(namedOneOf(ReactorLoadBalancerExchangeFilterFunction, LoadBalancerExchangeFilterFunction))\n    //            .transform(webClientFilter(named(\"filter\").and(takesArguments(2))\n    //                .and(takesArgument(0, named(\"org.springframework.web.reactive.function.client.ClientRequest\")))\n    //            ))\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return name(Const.ReactorLoadBalancerExchangeFilterFunction).or(name(Const.LoadBalancerExchangeFilterFunction));\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.multiBuilder()\n            .match(MethodMatcher.builder().named(\"filter\")\n                .argsLength(2)\n                .arg(0, \"org.springframework.web.reactive.function.client.ClientRequest\")\n                .qualifier(\"servicename\")\n                .build())\n            .build();\n    }\n}\n"
  },
  {
    "path": "plugins/servicename/src/main/java/com/megaease/easeagent/plugin/servicename/interceptor/BaseServiceNameInterceptor.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.servicename.interceptor;\n\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.api.config.AutoRefreshPluginConfigRegistry;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.servicename.ServiceNamePluginConfig;\n\npublic abstract class BaseServiceNameInterceptor implements Interceptor {\n    protected static ServiceNamePluginConfig config = null;\n\n    @Override\n    public void init(IPluginConfig pConfig, String className, String methodName, String methodDescriptor) {\n        config = AutoRefreshPluginConfigRegistry.getOrCreate(pConfig.domain(), pConfig.namespace(), pConfig.id(), ServiceNamePluginConfig.SUPPLIER);\n    }\n\n    @Override\n    public int order() {\n        return Order.HIGH.getOrder();\n    }\n\n    @Override\n    public String getType() {\n        return \"addServiceNameHead\";\n    }\n}\n"
  },
  {
    "path": "plugins/servicename/src/main/java/com/megaease/easeagent/plugin/servicename/interceptor/FeignBlockingLoadBalancerClientInterceptor.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.servicename.interceptor;\n\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.logging.Logger;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.servicename.ReflectionTool;\nimport com.megaease.easeagent.plugin.servicename.advice.FeignBlockingLoadBalancerClientAdvice;\nimport feign.Request;\n\nimport java.net.URI;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.HashMap;\n\n@AdviceTo(value = FeignBlockingLoadBalancerClientAdvice.class, qualifier = \"servicename\")\npublic class FeignBlockingLoadBalancerClientInterceptor extends BaseServiceNameInterceptor {\n    private static final Logger LOGGER = EaseAgent.getLogger(FeignBlockingLoadBalancerClientInterceptor.class);\n\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n        String method = methodInfo.getMethod();\n        try {\n            if (LOGGER.isDebugEnabled()) {\n                LOGGER.debug(\"enter method [{}]\", method);\n            }\n            feign.Request request = (Request) methodInfo.getArgs()[0];\n            String url = request.url();\n            String host = URI.create(url).getHost();\n            if (ReflectionTool.hasText(host)) {\n                final HashMap<String, Collection<String>> newHeaders = new HashMap<>(request.headers());\n                newHeaders.put(config.getPropagateHead(), Collections.singleton(host));\n                context.injectForwardedHeaders((name, value) -> newHeaders.put(name, Collections.singleton(value)));\n                final Request newRequest = Request.create(request.httpMethod(), request.url(), newHeaders, request.body(), request.charset(), request.requestTemplate());\n                methodInfo.changeArg(0, newRequest);\n            }\n        } catch (Throwable e) {\n            LOGGER.warn(\"intercept method [{}] failure\", method, e);\n        }\n    }\n\n}\n"
  },
  {
    "path": "plugins/servicename/src/main/java/com/megaease/easeagent/plugin/servicename/interceptor/FeignLoadBalancerInterceptor.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.servicename.interceptor;\n\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.logging.Logger;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.servicename.ReflectionTool;\nimport com.megaease.easeagent.plugin.servicename.advice.FeignLoadBalancerAdvice;\nimport com.netflix.client.config.IClientConfig;\n\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.Map;\n\n\n@AdviceTo(value = FeignLoadBalancerAdvice.class, qualifier = \"servicename\")\npublic class FeignLoadBalancerInterceptor extends BaseServiceNameInterceptor {\n    private static final Logger LOGGER = EaseAgent.getLogger(FeignLoadBalancerInterceptor.class);\n\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n        try {\n            if (LOGGER.isDebugEnabled()) {\n                LOGGER.debug(\"enter method [{}]\", methodInfo.getMethod());\n            }\n            Object request = methodInfo.getArgs()[0];\n            IClientConfig iClientConfig = (IClientConfig) methodInfo.getArgs()[1];\n            String serviceName = iClientConfig.getClientName();\n            Object realRequest = ReflectionTool.invokeMethod(request, \"getRequest\");\n            @SuppressWarnings(\"unchecked\")\n            Map<String, Collection<String>> headers = (Map<String, Collection<String>>) ReflectionTool.extractField(realRequest, \"headers\");\n            headers.put(config.getPropagateHead(), Collections.singleton(serviceName));\n            context.injectForwardedHeaders((name, value) -> headers.put(name, Collections.singleton(value)));\n        } catch (Throwable e) {\n            LOGGER.warn(\"intercept method [{}] failure\", methodInfo.getMethod(), e);\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/servicename/src/main/java/com/megaease/easeagent/plugin/servicename/interceptor/FilteringWebHandlerInterceptor.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.servicename.interceptor;\n\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.logging.Logger;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.servicename.Const;\nimport com.megaease.easeagent.plugin.servicename.advice.FilteringWebHandlerAdvice;\nimport org.springframework.http.server.reactive.ServerHttpRequest;\nimport org.springframework.util.StringUtils;\nimport org.springframework.web.server.ServerWebExchange;\n\nimport java.net.URI;\n\n@AdviceTo(value = FilteringWebHandlerAdvice.class, qualifier = \"servicename\")\npublic class FilteringWebHandlerInterceptor extends BaseServiceNameInterceptor {\n    private static final Logger LOGGER = EaseAgent.getLogger(FilteringWebHandlerInterceptor.class);\n\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n        String method = methodInfo.getMethod();\n        try {\n            if (LOGGER.isDebugEnabled()) {\n                LOGGER.debug(\"enter method [{}]\", method);\n            }\n            ServerWebExchange exchange = (ServerWebExchange) methodInfo.getArgs()[0];\n            org.springframework.cloud.gateway.route.Route route = exchange.getAttribute(Const.SERVER_WEB_EXCHANGE_ROUTE_ATTRIBUTE);\n            if (route == null) {\n                return;\n            }\n            URI uri = route.getUri();\n            String scheme = uri.getScheme();\n            if (!scheme.equals(\"lb\")) {\n                return;\n            }\n            String host = uri.getHost();\n            if (!StringUtils.hasText(host)) {\n                return;\n            }\n            ServerHttpRequest newRequest = exchange.getRequest().mutate().header(config.getPropagateHead(), host).build();\n            ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();\n            methodInfo.changeArg(0, newExchange);\n        } catch (Throwable e) {\n            LOGGER.warn(\"intercept method [{}] failure\", method, e);\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/servicename/src/main/java/com/megaease/easeagent/plugin/servicename/interceptor/LoadBalancerFeignClientInterceptor.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.servicename.interceptor;\n\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.logging.Logger;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.servicename.ReflectionTool;\nimport com.megaease.easeagent.plugin.servicename.advice.LoadBalancerFeignClientAdvice;\n\n@AdviceTo(value = LoadBalancerFeignClientAdvice.class, qualifier = \"servicename\")\npublic class LoadBalancerFeignClientInterceptor implements Interceptor {\n    private static final Logger LOGGER = EaseAgent.getLogger(LoadBalancerFeignClientInterceptor.class);\n\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n\n    }\n\n    @Override\n    public void after(MethodInfo methodInfo, Context context) {\n        try {\n            if (LOGGER.isDebugEnabled()) {\n                LOGGER.debug(\"exit method [{}]\", methodInfo.getMethod());\n            }\n            Object retValue = methodInfo.getRetValue();\n            Object clientName = ReflectionTool.invokeMethod(retValue, \"getClientName\");\n            if (clientName == null) {\n                clientName = methodInfo.getArgs()[1];\n                ReflectionTool.invokeMethod(retValue, \"setClientName\", clientName);\n            }\n        } catch (Throwable e) {\n            LOGGER.warn(\"intercept method [{}] failure\", methodInfo.getMethod(), e);\n        }\n    }\n\n\n    @Override\n    public int order() {\n        return Order.HIGH.getOrder();\n    }\n\n    @Override\n    public String getType() {\n        return \"addServiceNameHead\";\n    }\n}\n"
  },
  {
    "path": "plugins/servicename/src/main/java/com/megaease/easeagent/plugin/servicename/interceptor/RestTemplateInterceptInterceptor.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.servicename.interceptor;\n\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.logging.Logger;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.servicename.ReflectionTool;\nimport com.megaease.easeagent.plugin.servicename.advice.RestTemplateInterceptAdvice;\nimport org.springframework.util.MultiValueMap;\n\nimport java.net.URI;\n\n@AdviceTo(value = RestTemplateInterceptAdvice.class, qualifier = \"servicename\")\npublic class RestTemplateInterceptInterceptor  extends BaseServiceNameInterceptor  {\n    private static final Logger LOGGER = EaseAgent.getLogger(RestTemplateInterceptInterceptor.class);\n\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n        String method = methodInfo.getMethod();\n        try {\n            Object request = methodInfo.getArgs()[0];\n            if (LOGGER.isDebugEnabled()) {\n                LOGGER.debug(\"enter method [{}]\", method);\n            }\n            URI uri = (URI) ReflectionTool.invokeMethod(request, \"getURI\");\n            String host = uri.getHost();\n            if (ReflectionTool.hasText(host)) {\n                Object fakeHeaders = ReflectionTool.invokeMethod(request, \"getHeaders\");//org.springframework.http.HttpHeaders\n                @SuppressWarnings(\"unchecked\")\n                MultiValueMap<String, String> headers = (MultiValueMap<String, String>) ReflectionTool.extractField(fakeHeaders, \"headers\");\n                headers.add(config.getPropagateHead(), host);\n                context.injectForwardedHeaders(headers::add);\n            }\n        } catch (Throwable e) {\n            LOGGER.warn(\"intercept method [{}] failure\", method, e);\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/servicename/src/main/java/com/megaease/easeagent/plugin/servicename/interceptor/WebClientFilterInterceptor.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.servicename.interceptor;\n\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.logging.Logger;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.servicename.ReflectionTool;\nimport com.megaease.easeagent.plugin.servicename.advice.WebClientFilterAdvice;\nimport org.springframework.util.MultiValueMap;\n\nimport java.net.URI;\n\n@AdviceTo(value = WebClientFilterAdvice.class, qualifier = \"servicename\")\npublic class WebClientFilterInterceptor  extends BaseServiceNameInterceptor  {\n    private static final Logger LOGGER = EaseAgent.getLogger(WebClientFilterInterceptor.class);\n\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n        String method = methodInfo.getMethod();\n        try {\n            if (LOGGER.isDebugEnabled()) {\n                LOGGER.debug(\"enter method [{}]\", method);\n            }\n            Object request = methodInfo.getArgs()[0];\n            URI uri = (URI) ReflectionTool.invokeMethod(request, \"url\");\n            String host = uri.getHost();\n            if (ReflectionTool.hasText(host)) {\n                Object fakeHeaders = ReflectionTool.invokeMethod(request, \"headers\");//org.springframework.http.HttpHeaders\n                @SuppressWarnings(\"unchecked\")\n                MultiValueMap<String, String> headers = (MultiValueMap<String, String>) ReflectionTool.extractField(fakeHeaders, \"headers\");\n                headers.add(config.getPropagateHead(), host);\n                context.injectForwardedHeaders(headers::add);\n            }\n        } catch (Throwable e) {\n            LOGGER.warn(\"intercept method [{}] failure\", method, e);\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/servicename/src/test/java/com/megaease/easeagent/plugin/servicename/ReflectionToolTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.servicename;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\npublic class ReflectionToolTest {\n\n    @Test\n    public void invokeMethod() throws ReflectiveOperationException {\n        String name = \"testName\";\n        TestClass testClass = new TestClass(name);\n        assertEquals(name, ReflectionTool.invokeMethod(testClass, \"get\"));\n        String p = \"ttt\";\n        assertEquals(name + p, ReflectionTool.invokeMethod(testClass, \"get\", p));\n    }\n\n    @Test\n    public void extractField() throws ReflectiveOperationException {\n        String name = \"testName\";\n        TestClass testClass = new TestClass(name);\n        assertEquals(name, ReflectionTool.extractField(testClass, \"name\"));\n    }\n\n    @Test\n    public void hasText() {\n        assertFalse(ReflectionTool.hasText(null));\n        assertFalse(ReflectionTool.hasText(\"\"));\n        assertFalse(ReflectionTool.hasText(\"  \"));\n        assertTrue(ReflectionTool.hasText(\"ab\"));\n        assertTrue(ReflectionTool.hasText(\" ab \"));\n    }\n\n    class TestClass {\n        private final String name;\n\n        public TestClass(String name) {\n            this.name = name;\n        }\n\n        private String get() {\n            return name;\n        }\n\n        private String get(String p) {\n            return name + p;\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/servicename/src/test/java/com/megaease/easeagent/plugin/servicename/interceptor/BaseServiceNameInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.servicename.interceptor;\n\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.Config;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.servicename.Const;\nimport com.megaease.easeagent.plugin.servicename.ServiceNamePlugin;\nimport com.megaease.easeagent.plugin.servicename.ServiceNamePluginConfig;\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\npublic class BaseServiceNameInterceptorTest {\n\n    public static void initInterceptor(Interceptor interceptor) {\n        ServiceNamePlugin plugin = new ServiceNamePlugin();\n        IPluginConfig config = EaseAgent.getConfig(plugin.getDomain(), plugin.getNamespace(), interceptor.getType());\n        interceptor.init(config, \"\", \"\", \"\");\n    }\n\n\n    @Test\n    public void init() {\n        MockBaseServiceNameInterceptor mockBaseServiceNameInterceptor = new MockBaseServiceNameInterceptor();\n        initInterceptor(mockBaseServiceNameInterceptor);\n        ServiceNamePluginConfig serviceNamePluginConfig = mockBaseServiceNameInterceptor.getConfig();\n        assertEquals(Const.DEFAULT_PROPAGATE_HEAD, serviceNamePluginConfig.getPropagateHead());\n    }\n\n    @Test\n    public void order() {\n        MockBaseServiceNameInterceptor mockBaseServiceNameInterceptor = new MockBaseServiceNameInterceptor();\n        assertEquals(Order.HIGH.getOrder(), mockBaseServiceNameInterceptor.order());\n    }\n\n    @Test\n    public void getType() {\n        MockBaseServiceNameInterceptor mockBaseServiceNameInterceptor = new MockBaseServiceNameInterceptor();\n        assertEquals(\"addServiceNameHead\", mockBaseServiceNameInterceptor.getType());\n    }\n\n    static class MockBaseServiceNameInterceptor extends BaseServiceNameInterceptor {\n\n        @Override\n        public void before(MethodInfo methodInfo, Context context) {\n\n        }\n\n        public ServiceNamePluginConfig getConfig() {\n            return config;\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/servicename/src/test/java/com/megaease/easeagent/plugin/servicename/interceptor/CheckUtils.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.servicename.interceptor;\n\nimport com.megaease.easeagent.plugin.api.trace.Getter;\nimport com.megaease.easeagent.plugin.servicename.Const;\n\nimport static org.junit.Assert.assertEquals;\n\npublic class CheckUtils {\n    public static void check(Getter headers, String serviceName) {\n        assertEquals(serviceName, headers.header(Const.DEFAULT_PROPAGATE_HEAD));\n        assertEquals(TestConst.FORWARDED_VALUE, headers.header(TestConst.FORWARDED_NAME));\n    }\n}\n"
  },
  {
    "path": "plugins/servicename/src/test/java/com/megaease/easeagent/plugin/servicename/interceptor/FeignBlockingLoadBalancerClientInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.servicename.interceptor;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport feign.Request;\nimport feign.RequestTemplate;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport java.util.Collection;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotNull;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class FeignBlockingLoadBalancerClientInterceptorTest {\n\n\n    @Test\n    public void before() {\n        FeignBlockingLoadBalancerClientInterceptor interceptor = new FeignBlockingLoadBalancerClientInterceptor();\n        BaseServiceNameInterceptorTest.initInterceptor(interceptor);\n        EaseAgent.getContext().put(TestConst.FORWARDED_NAME, TestConst.FORWARDED_VALUE);\n\n\n        RequestTemplate requestTemplate = new RequestTemplate();\n        String host = \"TEST-SERVER\";\n        Request request = Request.create(\n            Request.HttpMethod.GET,\n            \"http://\" + host,\n            requestTemplate.headers(),\n            Request.Body.create(requestTemplate.body()),\n            requestTemplate\n        );\n\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{request}).build();\n\n        interceptor.before(methodInfo, EaseAgent.getContext());\n\n        Request newRequest = (Request) methodInfo.getArgs()[0];\n        CheckUtils.check(name -> {\n            Collection<String> head = newRequest.headers().get(name);\n            assertNotNull(head);\n            return head.iterator().next();\n        }, host);\n    }\n}\n"
  },
  {
    "path": "plugins/servicename/src/test/java/com/megaease/easeagent/plugin/servicename/interceptor/FeignLoadBalancerInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.servicename.interceptor;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.netflix.client.config.DefaultClientConfigImpl;\nimport feign.Request;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.cloud.openfeign.ribbon.MockRibbonRequest;\n\nimport java.net.URISyntaxException;\nimport java.util.Collection;\n\nimport static org.junit.Assert.assertNotNull;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class FeignLoadBalancerInterceptorTest {\n\n    @Test\n    public void before() throws URISyntaxException {\n        FeignLoadBalancerInterceptor interceptor = new FeignLoadBalancerInterceptor();\n        BaseServiceNameInterceptorTest.initInterceptor(interceptor);\n        EaseAgent.getContext().put(TestConst.FORWARDED_NAME, TestConst.FORWARDED_VALUE);\n\n        Object ribbonRequest = MockRibbonRequest.createRibbonRequest();\n        DefaultClientConfigImpl defaultClientConfig = new DefaultClientConfigImpl();\n        String serviceName = \"testServiceName\";\n        defaultClientConfig.setClientName(serviceName);\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{ribbonRequest, defaultClientConfig}).build();\n        interceptor.before(methodInfo, EaseAgent.getContext());\n\n        Request request = MockRibbonRequest.getRequest(methodInfo.getArgs()[0]);\n        CheckUtils.check(name -> {\n            Collection<String> head = request.headers().get(name);\n            assertNotNull(head);\n            return head.iterator().next();\n        }, serviceName);\n    }\n}\n"
  },
  {
    "path": "plugins/servicename/src/test/java/com/megaease/easeagent/plugin/servicename/interceptor/FilteringWebHandlerInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.servicename.interceptor;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.servicename.Const;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.cloud.gateway.route.Route;\nimport org.springframework.mock.http.server.reactive.MockServerHttpRequest;\nimport org.springframework.mock.web.server.MockServerWebExchange;\n\nimport java.net.URI;\nimport java.net.URISyntaxException;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNull;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class FilteringWebHandlerInterceptorTest {\n\n    @Test\n    public void before() throws URISyntaxException {\n        FilteringWebHandlerInterceptor interceptor = new FilteringWebHandlerInterceptor();\n        BaseServiceNameInterceptorTest.initInterceptor(interceptor);\n        EaseAgent.getContext().put(TestConst.FORWARDED_NAME, TestConst.FORWARDED_VALUE);\n\n        MockServerWebExchange mockServerWebExchange = buildMockServerWebExchange();\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{mockServerWebExchange}).build();\n        interceptor.before(methodInfo, EaseAgent.getContext());\n        assertNull(header(mockServerWebExchange, FilteringWebHandlerInterceptor.config.getPropagateHead()));\n\n        mockServerWebExchange = buildMockServerWebExchange();\n        Route route = new MockRouteBuilder().uri(new URI(\"http://127.0.0.1:8080\")).id(\"1\").build();\n        mockServerWebExchange.getAttributes().put(Const.SERVER_WEB_EXCHANGE_ROUTE_ATTRIBUTE, route);\n        methodInfo = MethodInfo.builder().args(new Object[]{mockServerWebExchange}).build();\n        interceptor.before(methodInfo, EaseAgent.getContext());\n        assertNull(header(mockServerWebExchange, FilteringWebHandlerInterceptor.config.getPropagateHead()));\n\n        mockServerWebExchange = buildMockServerWebExchange();\n        route = new MockRouteBuilder().uri(new URI(\"lb://127.0.0.1:8080\")).id(\"11\").build();\n        mockServerWebExchange.getAttributes().put(Const.SERVER_WEB_EXCHANGE_ROUTE_ATTRIBUTE, route);\n        methodInfo = MethodInfo.builder().args(new Object[]{mockServerWebExchange}).build();\n        interceptor.before(methodInfo, EaseAgent.getContext());\n        assertEquals(\"127.0.0.1\", header(mockServerWebExchange, FilteringWebHandlerInterceptor.config.getPropagateHead()));\n\n    }\n\n    private static final String header(MockServerWebExchange exchange, String name) {\n        return exchange.getRequest().getHeaders().getFirst(name);\n    }\n\n    private static final MockServerWebExchange buildMockServerWebExchange() {\n        MockServerHttpRequest mockServerHttpRequest = MockServerHttpRequest.get(\"http://127.0.0.1:8080\", \"a=b\").build();\n        return MockServerWebExchange.builder(mockServerHttpRequest).build();\n    }\n\n    static class MockRouteBuilder extends Route.Builder {\n        public MockRouteBuilder() {\n            predicate = serverWebExchange -> true;\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/servicename/src/test/java/com/megaease/easeagent/plugin/servicename/interceptor/LoadBalancerFeignClientInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.servicename.interceptor;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.netflix.client.config.DefaultClientConfigImpl;\nimport com.netflix.client.config.IClientConfig;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class LoadBalancerFeignClientInterceptorTest {\n\n    @Test\n    public void after() {\n        LoadBalancerFeignClientInterceptor loadBalancerFeignClientInterceptor = new LoadBalancerFeignClientInterceptor();\n\n        IClientConfig iClientConfig = new DefaultClientConfigImpl();\n        String clientName = \"testClientName\";\n        assertNull(iClientConfig.getClientName());\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{null, clientName}).retValue(iClientConfig).build();\n        loadBalancerFeignClientInterceptor.after(methodInfo, EaseAgent.getContext());\n        assertEquals(clientName, iClientConfig.getClientName());\n    }\n\n    @Test\n    public void order() {\n        LoadBalancerFeignClientInterceptor loadBalancerFeignClientInterceptor = new LoadBalancerFeignClientInterceptor();\n        assertEquals(Order.HIGH.getOrder(), loadBalancerFeignClientInterceptor.order());\n    }\n\n    @Test\n    public void getType() {\n        LoadBalancerFeignClientInterceptor loadBalancerFeignClientInterceptor = new LoadBalancerFeignClientInterceptor();\n        assertEquals(\"addServiceNameHead\", loadBalancerFeignClientInterceptor.getType());\n    }\n}\n"
  },
  {
    "path": "plugins/servicename/src/test/java/com/megaease/easeagent/plugin/servicename/interceptor/RestTemplateInterceptInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.servicename.interceptor;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.mock.http.client.MockClientHttpRequest;\n\nimport java.net.URI;\nimport java.net.URISyntaxException;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class RestTemplateInterceptInterceptorTest {\n\n    @Test\n    public void before() throws URISyntaxException {\n        RestTemplateInterceptInterceptor interceptor = new RestTemplateInterceptInterceptor();\n        BaseServiceNameInterceptorTest.initInterceptor(interceptor);\n        EaseAgent.getContext().put(TestConst.FORWARDED_NAME, TestConst.FORWARDED_VALUE);\n\n        MockClientHttpRequest httpRequest = new MockClientHttpRequest();\n        String host = \"TEST-SERVER\";\n        httpRequest.setURI(new URI(\"http://\" + host));\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{httpRequest}).build();\n\n        interceptor.before(methodInfo, EaseAgent.getContext());\n\n        MockClientHttpRequest newRequest = (MockClientHttpRequest) methodInfo.getArgs()[0];\n        CheckUtils.check(name -> newRequest.getHeaders().getFirst(name), host);\n\n    }\n}\n"
  },
  {
    "path": "plugins/servicename/src/test/java/com/megaease/easeagent/plugin/servicename/interceptor/TestConst.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.servicename.interceptor;\n\npublic class TestConst {\n    public static final String FORWARDED_NAME = \"X-Forwarded-For\";\n    public static final String FORWARDED_VALUE = \"testForwarded\";\n}\n"
  },
  {
    "path": "plugins/servicename/src/test/java/com/megaease/easeagent/plugin/servicename/interceptor/WebClientFilterInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.servicename.interceptor;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.web.reactive.function.client.ClientRequest;\nimport org.springframework.web.reactive.function.client.MockClientRequest;\n\nimport java.net.URI;\nimport java.net.URISyntaxException;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class WebClientFilterInterceptorTest {\n\n    @Test\n    public void before() throws URISyntaxException {\n        WebClientFilterInterceptor interceptor = new WebClientFilterInterceptor();\n        BaseServiceNameInterceptorTest.initInterceptor(interceptor);\n        EaseAgent.getContext().put(TestConst.FORWARDED_NAME, TestConst.FORWARDED_VALUE);\n\n        String host = \"TEST-SERVER\";\n        ClientRequest clientRequest = MockClientRequest.build(new URI(\"http://\" + host));\n\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{clientRequest}).build();\n\n        interceptor.before(methodInfo, EaseAgent.getContext());\n\n        ClientRequest newRequest = (ClientRequest) methodInfo.getArgs()[0];\n        CheckUtils.check(name -> newRequest.headers().getFirst(name), host);\n\n    }\n}\n"
  },
  {
    "path": "plugins/servicename/src/test/java/org/springframework/cloud/openfeign/ribbon/MockRibbonRequest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.openfeign.ribbon;\n\nimport feign.Request;\nimport feign.RequestTemplate;\n\nimport java.net.URI;\nimport java.net.URISyntaxException;\n\npublic class MockRibbonRequest {\n    public static FeignLoadBalancer.RibbonRequest createRibbonRequest() throws URISyntaxException {\n        RequestTemplate requestTemplate = new RequestTemplate();\n        String uri = \"http://127.0.0.1:8080\";\n        Request request = Request.create(\n            Request.HttpMethod.GET,\n            uri,\n            requestTemplate.headers(),\n            Request.Body.create(requestTemplate.body()),\n            requestTemplate\n        );\n        return new FeignLoadBalancer.RibbonRequest(null, request, new URI(uri));\n    }\n\n    public static Request getRequest(Object ribbonRequest) {\n        FeignLoadBalancer.RibbonRequest request = (FeignLoadBalancer.RibbonRequest) ribbonRequest;\n        return request.getRequest();\n    }\n}\n"
  },
  {
    "path": "plugins/servicename/src/test/java/org/springframework/web/reactive/function/client/MockClientRequest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.web.reactive.function.client;\n\nimport org.springframework.http.HttpMethod;\n\nimport java.net.URI;\n\npublic class MockClientRequest {\n    public static ClientRequest build(URI url) {\n        return new DefaultClientRequestBuilder(HttpMethod.GET, url).build();\n    }\n}\n"
  },
  {
    "path": "plugins/servicename/src/test/resources/mock_agent.properties",
    "content": "easeagent.progress.forwarded.headers=X-Forwarded-For\n"
  },
  {
    "path": "plugins/sofarpc/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>plugins</artifactId>\n        <groupId>com.megaease.easeagent</groupId>\n        <version>2.3.0</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>sofarpc</artifactId>\n\n    <properties>\n        <maven.compiler.source>8</maven.compiler.source>\n        <maven.compiler.target>8</maven.compiler.target>\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n        <sofa-rpc-all.version>5.3.0</sofa-rpc-all.version>\n    </properties>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>plugin-api</artifactId>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.google.code.findbugs</groupId>\n            <artifactId>jsr305</artifactId>\n            <scope>compile</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>plugin-api-mock</artifactId>\n            <version>${project.version}</version>\n            <scope>test</scope>\n        </dependency>\n\n        <dependency>\n            <groupId>com.alipay.sofa</groupId>\n            <artifactId>sofa-rpc-all</artifactId>\n            <version>${sofa-rpc-all.version}</version>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>log4j2-api</artifactId>\n        </dependency>\n    </dependencies>\n\n</project>"
  },
  {
    "path": "plugins/sofarpc/src/main/java/com/megaease/easeagent/plugin/sofarpc/SofaRpcCtxUtils.java",
    "content": "package com.megaease.easeagent.plugin.sofarpc;\n\nimport com.alipay.sofa.rpc.common.RpcConstants;\nimport com.alipay.sofa.rpc.core.request.SofaRequest;\nimport com.alipay.sofa.rpc.core.response.SofaResponse;\nimport com.alipay.sofa.rpc.filter.ConsumerInvoker;\nimport com.alipay.sofa.rpc.filter.ProviderInvoker;\nimport com.megaease.easeagent.plugin.api.Cleaner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.api.context.AsyncContext;\nimport com.megaease.easeagent.plugin.api.context.ContextUtils;\nimport com.megaease.easeagent.plugin.api.context.RequestContext;\nimport com.megaease.easeagent.plugin.api.logging.Logger;\nimport com.megaease.easeagent.plugin.api.trace.Scope;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.sofarpc.interceptor.metrics.SofaRpcMetricsBaseInterceptor;\nimport com.megaease.easeagent.plugin.sofarpc.interceptor.trace.SofaRpcTraceBaseInterceptor;\nimport com.megaease.easeagent.plugin.sofarpc.interceptor.trace.common.SofaClientTraceRequest;\nimport com.megaease.easeagent.plugin.sofarpc.interceptor.trace.common.SofaServerTraceRequest;\nimport com.megaease.easeagent.plugin.utils.SystemClock;\nimport com.megaease.easeagent.plugin.utils.common.JsonUtil;\n\npublic class SofaRpcCtxUtils {\n\tprivate static final Logger LOG = EaseAgent.getLogger(SofaRpcCtxUtils.class);\n\tpublic static final String METRICS_KEY_NAME = SofaRpcCtxUtils.class.getName() + \".METRICS_KEY_NAME\";\n\tprivate static final String METRICS_IS_ASYNC = SofaRpcCtxUtils.class.getName() + \".METRICS_IS_ASYNC\";\n\tprivate static final String TRACE_IS_ASYNC = SofaRpcCtxUtils.class.getName() + \".TRACE_IS_ASYNC\";\n\tprivate static final String BEGIN_TIME = SofaRpcCtxUtils.class.getName() + \".BEGIN_TIME\";\n\n\tpublic static final String CLIENT_REQUEST_CONTEXT_KEY = SofaRpcCtxUtils.class.getName() + \".CLIENT_REQUEST_CONTEXT\";\n\tpublic static final String SERVER_REQUEST_CONTEXT_KEY = SofaRpcCtxUtils.class.getName() + \".SERVER_REQUEST_CONTEXT\";\n\n\t/**\n\t * SOFARPC span name, e.g. TestService/test(String)\n\t *\n\t * @param sofaRequest sofa rpc request\n\t * @return\n\t */\n\tpublic static String name(SofaRequest sofaRequest) {\n\t\tStringBuilder operationName = new StringBuilder();\n\t\toperationName.append(sofaRequest.getMethod().getDeclaringClass().getSimpleName());\n\t\toperationName.append(\"/\").append(sofaRequest.getMethod().getName()).append(\"(\");\n\t\tfor (Class<?> parameterType : sofaRequest.getMethod().getParameterTypes()) {\n\t\t\toperationName.append(parameterType.getSimpleName()).append(\",\");\n\t\t}\n\n\t\tif (sofaRequest.getMethod().getParameterTypes().length > 0) {\n\t\t\toperationName.deleteCharAt(operationName.length() - 1);\n\t\t}\n\n\t\toperationName.append(\")\");\n\n\t\treturn operationName.toString();\n\t}\n\n\t/**\n\t * Format method name. e.g. test(String)\n\t *\n\t * @param sofaRequest\n\t * @return method name\n\t */\n\tpublic static String method(SofaRequest sofaRequest) {\n\t\tStringBuilder methodName = new StringBuilder();\n\t\tmethodName.append(sofaRequest.getMethod().getName())\n\t\t\t\t.append(\"(\");\n\t\tfor (Class<?> parameterType : sofaRequest.getMethod().getParameterTypes()) {\n\t\t\tmethodName.append(parameterType.getSimpleName()).append(\",\");\n\t\t}\n\n\t\tif (sofaRequest.getMethod().getParameterTypes().length > 0) {\n\t\t\tmethodName.deleteCharAt(methodName.length() - 1);\n\t\t}\n\t\tmethodName.append(\")\");\n\t\treturn methodName.toString();\n\t}\n\n\t/**\n\t * sofarpc interface signature, e.g. com.test.TestService/test(java.lang.String)\n\t *\n\t * @param sofaRequest\n\t * @return\n\t */\n\tpublic static String methodSignature(SofaRequest sofaRequest) {\n\t\tStringBuilder operationName = new StringBuilder();\n\t\toperationName.append(sofaRequest.getMethod().getDeclaringClass().getName());\n\t\toperationName.append(\"/\").append(sofaRequest.getMethod().getName()).append(\"(\");\n\t\tfor (Class<?> parameterType : sofaRequest.getMethod().getParameterTypes()) {\n\t\t\toperationName.append(parameterType.getSimpleName()).append(\",\");\n\t\t}\n\n\t\tif (sofaRequest.getMethod().getParameterTypes().length > 0) {\n\t\t\toperationName.deleteCharAt(operationName.length() - 1);\n\t\t}\n\n\t\toperationName.append(\")\");\n\n\t\treturn operationName.toString();\n\t}\n\n\t//-------------------Sofa Rpc trace operate method------------\n\n\t/**\n\t * Start client span\n\t * @param context\n\t * @param sofaRequest\n\t * @param consumerInvoker\n\t */\n\tpublic static void startClientSpan(Context context, SofaRequest sofaRequest, ConsumerInvoker consumerInvoker) {\n\t\tSofaClientTraceRequest sofaClientTraceRequest = new SofaClientTraceRequest(consumerInvoker, sofaRequest);\n\t\tRequestContext requestContext = context.clientRequest(sofaClientTraceRequest);\n\n\t\tSpan span = requestContext.span().start();\n\t\tspan.kind(sofaClientTraceRequest.kind());\n\t\tspan.name(sofaClientTraceRequest.name());\n\t\tspan.remoteServiceName(ConfigConst.Namespace.SOFARPC);\n\t\tspan.remoteIpAndPort(sofaClientTraceRequest.remoteHost(), sofaClientTraceRequest.remotePort());\n\t\tif (SofaRpcTraceBaseInterceptor.SOFA_RPC_TRACE_CONFIG.argsCollectEnabled()) {\n\t\t\tspan.tag(SofaRpcTraceTags.ARGS.name, JsonUtil.toJson(sofaRequest.getMethodArgs()));\n\t\t}\n\t\tspan.tag(SofaRpcTraceTags.CLIENT_APPLICATION.name, sofaClientTraceRequest.appName());\n\t\tspan.tag(SofaRpcTraceTags.SERVICE_UNIQUE_ID.name, sofaClientTraceRequest.uniqueId());\n\t\tspan.tag(SofaRpcTraceTags.SERVICE.name, sofaClientTraceRequest.service());\n\t\tspan.tag(SofaRpcTraceTags.METHOD.name, sofaClientTraceRequest.method());\n\n\t\tcontext.put(TRACE_IS_ASYNC, SofaRpcCtxUtils.isAsync(sofaRequest.getInvokeType()));\n\t\tcontext.put(CLIENT_REQUEST_CONTEXT_KEY, requestContext);\n\t}\n\n\t/**\n\t * Start server span\n\t * @param context interceptor context\n\t * @param providerInvoker sofa rpc provider invoker\n\t * @param sofaRequest sofa request\n\t */\n\tpublic static void startServerSpan(Context context, ProviderInvoker<?> providerInvoker, SofaRequest sofaRequest) {\n\t\tSofaServerTraceRequest sofaServerTraceRequest = new SofaServerTraceRequest(providerInvoker, sofaRequest);\n\t\tRequestContext requestContext = context.serverReceive(sofaServerTraceRequest);\n\n\t\tSpan span = requestContext.span().start();\n\t\tspan.kind(sofaServerTraceRequest.kind());\n\t\tspan.name(sofaServerTraceRequest.name());\n\t\tspan.remoteServiceName(ConfigConst.Namespace.SOFARPC);\n\t\tspan.tag(SofaRpcTraceTags.SERVER_APPLICATION.name, sofaServerTraceRequest.appName());\n\t\tspan.remoteIpAndPort(sofaServerTraceRequest.remoteHost(), sofaServerTraceRequest.remotePort());\n\t\tcontext.put(SERVER_REQUEST_CONTEXT_KEY, requestContext);\n\t}\n\n\t/**\n\t * Finish server span\n\t * @param context interceptor context\n\t * @param sofaResponse sofa response\n\t * @param throwable rpc server exception\n\t */\n\tpublic static void finishServerSpan(Context context, SofaResponse sofaResponse, Throwable throwable) {\n\t\tfinishSpan(SERVER_REQUEST_CONTEXT_KEY, context, sofaResponse, throwable);\n\t}\n\n\t/**\n\t * Sync finish client span\n\t * @param context interceptor context\n\t * @param sofaResponse sofa response\n\t * @param throwable rpc client call exception\n\t */\n\tpublic static void finishClientSpan(Context context, SofaResponse sofaResponse, Throwable throwable) {\n\t\tif (throwable != null) {\n\t\t\tSofaRpcCtxUtils.finishSpan(CLIENT_REQUEST_CONTEXT_KEY, context, null, throwable);\n\t\t\treturn;\n\t\t}\n\n\t\tboolean isAsync = context.remove(TRACE_IS_ASYNC);\n\t\tif (isAsync) {\n\t\t\treturn;\n\t\t}\n\n\t\tSofaRpcCtxUtils.finishSpan(CLIENT_REQUEST_CONTEXT_KEY, context, sofaResponse, null);\n\t}\n\n\t/**\n\t * Async finish client span\n\t * @param asyncContext interceptor async context\n\t * @param result rpc call return value\n\t */\n\tpublic static void asyncFinishClientSpan(AsyncContext asyncContext, Object result) {\n\t\ttry (Cleaner ignored = asyncContext.importToCurrent()) {\n\t\t\tContext context = EaseAgent.getContext();\n\t\t\tRequestContext requestContext = context.remove(SofaRpcCtxUtils.CLIENT_REQUEST_CONTEXT_KEY);\n\n\t\t\ttry (Scope ignoredScope = requestContext.scope()) {\n\t\t\t\tSpan span = requestContext.span();\n\t\t\t\tif (result instanceof Throwable) {\n\t\t\t\t\tspan.error((Throwable) result);\n\t\t\t\t} else if (SofaRpcTraceBaseInterceptor.SOFA_RPC_TRACE_CONFIG.resultCollectEnabled()) {\n\t\t\t\t\tspan.tag(SofaRpcTraceTags.RESULT.name, JsonUtil.toJson(result));\n\t\t\t\t}\n\t\t\t\tspan.finish();\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Finish span\n\t * @param requestContextKey get the key of request context\n\t * @param context interceptor context\n\t * @param sofaResponse sofa rpc response\n\t * @param throwable rpc call exception\n\t */\n\tprivate static void finishSpan(String requestContextKey, Context context, SofaResponse sofaResponse, Throwable throwable) {\n\t\tRequestContext requestContext = context.remove(requestContextKey);\n\t\ttry (Scope ignored = requestContext.scope()) {\n\t\t\tSpan span = requestContext.span();\n\t\t\ttry {\n\t\t\t\tif (!CLIENT_REQUEST_CONTEXT_KEY.equals(requestContextKey)) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (throwable != null) {\n\t\t\t\t\tspan.error(throwable);\n\t\t\t\t} else if (sofaResponse != null) {\n\t\t\t\t\tif (sofaResponse.isError() || sofaResponse.getAppResponse() instanceof Throwable) {\n\t\t\t\t\t\tspan.error((Throwable) sofaResponse.getAppResponse());\n\t\t\t\t\t} else if (SofaRpcTraceBaseInterceptor.SOFA_RPC_TRACE_CONFIG.resultCollectEnabled()) {\n\t\t\t\t\t\tspan.tag(SofaRpcTraceTags.RESULT.name, JsonUtil.toJson(sofaResponse.getAppResponse()));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\tspan.finish();\n\t\t\t}\n\t\t}\n\t}\n\n\t//-------------------Sofa Rpc metrics operate method------------\n\n\t/**\n\t * Start collect metrics\n\t * @param context interceptor context\n\t * @param sofaRequest sofa rpc request\n\t */\n\tpublic static void startCollectMetrics(Context context, SofaRequest sofaRequest) {\n\t\tString methodSignature = methodSignature(sofaRequest);\n\t\tcontext.put(BEGIN_TIME, SystemClock.now());\n\t\tcontext.put(METRICS_IS_ASYNC, isAsync(sofaRequest.getInvokeType()));\n\t\tcontext.put(METRICS_KEY_NAME, methodSignature);\n\t}\n\n\t/**\n\t * Finish collect metrics\n\t * @param context interceptor context\n\t * @param sofaResponse sofa rpc response\n\t * @param throwable call exception\n\t */\n\tpublic static void finishCollectMetrics(Context context, SofaResponse sofaResponse, Throwable throwable) {\n\t\tif (throwable != null) {\n\t\t\tcollectMetrics(context, sofaResponse, throwable);\n\t\t\treturn;\n\t\t}\n\n\t\tboolean isAsync = context.remove(METRICS_IS_ASYNC);\n\t\tif (isAsync) {\n\t\t\treturn;\n\t\t}\n\n\t\tcollectMetrics(context, sofaResponse, null);\n\t}\n\n\tprivate static void collectMetrics(Context context, SofaResponse sofaResponse, Throwable throwable) {\n\t\tlong duration = ContextUtils.getDuration(context, BEGIN_TIME);\n\t\tString methodSignature = context.remove(METRICS_KEY_NAME);\n\t\tif (methodSignature == null) {\n\t\t\tLOG.error(\"method signature is null\");\n\t\t\treturn;\n\t\t}\n\t\tboolean callResult = sofaResponse != null\n\t\t\t\t&& !sofaResponse.isError()\n\t\t\t\t&& !(sofaResponse.getAppResponse() instanceof Throwable)\n\t\t\t\t&& throwable == null;\n\t\tSofaRpcMetricsBaseInterceptor.SOFARPC_METRICS.collect(methodSignature, duration, callResult);\n\t}\n\n\t/**\n\t * Async finish collect metrics\n\t * @param asyncContext interceptor async context\n\t * @param result sofa rpc call return value\n\t */\n\tpublic static void asyncFinishCollectMetrics(AsyncContext asyncContext, Object result) {\n\t\ttry (Cleaner ignored = asyncContext.importToCurrent()) {\n\t\t\tString methodSignature = EaseAgent.getContext().remove(METRICS_KEY_NAME);\n\t\t\tif (methodSignature == null) {\n\t\t\t\tLOG.error(\"method signature is null\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tLong duration = ContextUtils.getDuration(EaseAgent.getContext(), BEGIN_TIME);\n\t\t\tboolean callResult = result != null && !(result instanceof Throwable);\n\t\t\tSofaRpcMetricsBaseInterceptor.SOFARPC_METRICS.collect(methodSignature, duration, callResult);\n\t\t}\n\t}\n\n\t/**\n\t * Check if the invoke type is async call\n\t * @param invokeType sofa rpc invoke type\n\t * @return returns true if the invoke type is future or callback, otherwise it returns false.\n\t */\n\tprivate static boolean isAsync(String invokeType) {\n\t\treturn RpcConstants.INVOKER_TYPE_CALLBACK.equals(invokeType) || RpcConstants.INVOKER_TYPE_FUTURE.equals(invokeType);\n\t}\n}\n"
  },
  {
    "path": "plugins/sofarpc/src/main/java/com/megaease/easeagent/plugin/sofarpc/SofaRpcMetricsTags.java",
    "content": "package com.megaease.easeagent.plugin.sofarpc;\n\npublic enum SofaRpcMetricsTags {\n\n\tCATEGORY(\"application\"),\n\tTYPE(\"sofarpc\"),\n    LABEL_NAME(\"interface\"),\n\t\t\t;\n\n\tpublic final String name;\n\n\tSofaRpcMetricsTags(String name) {\n\t\tthis.name = name;\n\t}\n}\n"
  },
  {
    "path": "plugins/sofarpc/src/main/java/com/megaease/easeagent/plugin/sofarpc/SofaRpcPlugin.java",
    "content": "package com.megaease.easeagent.plugin.sofarpc;\n\nimport com.megaease.easeagent.plugin.AgentPlugin;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\n\npublic class SofaRpcPlugin implements AgentPlugin {\n\t@Override\n\tpublic String getNamespace() {\n\t\treturn ConfigConst.Namespace.SOFARPC;\n\t}\n\n\t@Override\n\tpublic String getDomain() {\n\t\treturn ConfigConst.OBSERVABILITY;\n\t}\n}\n"
  },
  {
    "path": "plugins/sofarpc/src/main/java/com/megaease/easeagent/plugin/sofarpc/SofaRpcTraceTags.java",
    "content": "package com.megaease.easeagent.plugin.sofarpc;\n\npublic enum SofaRpcTraceTags {\n\tSERVICE(\"sofa.rpc.service\"),\n\tMETHOD(\"sofa.rpc.method\"),\n\tSERVICE_UNIQUE_ID(\"sofa.rpc.service.uniqueId\"),\n\tSERVER_APPLICATION(\"sofa.rpc.server.application\"),\n    CLIENT_APPLICATION(\"sofa.rpc.client.application\"),\n\tGROUP(\"sofa.rpc.group\"),\n\tARGS(\"sofa.rpc.args\"),\n\tRESULT(\"sofa.rpc.result\"),\n\t\t\t;\n\n\tpublic final String name;\n\n\tSofaRpcTraceTags(String name) {\n\t\tthis.name = name;\n\t}\n}\n"
  },
  {
    "path": "plugins/sofarpc/src/main/java/com/megaease/easeagent/plugin/sofarpc/adivce/BoltFutureInvokeCallbackConstructAdvice.java",
    "content": "package com.megaease.easeagent.plugin.sofarpc.adivce;\n\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.Set;\n\npublic class BoltFutureInvokeCallbackConstructAdvice implements Points {\n\tprivate static final String BOLT_FUTURE_INVOKE_CALLBACK_FULL_CLASS_NAME = \"com.alipay.sofa.rpc.message.bolt.BoltFutureInvokeCallback\";\n\tprivate static final String CONSTRUCT_METHOD_NAME = \"<init>\";\n\tprivate static final String BOLT_RESPONSE_FUTURE_FULL_CLASS_NAME = \"com.alipay.sofa.rpc.message.bolt.BoltResponseFuture\";\n\n\t@Override\n\tpublic IClassMatcher getClassMatcher() {\n\t\treturn ClassMatcher.builder()\n\t\t\t\t.isPublic()\n\t\t\t\t.hasClassName(BOLT_FUTURE_INVOKE_CALLBACK_FULL_CLASS_NAME)\n\t\t\t\t.build();\n\t}\n\n\t@Override\n\tpublic Set<IMethodMatcher> getMethodMatcher() {\n\t\treturn MethodMatcher.builder()\n\t\t\t\t.isPublic()\n\t\t\t\t.named(CONSTRUCT_METHOD_NAME)\n\t\t\t\t.argsLength(6)\n\t\t\t\t.arg(2, BOLT_RESPONSE_FUTURE_FULL_CLASS_NAME)\n\t\t\t\t.build()\n\t\t\t\t.toSet();\n\t}\n\n}\n"
  },
  {
    "path": "plugins/sofarpc/src/main/java/com/megaease/easeagent/plugin/sofarpc/adivce/ConsumerAdvice.java",
    "content": "package com.megaease.easeagent.plugin.sofarpc.adivce;\n\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.*;\n\nimport java.util.Set;\n\npublic class ConsumerAdvice implements Points {\n\tprivate static final String CONSUMER_INVOKER_CLASS_FULL_NAME = \"com.alipay.sofa.rpc.filter.ConsumerInvoker\";\n\tprivate static final String CONSUMER_INVOKER_METHOD_NAME = \"invoke\";\n\tprivate static final String CONSUMER_INVOKER_METHOD_PARAMETER_FULL_CLASS_NAME = \"com.alipay.sofa.rpc.core.request.SofaRequest\";\n\tprivate static final String CONSUMER_INVOKER_METHOD_RETURN_VALUE_FULL_CLASS_NAME = \"com.alipay.sofa.rpc.core.response.SofaResponse\";\n\n\t@Override\n\tpublic IClassMatcher getClassMatcher() {\n\t\treturn ClassMatcher.builder()\n\t\t\t\t.hasClassName(CONSUMER_INVOKER_CLASS_FULL_NAME)\n\t\t\t\t.build();\n\t}\n\n\t@Override\n\tpublic Set<IMethodMatcher> getMethodMatcher() {\n\t\treturn MethodMatcher.builder()\n\t\t\t\t.isPublic()\n\t\t\t\t.named(CONSUMER_INVOKER_METHOD_NAME)\n\t\t\t\t.arg(0,CONSUMER_INVOKER_METHOD_PARAMETER_FULL_CLASS_NAME)\n\t\t\t\t.returnType(CONSUMER_INVOKER_METHOD_RETURN_VALUE_FULL_CLASS_NAME)\n\t\t\t\t.build()\n\t\t\t\t.toSet();\n\t}\n}\n"
  },
  {
    "path": "plugins/sofarpc/src/main/java/com/megaease/easeagent/plugin/sofarpc/adivce/FutureInvokeCallbackConstructAdvice.java",
    "content": "package com.megaease.easeagent.plugin.sofarpc.adivce;\n\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.Set;\n\npublic class FutureInvokeCallbackConstructAdvice implements Points {\n\tprivate static final String BOLT_FUTURE_INVOKE_CALLBACK_FULL_CLASS_NAME = \"com.alipay.sofa.rpc.message.BoltFutureInvokeCallback\";\n\tprivate static final String CONSTRUCT_METHOD_NAME = \"<init>\";\n\tprivate static final String BOLT_RESPONSE_FUTURE_FULL_CLASS_NAME = \"com.alipay.sofa.rpc.message.BoltResponseFuture\";\n\n\t@Override\n\tpublic IClassMatcher getClassMatcher() {\n\t\treturn ClassMatcher.builder()\n\t\t\t\t.isPublic()\n\t\t\t\t.hasClassName(BOLT_FUTURE_INVOKE_CALLBACK_FULL_CLASS_NAME)\n\t\t\t\t.build();\n\t}\n\n\t@Override\n\tpublic Set<IMethodMatcher> getMethodMatcher() {\n\t\treturn MethodMatcher.builder()\n\t\t\t\t.isPublic()\n\t\t\t\t.named(CONSTRUCT_METHOD_NAME)\n\t\t\t\t.argsLength(6)\n\t\t\t\t.arg(2, BOLT_RESPONSE_FUTURE_FULL_CLASS_NAME)\n\t\t\t\t.build()\n\t\t\t\t.toSet();\n\t}\n\n}\n"
  },
  {
    "path": "plugins/sofarpc/src/main/java/com/megaease/easeagent/plugin/sofarpc/adivce/ProviderAdvice.java",
    "content": "package com.megaease.easeagent.plugin.sofarpc.adivce;\n\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.Set;\n\npublic class ProviderAdvice implements Points {\n\tprivate static final String PROVIDER_INVOKER_CLASS_FULL_NAME = \"com.alipay.sofa.rpc.filter.ProviderInvoker\";\n\tprivate static final String PROVIDER_INVOKER_METHOD_NAME = \"invoke\";\n\tprivate static final String PROVIDER_INVOKER_METHOD_PARAMETER_FULL_CLASS_NAME = \"com.alipay.sofa.rpc.core.request.SofaRequest\";\n\tprivate static final String PROVIDER_INVOKER_METHOD_RETURN_VALUE_FULL_CLASS_NAME = \"com.alipay.sofa.rpc.core.response.SofaResponse\";\n\n\t@Override\n\tpublic IClassMatcher getClassMatcher() {\n\t\treturn ClassMatcher.builder()\n\t\t\t\t.hasClassName(PROVIDER_INVOKER_CLASS_FULL_NAME)\n\t\t\t\t.build();\n\t}\n\n\t@Override\n\tpublic Set<IMethodMatcher> getMethodMatcher() {\n\t\treturn MethodMatcher.builder()\n\t\t\t\t.isPublic()\n\t\t\t\t.named(PROVIDER_INVOKER_METHOD_NAME)\n\t\t\t\t.arg(0,PROVIDER_INVOKER_METHOD_PARAMETER_FULL_CLASS_NAME)\n\t\t\t\t.returnType(PROVIDER_INVOKER_METHOD_RETURN_VALUE_FULL_CLASS_NAME)\n\t\t\t\t.build()\n\t\t\t\t.toSet();\n\t}\n}\n"
  },
  {
    "path": "plugins/sofarpc/src/main/java/com/megaease/easeagent/plugin/sofarpc/adivce/ResponseCallbackAdvice.java",
    "content": "package com.megaease.easeagent.plugin.sofarpc.adivce;\n\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.Set;\n\npublic class ResponseCallbackAdvice implements Points {\n\tprivate static final String BOLT_INVOKER_CALLBACK_FULL_CLASS_NAME = \"com.alipay.sofa.rpc.message.BoltInvokerCallback\";\n\tprivate static final String GREATER_THAN_VERSION_5_3_0_BOLT_INVOKER_CALLBACK_FULL_CLASS_NAME = \"com.alipay.sofa.rpc.message.bolt.BoltInvokerCallback\";\n\tprivate static final String CONSTRUCT_METHOD_NAME = \"<init>\";\n\tprivate static final String SOFA_RESPONSE_CALLBACK_FULL_CLASS_NAME = \"com.alipay.sofa.rpc.core.invoke.SofaResponseCallback\";\n\n\n\t@Override\n\tpublic IClassMatcher getClassMatcher() {\n\t\treturn ClassMatcher.builder()\n\t\t\t\t.isPublic()\n\t\t\t\t.hasClassName(BOLT_INVOKER_CALLBACK_FULL_CLASS_NAME)\n\t\t\t\t.or()\n\t\t\t\t.isPublic()\n\t\t\t\t.hasClassName(GREATER_THAN_VERSION_5_3_0_BOLT_INVOKER_CALLBACK_FULL_CLASS_NAME)\n\t\t\t\t.build();\n\t}\n\n\t@Override\n\tpublic Set<IMethodMatcher> getMethodMatcher() {\n\t\treturn MethodMatcher.builder()\n\t\t\t\t.isPublic()\n\t\t\t\t.named(CONSTRUCT_METHOD_NAME)\n\t\t\t\t.argsLength(6)\n\t\t\t\t.arg(2, SOFA_RESPONSE_CALLBACK_FULL_CLASS_NAME)\n\t\t\t\t.build()\n\t\t\t\t.toSet();\n\t}\n}\n"
  },
  {
    "path": "plugins/sofarpc/src/main/java/com/megaease/easeagent/plugin/sofarpc/adivce/ResponseFutureAdvice.java",
    "content": "package com.megaease.easeagent.plugin.sofarpc.adivce;\n\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.Set;\n\npublic class ResponseFutureAdvice implements Points {\n\tprivate static final String ABSTRACT_RESPONSE_FUTURE_FULL_CLASS_NAME = \"com.alipay.sofa.rpc.message.AbstractResponseFuture\";\n\tprivate static final String BOLT_RESPONSE_FUTURE_FULL_CLASS_NAME = \"com.alipay.sofa.rpc.message.BoltResponseFuture\";\n\tprivate static final String SET_SUCCESS_METHOD_NAME = \"setSuccess\";\n\tprivate static final String SET_FAILURE_METHOD_NAME = \"setFailure\";\n\n\t@Override\n\tpublic IClassMatcher getClassMatcher() {\n\t\treturn ClassMatcher.builder()\n\t\t\t\t.isPublic()\n\t\t\t\t.isAbstract()\n\t\t\t\t.hasClassName(ABSTRACT_RESPONSE_FUTURE_FULL_CLASS_NAME)\n\t\t\t\t.or()\n\t\t\t\t.isPublic()\n\t\t\t\t.hasClassName(BOLT_RESPONSE_FUTURE_FULL_CLASS_NAME)\n\t\t\t\t.build();\n\t}\n\n\t@Override\n\tpublic Set<IMethodMatcher> getMethodMatcher() {\n\t\treturn MethodMatcher.builder()\n\t\t\t\t.named(SET_SUCCESS_METHOD_NAME)\n\t\t\t\t.argsLength(1)\n\t\t\t\t.arg(0,Object.class.getName())\n\t\t\t\t.or()\n\t\t\t\t.named(SET_FAILURE_METHOD_NAME)\n\t\t\t\t.argsLength(1)\n\t\t\t\t.arg(0, Throwable.class.getName())\n\t\t\t\t.build()\n\t\t\t\t.toSet();\n\t}\n\n\t@Override\n\tpublic boolean isAddDynamicField() {\n\t\treturn true;\n\t}\n}\n"
  },
  {
    "path": "plugins/sofarpc/src/main/java/com/megaease/easeagent/plugin/sofarpc/config/SofaRpcTraceConfig.java",
    "content": "package com.megaease.easeagent.plugin.sofarpc.config;\n\nimport com.megaease.easeagent.plugin.api.config.AutoRefreshConfigSupplier;\nimport com.megaease.easeagent.plugin.api.config.AutoRefreshPluginConfig;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.utils.Pair;\n\npublic class SofaRpcTraceConfig implements AutoRefreshPluginConfig {\n\n\tprivate final Pair<String, Boolean> argsCollectEnabledPair = new Pair<>(\"args.collect.enabled\", false);\n    private final Pair<String, Boolean> resultCollectEnabledPair = new Pair<>(\"result.collect.enabled\", false);\n\tprivate volatile boolean argsCollectEnabled = argsCollectEnabledPair.getValue();\n    private volatile boolean resultCollectEnabled = resultCollectEnabledPair.getValue();\n\n\tpublic boolean argsCollectEnabled() {\n\t\treturn argsCollectEnabled;\n\t}\n\n    public boolean resultCollectEnabled() {\n        return resultCollectEnabled;\n    }\n\n    public static final AutoRefreshConfigSupplier<SofaRpcTraceConfig> SUPPLIER = new AutoRefreshConfigSupplier<SofaRpcTraceConfig>() {\n\t\t@Override\n\t\tpublic SofaRpcTraceConfig newInstance() {\n\t\t\treturn new SofaRpcTraceConfig();\n\t\t}\n\t};\n\n\t@Override\n\tpublic void onChange(IPluginConfig oldConfig, IPluginConfig newConfig) {\n\t\tString argsCollectEnabled = newConfig.getString(argsCollectEnabledPair.getKey());\n\t\tthis.argsCollectEnabled = Boolean.parseBoolean(argsCollectEnabled);\n\n        String resultCollectEnabled = newConfig.getString(resultCollectEnabledPair.getKey());\n        this.resultCollectEnabled = Boolean.parseBoolean(resultCollectEnabled);\n    }\n}\n"
  },
  {
    "path": "plugins/sofarpc/src/main/java/com/megaease/easeagent/plugin/sofarpc/interceptor/initalize/SofaRpcFutureInvokeCallbackConstructInterceptor.java",
    "content": "package com.megaease.easeagent.plugin.sofarpc.interceptor.initalize;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.RequestContext;\nimport com.megaease.easeagent.plugin.api.trace.Scope;\nimport com.megaease.easeagent.plugin.field.AgentDynamicFieldAccessor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.sofarpc.SofaRpcCtxUtils;\nimport com.megaease.easeagent.plugin.sofarpc.SofaRpcPlugin;\nimport com.megaease.easeagent.plugin.sofarpc.adivce.FutureInvokeCallbackConstructAdvice;\nimport com.megaease.easeagent.plugin.sofarpc.adivce.BoltFutureInvokeCallbackConstructAdvice;\nimport com.megaease.easeagent.plugin.sofarpc.interceptor.trace.SofaRpcTraceBaseInterceptor;\n\n@AdviceTo(value = FutureInvokeCallbackConstructAdvice.class, plugin = SofaRpcPlugin.class)\n@AdviceTo(value = BoltFutureInvokeCallbackConstructAdvice.class, plugin = SofaRpcPlugin.class)\npublic class SofaRpcFutureInvokeCallbackConstructInterceptor extends SofaRpcTraceBaseInterceptor {\n\n\t@Override\n\tpublic void before(MethodInfo methodInfo, Context context) {\n\t\tRequestContext requestContext = context.get(SofaRpcCtxUtils.CLIENT_REQUEST_CONTEXT_KEY);\n\t\tString methodSignature = context.get(SofaRpcCtxUtils.METRICS_KEY_NAME);\n\t\tif (requestContext != null) {\n\t\t\ttry (Scope ignore = requestContext.scope()) {\n\t\t\t\tAgentDynamicFieldAccessor.setDynamicFieldValue(methodInfo.getArgs()[2], context.exportAsync());\n\t\t\t}\n\t\t} else if (methodSignature != null) {\n\t\t\tAgentDynamicFieldAccessor.setDynamicFieldValue(methodInfo.getArgs()[2], context.exportAsync());\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "plugins/sofarpc/src/main/java/com/megaease/easeagent/plugin/sofarpc/interceptor/metrics/SofaRpcMetrics.java",
    "content": "package com.megaease.easeagent.plugin.sofarpc.interceptor.metrics;\n\nimport com.megaease.easeagent.plugin.api.metric.*;\nimport com.megaease.easeagent.plugin.api.metric.name.MetricField;\nimport com.megaease.easeagent.plugin.api.metric.name.MetricSubType;\nimport com.megaease.easeagent.plugin.api.metric.name.MetricValueFetcher;\nimport com.megaease.easeagent.plugin.api.metric.name.NameFactory;\nimport com.megaease.easeagent.plugin.tools.metrics.LastMinutesCounterGauge;\nimport com.megaease.easeagent.plugin.utils.ImmutableMap;\n\nimport java.util.concurrent.TimeUnit;\n\npublic class SofaRpcMetrics extends ServiceMetric {\n\n\tpublic static final ServiceMetricSupplier<SofaRpcMetrics> SOFARPC_METRICS_SUPPLIER = new ServiceMetricSupplier<SofaRpcMetrics>() {\n\t\t@Override\n\t\tpublic NameFactory newNameFactory() {\n\t\t\treturn nameFactory();\n\t\t}\n\n\t\t@Override\n\t\tpublic SofaRpcMetrics newInstance(MetricRegistry metricRegistry, NameFactory nameFactory) {\n\t\t\treturn new SofaRpcMetrics(metricRegistry, nameFactory);\n\t\t}\n\t};\n\n\tpublic SofaRpcMetrics(MetricRegistry metricRegistry, NameFactory nameFactory) {\n\t\tsuper(metricRegistry, nameFactory);\n\t}\n\n\tprivate static NameFactory nameFactory() {\n\t\treturn NameFactory.createBuilder()\n\t\t\t\t.timerType(MetricSubType.DEFAULT,\n\t\t\t\t\t\tImmutableMap.<MetricField, MetricValueFetcher>builder()\n\t\t\t\t\t\t\t\t.put(MetricField.MIN_EXECUTION_TIME, MetricValueFetcher.SnapshotMinValue)\n\t\t\t\t\t\t\t\t.put(MetricField.MAX_EXECUTION_TIME, MetricValueFetcher.SnapshotMaxValue)\n\t\t\t\t\t\t\t\t.put(MetricField.MEAN_EXECUTION_TIME, MetricValueFetcher.SnapshotMeanValue)\n\t\t\t\t\t\t\t\t.put(MetricField.P25_EXECUTION_TIME, MetricValueFetcher.Snapshot25Percentile)\n\t\t\t\t\t\t\t\t.put(MetricField.P50_EXECUTION_TIME, MetricValueFetcher.Snapshot50PercentileValue)\n\t\t\t\t\t\t\t\t.put(MetricField.P75_EXECUTION_TIME, MetricValueFetcher.Snapshot75PercentileValue)\n\t\t\t\t\t\t\t\t.put(MetricField.P95_EXECUTION_TIME, MetricValueFetcher.Snapshot95PercentileValue)\n\t\t\t\t\t\t\t\t.put(MetricField.P98_EXECUTION_TIME, MetricValueFetcher.Snapshot98PercentileValue)\n\t\t\t\t\t\t\t\t.put(MetricField.P99_EXECUTION_TIME, MetricValueFetcher.Snapshot99PercentileValue)\n\t\t\t\t\t\t\t\t.put(MetricField.P999_EXECUTION_TIME, MetricValueFetcher.Snapshot999PercentileValue)\n\t\t\t\t\t\t\t\t.build())\n\t\t\t\t.meterType(MetricSubType.DEFAULT,\n\t\t\t\t\t\tImmutableMap.<MetricField, MetricValueFetcher>builder()\n\t\t\t\t\t\t\t\t.put(MetricField.M1_RATE, MetricValueFetcher.MeteredM1Rate)\n\t\t\t\t\t\t\t\t.put(MetricField.M5_RATE, MetricValueFetcher.MeteredM5Rate)\n\t\t\t\t\t\t\t\t.put(MetricField.M15_RATE, MetricValueFetcher.MeteredM15Rate)\n\t\t\t\t\t\t\t\t.put(MetricField.MEAN_RATE, MetricValueFetcher.MeteredMeanRate)\n\t\t\t\t\t\t\t\t.build())\n\t\t\t\t.meterType(MetricSubType.ERROR,\n\t\t\t\t\t\tImmutableMap.<MetricField, MetricValueFetcher>builder()\n\t\t\t\t\t\t\t\t.put(MetricField.M1_ERROR_RATE, MetricValueFetcher.MeteredM1Rate)\n\t\t\t\t\t\t\t\t.put(MetricField.M5_ERROR_RATE, MetricValueFetcher.MeteredM5Rate)\n\t\t\t\t\t\t\t\t.put(MetricField.M15_ERROR_RATE, MetricValueFetcher.MeteredM15Rate)\n\t\t\t\t\t\t\t\t.build())\n\t\t\t\t.counterType(MetricSubType.DEFAULT,\n\t\t\t\t\t\tImmutableMap.<MetricField, MetricValueFetcher>builder()\n\t\t\t\t\t\t\t\t.put(MetricField.EXECUTION_COUNT, MetricValueFetcher.CountingCount)\n\t\t\t\t\t\t\t\t.build())\n\t\t\t\t.counterType(MetricSubType.ERROR,\n\t\t\t\t\t\tImmutableMap.<MetricField, MetricValueFetcher>builder()\n\t\t\t\t\t\t\t\t.put(MetricField.EXECUTION_ERROR_COUNT, MetricValueFetcher.CountingCount)\n\t\t\t\t\t\t\t\t.build())\n\t\t\t\t.gaugeType(MetricSubType.DEFAULT,\n\t\t\t\t\t\tImmutableMap.<MetricField, MetricValueFetcher>builder()\n\t\t\t\t\t\t\t\t.build())\n\t\t\t\t.build();\n\t}\n\n\tpublic void collect(String key, long duration, boolean success) {\n\n\t\tthis.timer(key, MetricSubType.DEFAULT).update(duration, TimeUnit.MILLISECONDS);\n\t\tfinal Meter meter = this.meter(key, MetricSubType.DEFAULT);\n\t\tfinal Counter counter = this.counter(key, MetricSubType.DEFAULT);\n\t\tmeter.mark();\n\t\tcounter.inc();\n\n\t\tif (!success) {\n\t\t\tfinal Meter errorMeter = this.meter(key, MetricSubType.ERROR);\n\t\t\tfinal Counter errorCounter = this.counter(key, MetricSubType.ERROR);\n\t\t\terrorMeter.mark();\n\t\t\terrorCounter.inc();\n\t\t}\n\n\t\tthis.gauge(key, MetricSubType.DEFAULT, new MetricSupplier<Gauge>() {\n\t\t\t@Override\n\t\t\tpublic Gauge<LastMinutesCounterGauge> newMetric() {\n\t\t\t\treturn () -> LastMinutesCounterGauge.builder()\n\t\t\t\t\t\t.m1Count((long) (meter.getOneMinuteRate() * 60))\n\t\t\t\t\t\t.m5Count((long) (meter.getFiveMinuteRate() * 60 * 5))\n\t\t\t\t\t\t.m15Count((long) (meter.getFifteenMinuteRate() * 60 * 15))\n\t\t\t\t\t\t.build();\n\t\t\t}\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "plugins/sofarpc/src/main/java/com/megaease/easeagent/plugin/sofarpc/interceptor/metrics/SofaRpcMetricsBaseInterceptor.java",
    "content": "package com.megaease.easeagent.plugin.sofarpc.interceptor.metrics;\n\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.metric.ServiceMetricRegistry;\nimport com.megaease.easeagent.plugin.api.metric.name.Tags;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.NonReentrantInterceptor;\nimport com.megaease.easeagent.plugin.sofarpc.SofaRpcMetricsTags;\n\npublic abstract class SofaRpcMetricsBaseInterceptor implements NonReentrantInterceptor {\n    public static volatile SofaRpcMetrics SOFARPC_METRICS;\n\n    @Override\n    public String getType() {\n        return Order.METRIC.getName();\n    }\n\n    @Override\n    public int order() {\n        return Order.METRIC.getOrder();\n    }\n\n    @Override\n    public void init(IPluginConfig config, String className, String methodName, String methodDescriptor) {\n        Tags tags = new Tags(SofaRpcMetricsTags.CATEGORY.name, SofaRpcMetricsTags.TYPE.name, SofaRpcMetricsTags.LABEL_NAME.name);\n        SOFARPC_METRICS = ServiceMetricRegistry.getOrCreate(config, tags, SofaRpcMetrics.SOFARPC_METRICS_SUPPLIER);\n    }\n}\n"
  },
  {
    "path": "plugins/sofarpc/src/main/java/com/megaease/easeagent/plugin/sofarpc/interceptor/metrics/callback/SofaRpcResponseCallbackMetrics.java",
    "content": "package com.megaease.easeagent.plugin.sofarpc.interceptor.metrics.callback;\n\nimport com.alipay.sofa.rpc.core.exception.SofaRpcException;\nimport com.alipay.sofa.rpc.core.invoke.SofaResponseCallback;\nimport com.alipay.sofa.rpc.core.request.RequestBase;\nimport com.megaease.easeagent.plugin.api.context.AsyncContext;\nimport com.megaease.easeagent.plugin.sofarpc.SofaRpcCtxUtils;\n\npublic class SofaRpcResponseCallbackMetrics implements SofaResponseCallback<Object> {\n\tprivate final SofaResponseCallback<?> sofaResponseCallback;\n\tprivate final AsyncContext asyncContext;\n\n\tpublic SofaRpcResponseCallbackMetrics(SofaResponseCallback<?> sofaResponseCallback, AsyncContext asyncContext) {\n\t\tthis.sofaResponseCallback = sofaResponseCallback;\n\t\tthis.asyncContext = asyncContext;\n\t}\n\n\t@Override\n\tpublic void onAppResponse(Object appResponse, String methodName, RequestBase request) {\n\t\ttry {\n\t\t\tthis.sofaResponseCallback.onAppResponse(appResponse, methodName, request);\n\t\t} finally {\n\t\t\tSofaRpcCtxUtils.asyncFinishCollectMetrics(this.asyncContext, appResponse);\n\t\t}\n\t}\n\n\t@Override\n\tpublic void onAppException(Throwable throwable, String methodName, RequestBase request) {\n\t\ttry {\n\t\t\tthis.sofaResponseCallback.onAppException(throwable, methodName, request);\n\t\t} finally {\n\t\t\tSofaRpcCtxUtils.asyncFinishCollectMetrics(this.asyncContext, throwable);\n\t\t}\n\t}\n\n\t@Override\n\tpublic void onSofaException(SofaRpcException sofaException, String methodName, RequestBase request) {\n\t\ttry {\n\t\t\tthis.sofaResponseCallback.onSofaException(sofaException, methodName, request);\n\t\t} finally {\n\t\t\tSofaRpcCtxUtils.asyncFinishCollectMetrics(this.asyncContext, sofaException);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "plugins/sofarpc/src/main/java/com/megaease/easeagent/plugin/sofarpc/interceptor/metrics/callback/SofaRpcResponseCallbackMetricsInterceptor.java",
    "content": "package com.megaease.easeagent.plugin.sofarpc.interceptor.metrics.callback;\n\nimport com.alipay.sofa.rpc.core.invoke.SofaResponseCallback;\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.sofarpc.SofaRpcPlugin;\nimport com.megaease.easeagent.plugin.sofarpc.adivce.ResponseCallbackAdvice;\nimport com.megaease.easeagent.plugin.sofarpc.interceptor.metrics.SofaRpcMetricsBaseInterceptor;\n\n@AdviceTo(value = ResponseCallbackAdvice.class, plugin = SofaRpcPlugin.class)\npublic class SofaRpcResponseCallbackMetricsInterceptor extends SofaRpcMetricsBaseInterceptor {\n\n\t@Override\n\tpublic void before(MethodInfo methodInfo, Context context) {\n\t\tSofaResponseCallback<?> sofaResponseCallback = (SofaResponseCallback<?>) methodInfo.getArgs()[2];\n\t\tmethodInfo.changeArg(2, new SofaRpcResponseCallbackMetrics(sofaResponseCallback, context.exportAsync()));\n\t}\n\n}\n"
  },
  {
    "path": "plugins/sofarpc/src/main/java/com/megaease/easeagent/plugin/sofarpc/interceptor/metrics/common/SofaRpcMetricsInterceptor.java",
    "content": "package com.megaease.easeagent.plugin.sofarpc.interceptor.metrics.common;\n\nimport com.alipay.sofa.rpc.core.request.SofaRequest;\nimport com.alipay.sofa.rpc.core.response.SofaResponse;\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.sofarpc.SofaRpcCtxUtils;\nimport com.megaease.easeagent.plugin.sofarpc.SofaRpcPlugin;\nimport com.megaease.easeagent.plugin.sofarpc.adivce.ConsumerAdvice;\nimport com.megaease.easeagent.plugin.sofarpc.interceptor.metrics.SofaRpcMetricsBaseInterceptor;\n\n@AdviceTo(value = ConsumerAdvice.class, plugin = SofaRpcPlugin.class)\npublic class SofaRpcMetricsInterceptor extends SofaRpcMetricsBaseInterceptor {\n\n\t@Override\n\tpublic void before(MethodInfo methodInfo, Context context) {\n\t\tSofaRequest sofaRequest = (SofaRequest) methodInfo.getArgs()[0];\n\t\tSofaRpcCtxUtils.startCollectMetrics(context, sofaRequest);\n\t}\n\n\n\t@Override\n\tpublic void after(MethodInfo methodInfo, Context context) {\n\t\tSofaResponse retValue = (SofaResponse) methodInfo.getRetValue();\n\t\tSofaRpcCtxUtils.finishCollectMetrics(context, retValue, methodInfo.getThrowable());\n\t}\n}\n"
  },
  {
    "path": "plugins/sofarpc/src/main/java/com/megaease/easeagent/plugin/sofarpc/interceptor/metrics/future/SofaRpcResponseFutureMetricsInterceptor.java",
    "content": "package com.megaease.easeagent.plugin.sofarpc.interceptor.metrics.future;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.AsyncContext;\nimport com.megaease.easeagent.plugin.field.AgentDynamicFieldAccessor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.sofarpc.SofaRpcCtxUtils;\nimport com.megaease.easeagent.plugin.sofarpc.SofaRpcPlugin;\nimport com.megaease.easeagent.plugin.sofarpc.adivce.ResponseFutureAdvice;\nimport com.megaease.easeagent.plugin.sofarpc.interceptor.metrics.SofaRpcMetricsBaseInterceptor;\n\n@AdviceTo(value = ResponseFutureAdvice.class, plugin = SofaRpcPlugin.class)\npublic class SofaRpcResponseFutureMetricsInterceptor extends SofaRpcMetricsBaseInterceptor {\n\n\t@Override\n\tpublic void after(MethodInfo methodInfo, Context context) {\n\t\tObject result = methodInfo.getArgs()[0];\n\t\tAsyncContext asyncContext = AgentDynamicFieldAccessor.getDynamicFieldValue(methodInfo.getInvoker());\n\t\tSofaRpcCtxUtils.asyncFinishCollectMetrics(asyncContext, result);\n\t}\n}\n"
  },
  {
    "path": "plugins/sofarpc/src/main/java/com/megaease/easeagent/plugin/sofarpc/interceptor/trace/SofaRpcTraceBaseInterceptor.java",
    "content": "package com.megaease.easeagent.plugin.sofarpc.interceptor.trace;\n\nimport com.megaease.easeagent.plugin.api.config.AutoRefreshPluginConfigRegistry;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.NonReentrantInterceptor;\nimport com.megaease.easeagent.plugin.sofarpc.config.SofaRpcTraceConfig;\n\npublic abstract class SofaRpcTraceBaseInterceptor implements NonReentrantInterceptor {\n\tpublic static volatile SofaRpcTraceConfig SOFA_RPC_TRACE_CONFIG;\n\n\t@Override\n\tpublic String getType() {\n\t\treturn Order.TRACING.getName();\n\t}\n\n\t@Override\n\tpublic int order() {\n\t\treturn Order.TRACING.getOrder();\n\t}\n\n\t@Override\n\tpublic void init(IPluginConfig config, String className, String methodName, String methodDescriptor) {\n\t\tSOFA_RPC_TRACE_CONFIG = AutoRefreshPluginConfigRegistry.getOrCreate(ConfigConst.OBSERVABILITY, ConfigConst.Namespace.SOFARPC, this.getType(), SofaRpcTraceConfig.SUPPLIER);\n\t}\n\n}\n"
  },
  {
    "path": "plugins/sofarpc/src/main/java/com/megaease/easeagent/plugin/sofarpc/interceptor/trace/callback/SofaRpcResponseCallbackTrace.java",
    "content": "package com.megaease.easeagent.plugin.sofarpc.interceptor.trace.callback;\n\nimport com.alipay.sofa.rpc.core.exception.SofaRpcException;\nimport com.alipay.sofa.rpc.core.invoke.SofaResponseCallback;\nimport com.alipay.sofa.rpc.core.request.RequestBase;\nimport com.megaease.easeagent.plugin.api.context.AsyncContext;\nimport com.megaease.easeagent.plugin.sofarpc.SofaRpcCtxUtils;\n\npublic class SofaRpcResponseCallbackTrace implements SofaResponseCallback<Object> {\n\tprivate final SofaResponseCallback<?> sofaResponseCallback;\n\tprivate final AsyncContext asyncContext;\n\n\tpublic SofaRpcResponseCallbackTrace(SofaResponseCallback<?> sofaResponseCallback, AsyncContext asyncContext) {\n\t\tthis.sofaResponseCallback = sofaResponseCallback;\n\t\tthis.asyncContext = asyncContext;\n\t}\n\n\t@Override\n\tpublic void onAppResponse(Object appResponse, String methodName, RequestBase request) {\n\t\ttry {\n\t\t\tthis.sofaResponseCallback.onAppResponse(appResponse, methodName, request);\n\t\t} finally {\n\t\t\tSofaRpcCtxUtils.asyncFinishClientSpan(this.asyncContext, appResponse);\n\t\t}\n\t}\n\n\t@Override\n\tpublic void onAppException(Throwable throwable, String methodName, RequestBase request) {\n\t\ttry {\n\t\t\tthis.sofaResponseCallback.onAppException(throwable, methodName, request);\n\t\t} finally {\n\t\t\tSofaRpcCtxUtils.asyncFinishClientSpan(this.asyncContext, throwable);\n\t\t}\n\t}\n\n\t@Override\n\tpublic void onSofaException(SofaRpcException sofaException, String methodName, RequestBase request) {\n\t\ttry {\n\t\t\tthis.sofaResponseCallback.onSofaException(sofaException, methodName, request);\n\t\t} finally {\n\t\t\tSofaRpcCtxUtils.asyncFinishClientSpan(this.asyncContext, sofaException);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "plugins/sofarpc/src/main/java/com/megaease/easeagent/plugin/sofarpc/interceptor/trace/callback/SofaRpcResponseCallbackTraceInterceptor.java",
    "content": "package com.megaease.easeagent.plugin.sofarpc.interceptor.trace.callback;\n\nimport com.alipay.sofa.rpc.core.invoke.SofaResponseCallback;\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.AsyncContext;\nimport com.megaease.easeagent.plugin.api.context.RequestContext;\nimport com.megaease.easeagent.plugin.api.trace.Scope;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.sofarpc.SofaRpcCtxUtils;\nimport com.megaease.easeagent.plugin.sofarpc.SofaRpcPlugin;\nimport com.megaease.easeagent.plugin.sofarpc.adivce.ResponseCallbackAdvice;\nimport com.megaease.easeagent.plugin.sofarpc.interceptor.trace.SofaRpcTraceBaseInterceptor;\n\n@AdviceTo(value = ResponseCallbackAdvice.class, plugin = SofaRpcPlugin.class)\npublic class SofaRpcResponseCallbackTraceInterceptor extends SofaRpcTraceBaseInterceptor {\n\n\t@Override\n\tpublic void before(MethodInfo methodInfo, Context context) {\n\t\tSofaResponseCallback<?> sofaResponseCallback = (SofaResponseCallback<?>) methodInfo.getArgs()[2];\n\n\t\tRequestContext requestContext = context.get(SofaRpcCtxUtils.CLIENT_REQUEST_CONTEXT_KEY);\n\t\ttry (Scope ignore = requestContext.scope()) {\n\t\t\tAsyncContext asyncContext = context.exportAsync();\n\t\t\tmethodInfo.changeArg(2, new SofaRpcResponseCallbackTrace(sofaResponseCallback, asyncContext));\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "plugins/sofarpc/src/main/java/com/megaease/easeagent/plugin/sofarpc/interceptor/trace/common/SofaClientTraceRequest.java",
    "content": "package com.megaease.easeagent.plugin.sofarpc.interceptor.trace.common;\n\nimport com.alipay.sofa.rpc.context.RpcInternalContext;\nimport com.alipay.sofa.rpc.core.request.SofaRequest;\nimport com.alipay.sofa.rpc.filter.ConsumerInvoker;\nimport com.megaease.easeagent.plugin.api.trace.Request;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.sofarpc.SofaRpcCtxUtils;\n\npublic class SofaClientTraceRequest implements Request {\n\tprivate final ConsumerInvoker consumerInvoker;\n\tprivate final SofaRequest sofaRequest;\n\n\tpublic SofaClientTraceRequest(ConsumerInvoker consumerInvoker, SofaRequest sofaRequest) {\n\t\tthis.consumerInvoker = consumerInvoker;\n\t\tthis.sofaRequest = sofaRequest;\n\t}\n\n\t@Override\n\tpublic Span.Kind kind() {\n\t\treturn Span.Kind.CLIENT;\n\t}\n\n\t@Override\n\tpublic String header(String name) {\n\t\treturn (String) this.sofaRequest.getRequestProp(name);\n\t}\n\n\t@Override\n\tpublic String name() {\n\t\treturn SofaRpcCtxUtils.name(this.sofaRequest);\n\t}\n\n\t@Override\n\tpublic boolean cacheScope() {\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic void setHeader(String name, String value) {\n\t\tthis.sofaRequest.addRequestProp(name,value);\n\t}\n\n\tpublic String service() {\n\t\treturn this.sofaRequest.getInterfaceName();\n\t}\n\n\tpublic String method() {\n\t\treturn SofaRpcCtxUtils.method(this.sofaRequest);\n\t}\n\n\tpublic String uniqueId() {\n\t\treturn consumerInvoker.getConfig().getUniqueId();\n\t}\n\n\tpublic String appName() {\n\t\treturn consumerInvoker.getConfig().getAppName();\n\t}\n\n\n\tpublic String remoteHost() {\n\t\treturn RpcInternalContext.getContext().getProviderInfo().getHost();\n\t}\n\n\tpublic int remotePort() {\n\t\treturn RpcInternalContext.getContext().getProviderInfo().getPort();\n\t}\n}\n"
  },
  {
    "path": "plugins/sofarpc/src/main/java/com/megaease/easeagent/plugin/sofarpc/interceptor/trace/common/SofaRpcConsumerTraceInterceptor.java",
    "content": "package com.megaease.easeagent.plugin.sofarpc.interceptor.trace.common;\n\nimport com.alipay.sofa.rpc.core.request.SofaRequest;\nimport com.alipay.sofa.rpc.core.response.SofaResponse;\nimport com.alipay.sofa.rpc.filter.ConsumerInvoker;\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.sofarpc.SofaRpcCtxUtils;\nimport com.megaease.easeagent.plugin.sofarpc.SofaRpcPlugin;\nimport com.megaease.easeagent.plugin.sofarpc.adivce.ConsumerAdvice;\nimport com.megaease.easeagent.plugin.sofarpc.interceptor.trace.SofaRpcTraceBaseInterceptor;\n\n@AdviceTo(value = ConsumerAdvice.class, plugin = SofaRpcPlugin.class)\npublic class SofaRpcConsumerTraceInterceptor extends SofaRpcTraceBaseInterceptor {\n\n\t@Override\n\tpublic void before(MethodInfo methodInfo, Context context) {\n\t\tObject[] args = methodInfo.getArgs();\n\t\tSofaRequest sofaRequest = (SofaRequest) args[0];\n\t\tConsumerInvoker consumerInvoker = (ConsumerInvoker) methodInfo.getInvoker();\n\t\tSofaRpcCtxUtils.startClientSpan(context, sofaRequest, consumerInvoker);\n\t}\n\n\n\t@Override\n\tpublic void after(MethodInfo methodInfo, Context context) {\n\t\tSofaResponse sofaResponse = (SofaResponse) methodInfo.getRetValue();\n\t\tSofaRpcCtxUtils.finishClientSpan(context, sofaResponse, methodInfo.getThrowable());\n\t}\n}\n"
  },
  {
    "path": "plugins/sofarpc/src/main/java/com/megaease/easeagent/plugin/sofarpc/interceptor/trace/common/SofaRpcProviderTraceInterceptor.java",
    "content": "package com.megaease.easeagent.plugin.sofarpc.interceptor.trace.common;\n\nimport com.alipay.sofa.rpc.core.request.SofaRequest;\nimport com.alipay.sofa.rpc.core.response.SofaResponse;\nimport com.alipay.sofa.rpc.filter.ProviderInvoker;\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.sofarpc.SofaRpcCtxUtils;\nimport com.megaease.easeagent.plugin.sofarpc.SofaRpcPlugin;\nimport com.megaease.easeagent.plugin.sofarpc.adivce.ProviderAdvice;\nimport com.megaease.easeagent.plugin.sofarpc.interceptor.trace.SofaRpcTraceBaseInterceptor;\n\n@AdviceTo(value = ProviderAdvice.class, plugin = SofaRpcPlugin.class)\npublic class SofaRpcProviderTraceInterceptor extends SofaRpcTraceBaseInterceptor {\n\n\t@Override\n\tpublic void before(MethodInfo methodInfo, Context context) {\n\n\t\tProviderInvoker<?> providerInvoker = (ProviderInvoker<?>)methodInfo.getInvoker();\n\t\tSofaRequest sofaRequest = (SofaRequest)methodInfo.getArgs()[0];\n\t\tSofaRpcCtxUtils.startServerSpan(context, providerInvoker, sofaRequest);\n\t}\n\n\n\t@Override\n\tpublic void after(MethodInfo methodInfo, Context context) {\n\t\tSofaResponse sofaResponse = (SofaResponse) methodInfo.getRetValue();\n\t\tSofaRpcCtxUtils.finishServerSpan(context, sofaResponse, methodInfo.getThrowable());\n\t}\n}\n"
  },
  {
    "path": "plugins/sofarpc/src/main/java/com/megaease/easeagent/plugin/sofarpc/interceptor/trace/common/SofaServerTraceRequest.java",
    "content": "package com.megaease.easeagent.plugin.sofarpc.interceptor.trace.common;\n\nimport com.alipay.sofa.rpc.context.RpcInternalContext;\nimport com.alipay.sofa.rpc.core.request.SofaRequest;\nimport com.alipay.sofa.rpc.filter.ProviderInvoker;\nimport com.megaease.easeagent.plugin.api.trace.Request;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.sofarpc.SofaRpcCtxUtils;\n\nimport java.net.InetSocketAddress;\n\npublic class SofaServerTraceRequest implements Request {\n\n\tprivate final ProviderInvoker<?> providerInvoker;\n\tprivate final SofaRequest sofaRequest;\n\n\tpublic SofaServerTraceRequest(ProviderInvoker<?> providerInvoker, SofaRequest sofaRequest) {\n\t\tthis.providerInvoker = providerInvoker;\n\t\tthis.sofaRequest = sofaRequest;\n\t}\n\n\t@Override\n\tpublic Span.Kind kind() {\n\t\treturn Span.Kind.SERVER;\n\t}\n\n\t@Override\n\tpublic String header(String name) {\n\t\treturn (String) this.sofaRequest.getRequestProp(name);\n\t}\n\n\t@Override\n\tpublic String name() {\n\t\treturn SofaRpcCtxUtils.name(this.sofaRequest);\n\t}\n\n\t@Override\n\tpublic boolean cacheScope() {\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic void setHeader(String name, String value) {\n\t\tthis.sofaRequest.addRequestProp(name,value);\n\t}\n\n\tpublic String appName() {\n\t\treturn this.providerInvoker.getConfig().getAppName();\n\t}\n\n\tpublic String remoteHost() {\n\t\tInetSocketAddress remoteAddress = RpcInternalContext.getContext().getRemoteAddress();\n\t\treturn remoteAddress != null ? remoteAddress.getHostString() : null;\n\t}\n\n\tpublic int remotePort() {\n\t\tInetSocketAddress remoteAddress = RpcInternalContext.getContext().getRemoteAddress();\n\t\treturn remoteAddress != null ? remoteAddress.getPort() : 0;\n\t}\n\n}\n"
  },
  {
    "path": "plugins/sofarpc/src/main/java/com/megaease/easeagent/plugin/sofarpc/interceptor/trace/future/SofaRpcResponseFutureTraceInterceptor.java",
    "content": "package com.megaease.easeagent.plugin.sofarpc.interceptor.trace.future;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.AsyncContext;\nimport com.megaease.easeagent.plugin.field.AgentDynamicFieldAccessor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.sofarpc.SofaRpcCtxUtils;\nimport com.megaease.easeagent.plugin.sofarpc.SofaRpcPlugin;\nimport com.megaease.easeagent.plugin.sofarpc.adivce.ResponseFutureAdvice;\nimport com.megaease.easeagent.plugin.sofarpc.interceptor.trace.SofaRpcTraceBaseInterceptor;\n\n@AdviceTo(value = ResponseFutureAdvice.class, plugin = SofaRpcPlugin.class)\npublic class SofaRpcResponseFutureTraceInterceptor extends SofaRpcTraceBaseInterceptor {\n\n\t@Override\n\tpublic void after(MethodInfo methodInfo, Context context) {\n\t\tObject result = methodInfo.getArgs()[0];\n\t\tAsyncContext asyncContext = AgentDynamicFieldAccessor.getDynamicFieldValue(methodInfo.getInvoker());\n\n\t\tif (methodInfo.getThrowable() != null) {\n\t\t\tresult = methodInfo.getThrowable();\n\t\t}\n\t\tSofaRpcCtxUtils.asyncFinishClientSpan(asyncContext, result);\n\t}\n}\n"
  },
  {
    "path": "plugins/sofarpc/src/test/java/com/megaease/easeagent/plugin/sofarpc/interceptor/BaseInterceptorTest.java",
    "content": "package com.megaease.easeagent.plugin.sofarpc.interceptor;\n\nimport com.alipay.sofa.rpc.common.RpcConstants;\nimport com.alipay.sofa.rpc.config.ConsumerConfig;\nimport com.alipay.sofa.rpc.config.ProviderConfig;\nimport com.alipay.sofa.rpc.context.RpcInternalContext;\nimport com.alipay.sofa.rpc.core.request.SofaRequest;\nimport com.alipay.sofa.rpc.filter.ConsumerInvoker;\nimport com.alipay.sofa.rpc.filter.ProviderInvoker;\nimport com.megaease.easeagent.mock.config.MockConfig;\nimport com.megaease.easeagent.mock.plugin.api.MockEaseAgent;\nimport com.megaease.easeagent.mock.plugin.api.utils.InterceptorTestUtils;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport com.megaease.easeagent.plugin.sofarpc.SofaRpcCtxUtils;\nimport com.megaease.easeagent.plugin.sofarpc.SofaRpcPlugin;\nimport com.megaease.easeagent.plugin.sofarpc.SofaRpcTraceTags;\nimport com.megaease.easeagent.plugin.sofarpc.interceptor.trace.SofaRpcTraceBaseInterceptor;\nimport com.megaease.easeagent.plugin.sofarpc.interceptor.trace.common.SofaRpcConsumerTraceInterceptor;\nimport com.megaease.easeagent.plugin.utils.common.JsonUtil;\nimport org.junit.*;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\n\nimport java.lang.reflect.Method;\n\nimport static org.mockito.Mockito.when;\n\npublic abstract class BaseInterceptorTest {\n\n\tprotected RpcInternalContext rpcContext = RpcInternalContext.getContext();\n\n\t@Mock\n\tprotected SofaRequest sofaRequest;\n\n\t@Mock\n\tprotected ConsumerInvoker consumerInvoker;\n\n\t@Mock\n\tprotected ConsumerConfig<?> consumerConfig;\n\n\t@Mock\n\tprotected ProviderInvoker<?> providerInvoker;\n\n\t@Mock\n\tprotected ProviderConfig<?> providerConfig;\n\n\t@Mock\n\tprotected Method mockMethod;\n\n\tprotected Object[] allArguments;\n\n    private AutoCloseable autoCloseable;\n\n\t@BeforeClass\n\tpublic static void beforeClass() {\n\t}\n\n\t@AfterClass\n\tpublic static void afterClass() {\n\t}\n\n\t@Test\n\tpublic void testGetType() {\n\t\tString type = getInterceptor().getType();\n\t\tAssert.assertEquals(Order.TRACING.getName(), type);\n\t}\n\n\t@Test\n\tpublic void testOrder() {\n\t\tint order = getInterceptor().order();\n\t\tAssert.assertEquals(Order.TRACING.getOrder(), order);\n\t}\n\n\t@Before\n\tpublic void init() {\n\t\tautoCloseable = MockitoAnnotations.openMocks(this);\n\t\twhen(consumerInvoker.getConfig()).thenReturn(consumerConfig);\n\t\twhen(consumerConfig.getAppName()).thenReturn(\"sofa-client\");\n\n\t\twhen(providerInvoker.getConfig()).thenReturn(providerConfig);\n\t\twhen(providerConfig.getAppName()).thenReturn(\"sofa-server\");\n\n\t\twhen(sofaRequest.getMethod()).thenReturn(mockMethod);\n\t\t@SuppressWarnings(\"unchecked\")\n\t\tClass<BaseInterceptorTest> declaringClass = (Class<BaseInterceptorTest>) mockMethod.getDeclaringClass();\n\t\twhen(declaringClass).thenReturn(BaseInterceptorTest.class);\n\t\twhen(mockMethod.getName()).thenReturn(\"mock\");\n\t\twhen(mockMethod.getParameterTypes()).thenReturn(new Class[]{String.class, Integer.class});\n\t\twhen(sofaRequest.getInvokeType()).thenReturn(RpcConstants.INVOKER_TYPE_SYNC);\n\t\twhen(sofaRequest.getMethodArgs()).thenReturn(new Object[]{\"abc\", 3});\n\n\t\tallArguments = new Object[]{sofaRequest};\n\n\t\tEaseAgent.configFactory = MockConfig.getPluginConfigManager();\n\t\tSofaRpcPlugin sofaRpcPlugin = new SofaRpcPlugin();\n\t\tSofaRpcTraceBaseInterceptor sofaRpcTraceBaseInterceptor = getInterceptor();\n\t\tInterceptorTestUtils.init(sofaRpcTraceBaseInterceptor, sofaRpcPlugin);\n\t}\n\n    @After\n    public void destroy() throws Exception {\n        autoCloseable.close();\n    }\n\n\tprotected abstract SofaRpcTraceBaseInterceptor getInterceptor();\n\n\tprotected void assertProviderTrace(SofaRequest sofaRequest, Object result) {\n\t\tassertTrace(false, sofaRequest, result);\n\t}\n\n\tprotected void assertConsumerTrace(SofaRequest sofaRequest, Object result) {\n\t\tassertTrace(true, sofaRequest, result);\n\t}\n\n\tprivate void assertTrace(boolean isClientSide, SofaRequest sofaRequest, Object result) {\n\t\tReportSpan lastSpan = MockEaseAgent.getLastSpan();\n\t\tAssert.assertNotNull(lastSpan);\n\t\tAssert.assertNull(lastSpan.parentId());\n\t\tAssert.assertEquals(ConfigConst.Namespace.SOFARPC, lastSpan.remoteServiceName());\n\t\tAssert.assertEquals(SofaRpcCtxUtils.name(sofaRequest).toLowerCase(), lastSpan.name());\n\t\tif (isClientSide) {\n\t\t\tAssert.assertEquals(Span.Kind.CLIENT.name(), lastSpan.kind());\n\t\t\tAssert.assertEquals(consumerConfig.getAppName(), lastSpan.tag(SofaRpcTraceTags.CLIENT_APPLICATION.name));\n\t\t\tAssert.assertEquals(rpcContext.getProviderInfo().getHost(), lastSpan.remoteEndpoint().ipv4());\n\t\t\tAssert.assertEquals(rpcContext.getProviderInfo().getPort(), lastSpan.remoteEndpoint().port());\n\t\t\tAssert.assertEquals(SofaRpcCtxUtils.method(sofaRequest), lastSpan.tag(SofaRpcTraceTags.METHOD.name));\n\t\t\tif (SofaRpcConsumerTraceInterceptor.SOFA_RPC_TRACE_CONFIG.argsCollectEnabled()) {\n\t\t\t\tAssert.assertEquals(JsonUtil.toJson(sofaRequest.getMethodArgs()), lastSpan.tag(SofaRpcTraceTags.ARGS.name));\n\t\t\t}\n\t\t\tif (result instanceof Throwable) {\n\t\t\t\tAssert.assertTrue(lastSpan.hasError());\n\t\t\t\tAssert.assertNull(lastSpan.tag(SofaRpcTraceTags.RESULT.name));\n\t\t\t\tAssert.assertEquals(lastSpan.errorInfo(), ((Throwable) result).getMessage());\n\t\t\t} else if (SofaRpcConsumerTraceInterceptor.SOFA_RPC_TRACE_CONFIG.resultCollectEnabled()) {\n\t\t\t\tAssert.assertFalse(lastSpan.hasError());\n\t\t\t\tAssert.assertEquals(JsonUtil.toJson(result), lastSpan.tag(SofaRpcTraceTags.RESULT.name));\n\t\t\t}\n\t\t} else {\n\t\t\tAssert.assertEquals(Span.Kind.SERVER.name(), lastSpan.kind());\n\t\t\tAssert.assertEquals(providerConfig.getAppName(), lastSpan.tag(SofaRpcTraceTags.SERVER_APPLICATION.name));\n\t\t\tAssert.assertEquals(rpcContext.getRemoteAddress().getHostString(), lastSpan.remoteEndpoint().ipv4());\n\t\t\tAssert.assertEquals(rpcContext.getRemoteAddress().getPort(), lastSpan.remoteEndpoint().port());\n\t\t}\n\n\t}\n}\n"
  },
  {
    "path": "plugins/sofarpc/src/test/java/com/megaease/easeagent/plugin/sofarpc/interceptor/MockBoltResponseFuture.java",
    "content": "package com.megaease.easeagent.plugin.sofarpc.interceptor;\n\nimport com.megaease.easeagent.plugin.field.DynamicFieldAccessor;\n\npublic class MockBoltResponseFuture implements DynamicFieldAccessor {\n\tprivate Object data;\n\n\t@Override\n\tpublic void setEaseAgent$$DynamicField$$Data(Object data) {\n\t\tthis.data = data;\n\t}\n\n\t@Override\n\tpublic Object getEaseAgent$$DynamicField$$Data() {\n\t\treturn data;\n\t}\n\n}\n"
  },
  {
    "path": "plugins/sofarpc/src/test/java/com/megaease/easeagent/plugin/sofarpc/interceptor/metrics/BaseMetricsInterceptorTest.java",
    "content": "package com.megaease.easeagent.plugin.sofarpc.interceptor.metrics;\n\nimport com.alipay.sofa.rpc.common.RpcConstants;\nimport com.alipay.sofa.rpc.config.ConsumerConfig;\nimport com.alipay.sofa.rpc.config.ProviderConfig;\nimport com.alipay.sofa.rpc.context.RpcInternalContext;\nimport com.alipay.sofa.rpc.core.request.SofaRequest;\nimport com.alipay.sofa.rpc.filter.ConsumerInvoker;\nimport com.alipay.sofa.rpc.filter.ProviderInvoker;\nimport com.megaease.easeagent.mock.plugin.api.MockEaseAgent;\nimport com.megaease.easeagent.mock.plugin.api.utils.InterceptorTestUtils;\nimport com.megaease.easeagent.mock.plugin.api.utils.TagVerifier;\nimport com.megaease.easeagent.mock.report.impl.LastJsonReporter;\nimport com.megaease.easeagent.plugin.api.metric.name.MetricField;\nimport com.megaease.easeagent.plugin.api.metric.name.Tags;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.sofarpc.SofaRpcCtxUtils;\nimport com.megaease.easeagent.plugin.sofarpc.SofaRpcMetricsTags;\nimport com.megaease.easeagent.plugin.sofarpc.SofaRpcPlugin;\nimport org.junit.*;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\n\nimport java.lang.reflect.Method;\nimport java.util.Map;\n\nimport static org.junit.Assert.*;\nimport static org.mockito.Mockito.when;\n\npublic abstract class BaseMetricsInterceptorTest {\n\n\tprotected RpcInternalContext rpcContext = RpcInternalContext.getContext();\n\t@Mock\n\tprotected SofaRequest sofaRequest;\n\t@Mock\n\tprotected ConsumerInvoker consumerInvoker;\n\t@Mock\n\tprotected ConsumerConfig<?> consumerConfig;\n\t@Mock\n\tprotected ProviderInvoker<?> providerInvoker;\n\t@Mock\n\tprotected ProviderConfig<?> providerConfig;\n\t@Mock\n\tprotected Method mockMethod;\n\tprotected Object[] allArguments;\n    private AutoCloseable autoCloseable;\n\n\t@BeforeClass\n\tpublic static void beforeClass() {\n\t}\n\n\t@AfterClass\n\tpublic static void afterClass() {\n\t}\n\n\t@Test\n\tpublic void testGetType() {\n\t\tString type = getInterceptor().getType();\n\t\tAssert.assertEquals(Order.METRIC.getName(), type);\n\t}\n\n\t@Test\n\tpublic void testOrder() {\n\t\tint order = getInterceptor().order();\n\t\tAssert.assertEquals(Order.METRIC.getOrder(), order);\n\t}\n    @After\n    public void destroy() throws Exception {\n        autoCloseable.close();\n    }\n\n\t@Before\n\tpublic void init() {\n\t\tautoCloseable = MockitoAnnotations.openMocks(this);\n\t\twhen(consumerInvoker.getConfig()).thenReturn(consumerConfig);\n\t\twhen(consumerConfig.getAppName()).thenReturn(\"sofa-client\");\n\n\t\twhen(providerInvoker.getConfig()).thenReturn(providerConfig);\n\t\twhen(providerConfig.getAppName()).thenReturn(\"sofa-server\");\n\n\t\twhen(sofaRequest.getMethod()).thenReturn(mockMethod);\n\t\t@SuppressWarnings(\"unchecked\")\n\t\tClass<BaseMetricsInterceptorTest> declaringClass = (Class<BaseMetricsInterceptorTest>) mockMethod.getDeclaringClass();\n\t\twhen(declaringClass).thenReturn(BaseMetricsInterceptorTest.class);\n\t\twhen(mockMethod.getName()).thenReturn(\"mock\");\n\t\twhen(mockMethod.getParameterTypes()).thenReturn(new Class[]{String.class, Integer.class});\n\t\twhen(sofaRequest.getInvokeType()).thenReturn(RpcConstants.INVOKER_TYPE_SYNC);\n\t\twhen(sofaRequest.getMethodArgs()).thenReturn(new Object[]{\"abc\", 3});\n\n\t\tallArguments = new Object[]{sofaRequest};\n\n\t\tSofaRpcPlugin sofaRpcPlugin = new SofaRpcPlugin();\n\t\tSofaRpcMetricsBaseInterceptor sofaRpcMetricsBaseInterceptor = getInterceptor();\n\t\tInterceptorTestUtils.init(sofaRpcMetricsBaseInterceptor, sofaRpcPlugin);\n\t}\n\n\tprotected abstract SofaRpcMetricsBaseInterceptor getInterceptor();\n\n\tprotected void assertMetrics(SofaRequest sofaRequest, Object result) {\n\t\tTagVerifier tagVerifier = new TagVerifier()\n\t\t\t\t.add(Tags.CATEGORY, SofaRpcMetricsTags.CATEGORY.name)\n\t\t\t\t.add(Tags.TYPE, SofaRpcMetricsTags.TYPE.name)\n\t\t\t\t.add(SofaRpcMetricsTags.LABEL_NAME.name, SofaRpcCtxUtils.methodSignature(sofaRequest));\n\t\tLastJsonReporter lastJsonReporter = MockEaseAgent.lastMetricJsonReporter(tagVerifier::verifyAnd);\n\t\tMap<String, Object> metrics = lastJsonReporter.flushAndOnlyOne();\n\n\t\tassertEquals(1, metrics.get(MetricField.EXECUTION_COUNT.getField()));\n\t\tif (result instanceof Throwable) {\n\t\t\tassertEquals(1, metrics.get(MetricField.EXECUTION_ERROR_COUNT.getField()));\n\t\t}\n\t}\n\n\tprivate static double metricValue(Map<String,Object> metrics, MetricField metricField) {\n\t\treturn (double) metrics.get(metricField.getField());\n\t}\n}\n"
  },
  {
    "path": "plugins/sofarpc/src/test/java/com/megaease/easeagent/plugin/sofarpc/interceptor/metrics/callback/MockSofaResponseCallback.java",
    "content": "package com.megaease.easeagent.plugin.sofarpc.interceptor.metrics.callback;\n\nimport com.alipay.sofa.rpc.core.exception.SofaRpcException;\nimport com.alipay.sofa.rpc.core.invoke.SofaResponseCallback;\nimport com.alipay.sofa.rpc.core.request.RequestBase;\n\npublic class MockSofaResponseCallback implements SofaResponseCallback<Object> {\n\tprivate Object result;\n\n\tpublic Object getResult() {\n\t\treturn result;\n\t}\n\n\t@Override\n\tpublic void onAppResponse(Object appResponse, String methodName, RequestBase request) {\n\t\tthis.result = appResponse;\n\t}\n\n\t@Override\n\tpublic void onAppException(Throwable throwable, String methodName, RequestBase request) {\n\t\tthis.result = throwable;\n\t}\n\n\t@Override\n\tpublic void onSofaException(SofaRpcException sofaException, String methodName, RequestBase request) {\n\t\tthis.result = sofaException;\n\t}\n}\n"
  },
  {
    "path": "plugins/sofarpc/src/test/java/com/megaease/easeagent/plugin/sofarpc/interceptor/metrics/callback/SofaRpcResponseCallbackMetricsInterceptorTest.java",
    "content": "package com.megaease.easeagent.plugin.sofarpc.interceptor.metrics.callback;\n\nimport com.alipay.sofa.rpc.client.ProviderInfo;\nimport com.alipay.sofa.rpc.common.RpcConstants;\nimport com.alipay.sofa.rpc.core.exception.SofaTimeOutException;\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.AsyncContext;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.sofarpc.SofaRpcCtxUtils;\nimport com.megaease.easeagent.plugin.sofarpc.interceptor.metrics.BaseMetricsInterceptorTest;\nimport com.megaease.easeagent.plugin.sofarpc.interceptor.metrics.SofaRpcMetricsBaseInterceptor;\nimport com.megaease.easeagent.plugin.sofarpc.interceptor.metrics.common.SofaRpcMetricsInterceptor;\nimport lombok.SneakyThrows;\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static org.mockito.Mockito.when;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class SofaRpcResponseCallbackMetricsInterceptorTest extends BaseMetricsInterceptorTest {\n\tprivate final SofaRpcResponseCallbackMetricsInterceptor sofaRpcResponseCallbackMetricsInterceptor = new SofaRpcResponseCallbackMetricsInterceptor();\n\tprivate final SofaRpcMetricsInterceptor sofaRpcMetricsInterceptor = new SofaRpcMetricsInterceptor();\n\tprivate final MockSofaResponseCallback mockSofaResponseCallback = new MockSofaResponseCallback();\n\tprivate final Object[] responseCallbackMetricsInterceptorArgs = new Object[6];\n\n\n\t@Override\n\tprotected SofaRpcMetricsBaseInterceptor getInterceptor() {\n\t\treturn sofaRpcResponseCallbackMetricsInterceptor;\n\t}\n\n\t@Before\n\tpublic void setup() {\n\t\twhen(sofaRequest.getInvokeType()).thenReturn(RpcConstants.INVOKER_TYPE_CALLBACK);\n\n\t\tresponseCallbackMetricsInterceptorArgs[2] = mockSofaResponseCallback;\n\t\tProviderInfo providerInfo = new ProviderInfo();\n\t\tproviderInfo.setHost(\"127.0.0.1\");\n\t\tproviderInfo.setPort(12200);\n\t\trpcContext.setProviderInfo(providerInfo);\n\t}\n\n\t@SneakyThrows\n\t@Test\n\tpublic void testConsumerCallbackSuccess() {\n\t\tMethodInfo methodInfo = MethodInfo.builder()\n\t\t\t\t.invoker(consumerInvoker)\n\t\t\t\t.args(allArguments)\n\t\t\t\t.build();\n\n\n\t\tContext context = EaseAgent.getContext();\n\t\tsofaRpcMetricsInterceptor.before(methodInfo, context);\n\t\tsofaRpcMetricsInterceptor.after(methodInfo, context);\n\t\tmethodInfo.setArgs(responseCallbackMetricsInterceptorArgs);\n\t\tsofaRpcResponseCallbackMetricsInterceptor.before(methodInfo, context);\n\t\tThread asyncThread = new Thread(new Runnable() {\n\t\t\t@Override\n\t\t\tpublic void run() {\n\t\t\t\tSofaRpcResponseCallbackMetrics sofaRpcResponseCallbackMetrics = (SofaRpcResponseCallbackMetrics) methodInfo.getArgs()[2];\n\t\t\t\tsofaRpcResponseCallbackMetrics.onAppResponse(\"success\", sofaRequest.getMethod().getName(), sofaRequest);\n\t\t\t}\n\t\t});\n\t\tasyncThread.start();\n\t\tasyncThread.join();\n\n\t\tassertMetrics(sofaRequest, mockSofaResponseCallback.getResult());\n\t}\n\n\t@SneakyThrows\n\t@Test\n\tpublic void testConsumerCallbackWithException() {\n\t\tMethodInfo methodInfo = MethodInfo.builder()\n\t\t\t\t.invoker(consumerInvoker)\n\t\t\t\t.args(allArguments)\n\t\t\t\t.build();\n\n\n\t\tContext context = EaseAgent.getContext();\n\t\tsofaRpcMetricsInterceptor.before(methodInfo, context);\n\t\tsofaRpcMetricsInterceptor.after(methodInfo, context);\n\t\tmethodInfo.setArgs(responseCallbackMetricsInterceptorArgs);\n\t\tsofaRpcResponseCallbackMetricsInterceptor.before(methodInfo, context);\n\n\t\tThrowable throwable = new Throwable(\"call exception\");\n\t\tThread asyncThread = new Thread(new Runnable() {\n\t\t\t@Override\n\t\t\tpublic void run() {\n\t\t\t\tSofaRpcResponseCallbackMetrics sofaRpcResponseCallbackMetrics = (SofaRpcResponseCallbackMetrics) methodInfo.getArgs()[2];\n\t\t\t\tsofaRpcResponseCallbackMetrics.onAppException(throwable, sofaRequest.getMethod().getName(), sofaRequest);\n\t\t\t}\n\t\t});\n\t\tasyncThread.start();\n\t\tasyncThread.join();\n\n\t\tassertMetrics(sofaRequest, mockSofaResponseCallback.getResult());\n\t}\n\n\t@SneakyThrows\n\t@Test\n\tpublic void testConsumerCallbackWithSofaException() {\n\t\tMethodInfo methodInfo = MethodInfo.builder()\n\t\t\t\t.invoker(consumerInvoker)\n\t\t\t\t.args(allArguments)\n\t\t\t\t.build();\n\n\n\t\tContext context = EaseAgent.getContext();\n\t\tsofaRpcMetricsInterceptor.before(methodInfo, context);\n\t\tsofaRpcMetricsInterceptor.after(methodInfo, context);\n\t\tmethodInfo.setArgs(responseCallbackMetricsInterceptorArgs);\n\t\tsofaRpcResponseCallbackMetricsInterceptor.before(methodInfo, context);\n\n\t\tSofaTimeOutException sofaTimeOutException = new SofaTimeOutException(\"call timeout\");\n\t\tThread asyncThread = new Thread(new Runnable() {\n\t\t\t@Override\n\t\t\tpublic void run() {\n\t\t\t\tSofaRpcResponseCallbackMetrics sofaRpcResponseCallbackMetrics = (SofaRpcResponseCallbackMetrics) methodInfo.getArgs()[2];\n\t\t\t\tsofaRpcResponseCallbackMetrics.onSofaException(sofaTimeOutException, sofaRequest.getMethod().getName(), sofaRequest);\n\t\t\t}\n\t\t});\n\t\tasyncThread.start();\n\t\tasyncThread.join();\n\n\t\tassertMetrics(sofaRequest, mockSofaResponseCallback.getResult());\n\t}\n\n\t@SneakyThrows\n\t@Test\n\tpublic void testConsumerCallbackNoObtainedMethodFullName() {\n\t\tMethodInfo methodInfo = MethodInfo.builder()\n\t\t\t\t.invoker(consumerInvoker)\n\t\t\t\t.args(allArguments)\n\t\t\t\t.build();\n\n\n\t\tContext context = EaseAgent.getContext();\n\t\tsofaRpcMetricsInterceptor.before(methodInfo, context);\n\t\tsofaRpcMetricsInterceptor.after(methodInfo, context);\n\t\tmethodInfo.setArgs(responseCallbackMetricsInterceptorArgs);\n\t\tsofaRpcResponseCallbackMetricsInterceptor.before(methodInfo, context);\n\n\t\tSofaTimeOutException sofaTimeOutException = new SofaTimeOutException(\"call timeout\");\n\t\tThread asyncThread = new Thread(new Runnable() {\n\t\t\t@Override\n\t\t\tpublic void run() {\n\t\t\t\tSofaRpcResponseCallbackMetrics sofaRpcResponseCallbackMetrics = (SofaRpcResponseCallbackMetrics) methodInfo.getArgs()[2];\n\t\t\t\tAsyncContext asyncContext = AgentFieldReflectAccessor.getFieldValue(sofaRpcResponseCallbackMetrics, \"asyncContext\");\n\t\t\t\tAssert.assertNotNull(asyncContext);\n\t\t\t\tasyncContext.put(SofaRpcCtxUtils.METRICS_KEY_NAME, null);\n\t\t\t\tsofaRpcResponseCallbackMetrics.onSofaException(sofaTimeOutException, sofaRequest.getMethod().getName(), sofaRequest);\n\t\t\t}\n\t\t});\n\t\tasyncThread.start();\n\t\tasyncThread.join();\n\t}\n}\n"
  },
  {
    "path": "plugins/sofarpc/src/test/java/com/megaease/easeagent/plugin/sofarpc/interceptor/metrics/common/SofaRpcMetricsInterceptorTest.java",
    "content": "package com.megaease.easeagent.plugin.sofarpc.interceptor.metrics.common;\n\nimport com.alipay.sofa.rpc.client.ProviderInfo;\nimport com.alipay.sofa.rpc.common.RpcConstants;\nimport com.alipay.sofa.rpc.core.exception.SofaTimeOutException;\nimport com.alipay.sofa.rpc.core.response.SofaResponse;\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.sofarpc.interceptor.metrics.BaseMetricsInterceptorTest;\nimport com.megaease.easeagent.plugin.sofarpc.interceptor.metrics.SofaRpcMetricsBaseInterceptor;\nimport lombok.SneakyThrows;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static org.mockito.Mockito.when;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class SofaRpcMetricsInterceptorTest extends BaseMetricsInterceptorTest {\n\tprivate final SofaRpcMetricsInterceptor sofaRpcMetricsInterceptor = new SofaRpcMetricsInterceptor();\n\n\t@Override\n\tprotected SofaRpcMetricsBaseInterceptor getInterceptor() {\n\t\treturn sofaRpcMetricsInterceptor;\n\t}\n\n\t@Before\n\tpublic void setUp() {\n\t\twhen(sofaRequest.getInvokeType()).thenReturn(RpcConstants.INVOKER_TYPE_SYNC);\n\n\t\tProviderInfo providerInfo = new ProviderInfo();\n\t\tproviderInfo.setHost(\"127.0.0.1\");\n\t\tproviderInfo.setPort(12200);\n\t\trpcContext.setProviderInfo(providerInfo);\n\t}\n\n\n\t@Test\n\t@SneakyThrows\n\tpublic void testSuccess() {\n\t\tSofaResponse sofaResponse = new SofaResponse();\n\t\tsofaResponse.setAppResponse(\"success\");\n\n\t\tMethodInfo methodInfo = MethodInfo.builder()\n\t\t\t\t.invoker(consumerInvoker)\n\t\t\t\t.args(allArguments)\n\t\t\t\t.retValue(sofaResponse)\n\t\t\t\t.build();\n\n\t\tContext context = EaseAgent.getContext();\n\t\tsofaRpcMetricsInterceptor.before(methodInfo, context);\n\t\tsofaRpcMetricsInterceptor.after(methodInfo, context);\n\n\t\tassertMetrics(sofaRequest, sofaResponse.getAppResponse());\n\t}\n\n\t@Test\n\t@SneakyThrows\n\tpublic void testWithResultHasException() {\n\t\tSofaTimeOutException timeoutException = new SofaTimeOutException(\"call timeout\");\n\t\tSofaResponse sofaResponse = new SofaResponse();\n\t\tsofaResponse.setAppResponse(timeoutException);\n\t\tsofaResponse.setErrorMsg(timeoutException.getMessage());\n\n\t\tMethodInfo methodInfo = MethodInfo.builder()\n\t\t\t\t.invoker(consumerInvoker)\n\t\t\t\t.args(allArguments)\n\t\t\t\t.retValue(sofaResponse)\n\t\t\t\t.build();\n\n\t\tContext context = EaseAgent.getContext();\n\t\tsofaRpcMetricsInterceptor.before(methodInfo, context);\n\t\tsofaRpcMetricsInterceptor.after(methodInfo, context);\n\n\t\tassertMetrics(sofaRequest, sofaResponse.getAppResponse());\n\t}\n\n\t@Test\n\t@SneakyThrows\n\tpublic void testWithExecuteException() {\n\t\tThrowable executeException = new Throwable(\"method execute exception\");\n\n\t\tMethodInfo methodInfo = MethodInfo.builder()\n\t\t\t\t.invoker(consumerInvoker)\n\t\t\t\t.args(allArguments)\n\t\t\t\t.throwable(executeException)\n\t\t\t\t.build();\n\n\t\tContext context = EaseAgent.getContext();\n\t\tsofaRpcMetricsInterceptor.before(methodInfo, context);\n\t\tsofaRpcMetricsInterceptor.after(methodInfo, context);\n\n\t\tassertMetrics(sofaRequest, executeException);\n\t}\n\t\n}"
  },
  {
    "path": "plugins/sofarpc/src/test/java/com/megaease/easeagent/plugin/sofarpc/interceptor/metrics/future/SofaRpcResponseFutureMetricsInterceptorTest.java",
    "content": "package com.megaease.easeagent.plugin.sofarpc.interceptor.metrics.future;\n\nimport com.alipay.sofa.rpc.client.ProviderInfo;\nimport com.alipay.sofa.rpc.common.RpcConstants;\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.sofarpc.interceptor.MockBoltResponseFuture;\nimport com.megaease.easeagent.plugin.sofarpc.interceptor.initalize.SofaRpcFutureInvokeCallbackConstructInterceptor;\nimport com.megaease.easeagent.plugin.sofarpc.interceptor.metrics.BaseMetricsInterceptorTest;\nimport com.megaease.easeagent.plugin.sofarpc.interceptor.metrics.SofaRpcMetricsBaseInterceptor;\nimport com.megaease.easeagent.plugin.sofarpc.interceptor.metrics.common.SofaRpcMetricsInterceptor;\nimport lombok.SneakyThrows;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static org.mockito.Mockito.when;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class SofaRpcResponseFutureMetricsInterceptorTest extends BaseMetricsInterceptorTest {\n\tprivate final SofaRpcMetricsInterceptor sofaRpcMetricsInterceptor = new SofaRpcMetricsInterceptor();\n\tprivate final SofaRpcResponseFutureMetricsInterceptor sofaRpcResponseFutureMetricsInterceptor = new SofaRpcResponseFutureMetricsInterceptor();\n\tprivate final SofaRpcFutureInvokeCallbackConstructInterceptor sofaRpcFutureInvokeCallbackConstructInterceptor = new SofaRpcFutureInvokeCallbackConstructInterceptor();\n\tprivate final Object[] futureInvokeCallbackConstructMetricsInterceptorArgs = new Object[6];\n\n\t@Override\n\tprotected SofaRpcMetricsBaseInterceptor getInterceptor() {\n\t\treturn sofaRpcResponseFutureMetricsInterceptor;\n\t}\n\n\t@Before\n\tpublic void setup() {\n\t\tfutureInvokeCallbackConstructMetricsInterceptorArgs[2] = new MockBoltResponseFuture();\n\t\twhen(sofaRequest.getInvokeType()).thenReturn(RpcConstants.INVOKER_TYPE_FUTURE);\n\n\t\tProviderInfo providerInfo = new ProviderInfo();\n\t\tproviderInfo.setHost(\"127.0.0.1\");\n\t\tproviderInfo.setPort(12200);\n\t\trpcContext.setProviderInfo(providerInfo);\n\t}\n\n\n\t@Test\n\tpublic void testConsumerFutureInvokeSuccess() throws InterruptedException {\n\n\t\tMethodInfo methodInfo = MethodInfo.builder()\n\t\t\t\t.invoker(consumerInvoker)\n\t\t\t\t.args(allArguments)\n\t\t\t\t.build();\n\n\t\tContext context = EaseAgent.getContext();\n\t\tsofaRpcMetricsInterceptor.before(methodInfo, context);\n\t\tsofaRpcMetricsInterceptor.after(methodInfo, context);\n\t\tmethodInfo.setArgs(futureInvokeCallbackConstructMetricsInterceptorArgs);\n\t\tsofaRpcFutureInvokeCallbackConstructInterceptor.before(methodInfo,context);\n\t\tString result = \"success\";\n\t\tThread asyncThread = new Thread(new Runnable() {\n\t\t\t@Override\n\t\t\tpublic void run() {\n\t\t\t\tmethodInfo.setInvoker(futureInvokeCallbackConstructMetricsInterceptorArgs[2]);\n\t\t\t\tmethodInfo.setArgs(new Object[]{result});\n\t\t\t\tsofaRpcResponseFutureMetricsInterceptor.after(methodInfo, context);\n\t\t\t}\n\t\t});\n\t\tasyncThread.start();\n\t\tasyncThread.join();\n\n\t\tassertMetrics(sofaRequest, result);\n\t}\n\n\t@Test\n\t@SneakyThrows\n\tpublic void testConsumerFutureInvokeFailure() {\n\t\tThrowable throwable = new Throwable(\"Unknown Exception\");\n\n\t\tMethodInfo methodInfo = MethodInfo.builder()\n\t\t\t\t.invoker(consumerInvoker)\n\t\t\t\t.args(allArguments)\n\t\t\t\t.build();\n\n\t\tContext context = EaseAgent.getContext();\n\t\tsofaRpcMetricsInterceptor.before(methodInfo, context);\n\t\tsofaRpcMetricsInterceptor.after(methodInfo, context);\n\t\tmethodInfo.setArgs(futureInvokeCallbackConstructMetricsInterceptorArgs);\n\t\tsofaRpcFutureInvokeCallbackConstructInterceptor.before(methodInfo,context);\n\t\tThread asyncThread = new Thread(new Runnable() {\n\t\t\t@Override\n\t\t\tpublic void run() {\n\t\t\t\tmethodInfo.setInvoker(futureInvokeCallbackConstructMetricsInterceptorArgs[2]);\n\t\t\t\tmethodInfo.setArgs(new Object[]{throwable});\n\t\t\t\tsofaRpcResponseFutureMetricsInterceptor.after(methodInfo, context);\n\t\t\t}\n\t\t});\n\t\tasyncThread.start();\n\t\tasyncThread.join();\n\n\t\tassertMetrics(sofaRequest, throwable);\n\t}\n\n\t@Test\n\t@SneakyThrows\n\tpublic void testConsumerFutureInvokeException() {\n\t\tThrowable throwable = new Throwable(\"Unknown Exception\");\n\n\t\tMethodInfo methodInfo = MethodInfo.builder()\n\t\t\t\t.invoker(consumerInvoker)\n\t\t\t\t.args(allArguments)\n\t\t\t\t.build();\n\n\t\tContext context = EaseAgent.getContext();\n\t\tsofaRpcMetricsInterceptor.before(methodInfo, context);\n\t\tsofaRpcMetricsInterceptor.after(methodInfo, context);\n\t\tmethodInfo.setArgs(futureInvokeCallbackConstructMetricsInterceptorArgs);\n\t\tsofaRpcFutureInvokeCallbackConstructInterceptor.before(methodInfo,context);\n\t\tThread asyncThread = new Thread(new Runnable() {\n\t\t\t@Override\n\t\t\tpublic void run() {\n\t\t\t\tmethodInfo.throwable(throwable);\n\t\t\t\tmethodInfo.setInvoker(futureInvokeCallbackConstructMetricsInterceptorArgs[2]);\n\t\t\t\tsofaRpcResponseFutureMetricsInterceptor.after(methodInfo, context);\n\t\t\t}\n\t\t});\n\t\tasyncThread.start();\n\t\tasyncThread.join();\n\n\t\tassertMetrics(sofaRequest, throwable);\n\t}\n\n\t@Test(expected = NullPointerException.class)\n\tpublic void testNotObtainedAsyncContext() {\n\n\t\tMethodInfo methodInfo = MethodInfo.builder()\n\t\t\t\t.invoker(consumerInvoker)\n\t\t\t\t.args(allArguments)\n\t\t\t\t.build();\n\n\t\tContext context = EaseAgent.getContext();\n\t\tmethodInfo.setInvoker(futureInvokeCallbackConstructMetricsInterceptorArgs[2]);\n\t\tsofaRpcResponseFutureMetricsInterceptor.after(methodInfo, context);\n\t}\n}\n"
  },
  {
    "path": "plugins/sofarpc/src/test/java/com/megaease/easeagent/plugin/sofarpc/interceptor/trace/callback/MockSofaResponseCallback.java",
    "content": "package com.megaease.easeagent.plugin.sofarpc.interceptor.trace.callback;\n\nimport com.alipay.sofa.rpc.core.exception.SofaRpcException;\nimport com.alipay.sofa.rpc.core.invoke.SofaResponseCallback;\nimport com.alipay.sofa.rpc.core.request.RequestBase;\n\npublic class MockSofaResponseCallback implements SofaResponseCallback<Object> {\n\tprivate Object result;\n\n\tpublic Object getResult() {\n\t\treturn result;\n\t}\n\n\t@Override\n\tpublic void onAppResponse(Object appResponse, String methodName, RequestBase request) {\n\t\tthis.result = appResponse;\n\t}\n\n\t@Override\n\tpublic void onAppException(Throwable throwable, String methodName, RequestBase request) {\n\t\tthis.result = throwable;\n\t}\n\n\t@Override\n\tpublic void onSofaException(SofaRpcException sofaException, String methodName, RequestBase request) {\n\t\tthis.result = sofaException;\n\t}\n}\n"
  },
  {
    "path": "plugins/sofarpc/src/test/java/com/megaease/easeagent/plugin/sofarpc/interceptor/trace/callback/SofaRpcResponseCallbackTraceInterceptorTest.java",
    "content": "package com.megaease.easeagent.plugin.sofarpc.interceptor.trace.callback;\n\nimport com.alipay.sofa.rpc.client.ProviderInfo;\nimport com.alipay.sofa.rpc.common.RpcConstants;\nimport com.alipay.sofa.rpc.core.exception.SofaTimeOutException;\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.AsyncContext;\nimport com.megaease.easeagent.plugin.api.context.ContextUtils;\nimport com.megaease.easeagent.plugin.api.context.RequestContext;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.sofarpc.SofaRpcCtxUtils;\nimport com.megaease.easeagent.plugin.sofarpc.interceptor.BaseInterceptorTest;\nimport com.megaease.easeagent.plugin.sofarpc.interceptor.trace.SofaRpcTraceBaseInterceptor;\nimport com.megaease.easeagent.plugin.sofarpc.interceptor.trace.common.SofaRpcConsumerTraceInterceptor;\nimport lombok.SneakyThrows;\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static org.mockito.Mockito.when;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class SofaRpcResponseCallbackTraceInterceptorTest extends BaseInterceptorTest {\n\tprivate final SofaRpcResponseCallbackTraceInterceptor sofaRpcResponseCallbackTraceInterceptor = new SofaRpcResponseCallbackTraceInterceptor();\n\tprivate final SofaRpcConsumerTraceInterceptor sofaRpcConsumerTraceInterceptor = new SofaRpcConsumerTraceInterceptor();\n\tprivate final MockSofaResponseCallback mockSofaResponseCallback = new MockSofaResponseCallback();\n\tprivate final Object[] responseCallbackTraceInterceptorArgs = new Object[6];\n\n\n\t@Override\n\tprotected SofaRpcTraceBaseInterceptor getInterceptor() {\n\t\treturn sofaRpcResponseCallbackTraceInterceptor;\n\t}\n\n\t@Before\n\tpublic void setup() {\n\t\twhen(sofaRequest.getInvokeType()).thenReturn(RpcConstants.INVOKER_TYPE_CALLBACK);\n\n\t\tresponseCallbackTraceInterceptorArgs[2] = mockSofaResponseCallback;\n\t\tProviderInfo providerInfo = new ProviderInfo();\n\t\tproviderInfo.setHost(\"127.0.0.1\");\n\t\tproviderInfo.setPort(12200);\n\t\trpcContext.setProviderInfo(providerInfo);\n\t}\n\n\t@SneakyThrows\n\t@Test\n\tpublic void testConsumerCallbackSuccess() {\n\t\tMethodInfo methodInfo = MethodInfo.builder()\n\t\t\t\t.invoker(consumerInvoker)\n\t\t\t\t.args(allArguments)\n\t\t\t\t.build();\n\n\n\t\tContext context = EaseAgent.getContext();\n\t\tsofaRpcConsumerTraceInterceptor.before(methodInfo, context);\n\t\tsofaRpcConsumerTraceInterceptor.after(methodInfo,context);\n\t\tmethodInfo.setArgs(responseCallbackTraceInterceptorArgs);\n\t\tsofaRpcResponseCallbackTraceInterceptor.before(methodInfo, context);\n\t\tThread asyncThread = new Thread(new Runnable() {\n\t\t\t@Override\n\t\t\tpublic void run() {\n\t\t\t\tSofaRpcResponseCallbackTrace sofaRpcResponseCallbackTrace = (SofaRpcResponseCallbackTrace) methodInfo.getArgs()[2];\n\t\t\t\tsofaRpcResponseCallbackTrace.onAppResponse(\"success\", sofaRequest.getMethod().getName(), sofaRequest);\n\t\t\t}\n\t\t});\n\t\tasyncThread.start();\n\t\tasyncThread.join();\n\n\t\tassertConsumerTrace(sofaRequest, mockSofaResponseCallback.getResult());\n\t}\n\n\t@SneakyThrows\n\t@Test\n\tpublic void testConsumerCallbackWithException() {\n\t\tMethodInfo methodInfo = MethodInfo.builder()\n\t\t\t\t.invoker(consumerInvoker)\n\t\t\t\t.args(allArguments)\n\t\t\t\t.build();\n\n\n\t\tContext context = EaseAgent.getContext();\n\t\tsofaRpcConsumerTraceInterceptor.before(methodInfo, context);\n\t\tmethodInfo.setArgs(responseCallbackTraceInterceptorArgs);\n\t\tsofaRpcResponseCallbackTraceInterceptor.before(methodInfo, context);\n\n\t\tThrowable throwable = new Throwable(\"call exception\");\n\t\tThread asyncThread = new Thread(new Runnable() {\n\t\t\t@Override\n\t\t\tpublic void run() {\n\t\t\t\tSofaRpcResponseCallbackTrace sofaRpcResponseCallbackTrace = (SofaRpcResponseCallbackTrace) methodInfo.getArgs()[2];\n\t\t\t\tsofaRpcResponseCallbackTrace.onAppException(throwable, sofaRequest.getMethod().getName(), sofaRequest);\n\t\t\t}\n\t\t});\n\t\tasyncThread.start();\n\t\tasyncThread.join();\n\n\t\tassertConsumerTrace(sofaRequest, mockSofaResponseCallback.getResult());\n\t}\n\n\t@SneakyThrows\n\t@Test\n\tpublic void testConsumerCallbackWithSofaException() {\n\t\tMethodInfo methodInfo = MethodInfo.builder()\n\t\t\t\t.invoker(consumerInvoker)\n\t\t\t\t.args(allArguments)\n\t\t\t\t.build();\n\n\n\t\tContext context = EaseAgent.getContext();\n\t\tsofaRpcConsumerTraceInterceptor.before(methodInfo, context);\n\t\tmethodInfo.setArgs(responseCallbackTraceInterceptorArgs);\n\t\tsofaRpcResponseCallbackTraceInterceptor.before(methodInfo, context);\n\n\t\tSofaTimeOutException sofaTimeOutException = new SofaTimeOutException(\"call timeout\");\n\t\tThread asyncThread = new Thread(new Runnable() {\n\t\t\t@Override\n\t\t\tpublic void run() {\n\t\t\t\tSofaRpcResponseCallbackTrace sofaRpcResponseCallbackTrace = (SofaRpcResponseCallbackTrace) methodInfo.getArgs()[2];\n\t\t\t\tsofaRpcResponseCallbackTrace.onSofaException(sofaTimeOutException, sofaRequest.getMethod().getName(), sofaRequest);\n\t\t\t}\n\t\t});\n\t\tasyncThread.start();\n\t\tasyncThread.join();\n\n\t\tassertConsumerTrace(sofaRequest, mockSofaResponseCallback.getResult());\n\t}\n\n\n\t@Test(expected = NullPointerException.class)\n\tpublic void testConsumerNoObtainedRequestContext() {\n\t\tMethodInfo methodInfo = MethodInfo.builder()\n\t\t\t\t.invoker(consumerInvoker)\n\t\t\t\t.args(allArguments)\n\t\t\t\t.build();\n\n\t\tContext context = EaseAgent.getContext();\n\t\tsofaRpcConsumerTraceInterceptor.before(methodInfo, context);\n\t\tmethodInfo.setArgs(responseCallbackTraceInterceptorArgs);\n\t\tRequestContext requestContext = ContextUtils.removeFromContext(context, SofaRpcCtxUtils.CLIENT_REQUEST_CONTEXT_KEY);\n\t\trequestContext.span().finish();\n\t\trequestContext.scope().close();\n\t\tsofaRpcResponseCallbackTraceInterceptor.before(methodInfo, context);\n\n\t}\n\n\n\t@SneakyThrows\n\t@Test\n\tpublic void testConsumerCallbackNoObtainedRequestContext() {\n\t\tMethodInfo methodInfo = MethodInfo.builder()\n\t\t\t\t.invoker(consumerInvoker)\n\t\t\t\t.args(allArguments)\n\t\t\t\t.build();\n\n\n\t\tContext context = EaseAgent.getContext();\n\t\tsofaRpcConsumerTraceInterceptor.before(methodInfo, context);\n\t\tmethodInfo.setArgs(responseCallbackTraceInterceptorArgs);\n\t\tsofaRpcResponseCallbackTraceInterceptor.before(methodInfo, context);\n\n\t\tSofaTimeOutException sofaTimeOutException = new SofaTimeOutException(\"call timeout\");\n\t\tThread asyncThread = new Thread(new Runnable() {\n\t\t\t@Override\n\t\t\tpublic void run() {\n\t\t\t\tSofaRpcResponseCallbackTrace sofaRpcResponseCallbackTrace = (SofaRpcResponseCallbackTrace) methodInfo.getArgs()[2];\n\t\t\t\tAsyncContext asyncContext = AgentFieldReflectAccessor.getFieldValue(sofaRpcResponseCallbackTrace, \"asyncContext\");\n\t\t\t\tAssert.assertNotNull(asyncContext);\n\t\t\t\tRequestContext requestContext = asyncContext.get(SofaRpcCtxUtils.CLIENT_REQUEST_CONTEXT_KEY);\n\t\t\t\tasyncContext.put(SofaRpcCtxUtils.CLIENT_REQUEST_CONTEXT_KEY, null);\n\t\t\t\trequestContext.span().finish();\n\t\t\t\trequestContext.scope().close();\n\t\t\t\ttry {\n\t\t\t\t\tsofaRpcResponseCallbackTrace.onSofaException(sofaTimeOutException, sofaRequest.getMethod().getName(), sofaRequest);\n\t\t\t\t} catch (NullPointerException ignore) {\n\t\t\t\t    //Must be throw NullPointerException\n\t\t\t\t    return;\n\t\t\t\t}\n\t\t\t\tthrow new RuntimeException(\"Must be throw NullPointerException\");\n\t\t\t}\n\t\t});\n\t\tasyncThread.start();\n\t\tasyncThread.join();\n\t}\n}\n"
  },
  {
    "path": "plugins/sofarpc/src/test/java/com/megaease/easeagent/plugin/sofarpc/interceptor/trace/common/SofaRpcConsumerTraceInterceptorTest.java",
    "content": "package com.megaease.easeagent.plugin.sofarpc.interceptor.trace.common;\n\nimport com.alipay.sofa.rpc.client.ProviderInfo;\nimport com.alipay.sofa.rpc.common.RpcConstants;\nimport com.alipay.sofa.rpc.core.response.SofaResponse;\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.RequestContext;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.sofarpc.SofaRpcCtxUtils;\nimport com.megaease.easeagent.plugin.sofarpc.interceptor.BaseInterceptorTest;\nimport com.megaease.easeagent.plugin.sofarpc.interceptor.trace.SofaRpcTraceBaseInterceptor;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static org.mockito.Mockito.when;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class SofaRpcConsumerTraceInterceptorTest extends BaseInterceptorTest {\n\tprivate final SofaRpcConsumerTraceInterceptor consumerTraceInterceptor = new SofaRpcConsumerTraceInterceptor();\n\n\t@Override\n\tprotected SofaRpcTraceBaseInterceptor getInterceptor() {\n\t\treturn consumerTraceInterceptor;\n\t}\n\n\t@Before\n\tpublic void setUp() {\n\t\twhen(sofaRequest.getInvokeType()).thenReturn(RpcConstants.INVOKER_TYPE_SYNC);\n\n\t\tProviderInfo providerInfo = new ProviderInfo();\n\t\tproviderInfo.setHost(\"127.0.0.1\");\n\t\tproviderInfo.setPort(12200);\n\t\trpcContext.setProviderInfo(providerInfo);\n\t}\n\n\n\t@Test\n\tpublic void testConsumerSuccess() {\n\t\tSofaResponse sofaResponse = new SofaResponse();\n\t\tsofaResponse.setAppResponse(\"success\");\n\n\t\tMethodInfo methodInfo = MethodInfo.builder()\n\t\t\t\t.invoker(consumerInvoker)\n\t\t\t\t.args(allArguments)\n\t\t\t\t.retValue(sofaResponse)\n\t\t\t\t.build();\n\n\t\tContext context = EaseAgent.getContext();\n\t\tconsumerTraceInterceptor.before(methodInfo, context);\n\t\tconsumerTraceInterceptor.after(methodInfo, context);\n\n\t\tassertConsumerTrace(sofaRequest, sofaResponse.getAppResponse());\n\t}\n\n\t@Test\n\tpublic void testConsumerWithResultHasException() {\n\t\tRuntimeException runtimeException = new RuntimeException(\"call timeout\");\n\t\tSofaResponse sofaResponse = new SofaResponse();\n\t\tsofaResponse.setAppResponse(runtimeException);\n\t\tsofaResponse.setErrorMsg(runtimeException.getMessage());\n\n\t\tMethodInfo methodInfo = MethodInfo.builder()\n\t\t\t\t.invoker(consumerInvoker)\n\t\t\t\t.args(allArguments)\n\t\t\t\t.retValue(sofaResponse)\n\t\t\t\t.build();\n\n\t\tContext context = EaseAgent.getContext();\n\t\tconsumerTraceInterceptor.before(methodInfo, context);\n\t\tconsumerTraceInterceptor.after(methodInfo, context);\n\n\t\tassertConsumerTrace(sofaRequest, sofaResponse.getAppResponse());\n\t}\n\n\t@Test\n\tpublic void testConsumerWithExecuteException() {\n\t\tThrowable executeException = new Throwable(\"method execute exception\");\n\n\t\tMethodInfo methodInfo = MethodInfo.builder()\n\t\t\t\t.invoker(consumerInvoker)\n\t\t\t\t.args(allArguments)\n\t\t\t\t.throwable(executeException)\n\t\t\t\t.build();\n\n\t\tContext context = EaseAgent.getContext();\n\t\tconsumerTraceInterceptor.before(methodInfo, context);\n\t\tconsumerTraceInterceptor.after(methodInfo, context);\n\n\t\tassertConsumerTrace(sofaRequest, executeException);\n\t}\n\n\n\t@Test(expected = NullPointerException.class)\n\tpublic void testConsumerNoObtainedRequestContext() {\n\n\t\tMethodInfo methodInfo = MethodInfo.builder()\n\t\t\t\t.invoker(consumerInvoker)\n\t\t\t\t.args(allArguments)\n\t\t\t\t.build();\n\n\t\tContext context = EaseAgent.getContext();\n\t\tconsumerTraceInterceptor.before(methodInfo, context);\n\t\tRequestContext requestContext = context.remove(SofaRpcCtxUtils.CLIENT_REQUEST_CONTEXT_KEY);\n\t\trequestContext.span().finish();\n\t\trequestContext.scope().close();\n\t\tconsumerTraceInterceptor.after(methodInfo, context);\n\t}\n\t\n}"
  },
  {
    "path": "plugins/sofarpc/src/test/java/com/megaease/easeagent/plugin/sofarpc/interceptor/trace/common/SofaRpcProviderTraceInterceptorTest.java",
    "content": "package com.megaease.easeagent.plugin.sofarpc.interceptor.trace.common;\n\nimport com.alipay.sofa.rpc.core.response.SofaResponse;\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.ContextUtils;\nimport com.megaease.easeagent.plugin.api.context.RequestContext;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.sofarpc.SofaRpcCtxUtils;\nimport com.megaease.easeagent.plugin.sofarpc.interceptor.BaseInterceptorTest;\nimport com.megaease.easeagent.plugin.sofarpc.interceptor.trace.SofaRpcTraceBaseInterceptor;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class SofaRpcProviderTraceInterceptorTest extends BaseInterceptorTest {\n\n\tprivate final SofaRpcProviderTraceInterceptor sofaRpcProviderTraceInterceptor = new SofaRpcProviderTraceInterceptor();\n\n\t@Override\n\tprotected SofaRpcTraceBaseInterceptor getInterceptor() {\n\t\treturn sofaRpcProviderTraceInterceptor;\n\t}\n\n\t@Before\n\tpublic void setUp() {\n\t\trpcContext.setRemoteAddress(\"127.0.0.1\",12200);\n\t}\n\n\n\t@Test\n\tpublic void testProviderSuccess() {\n\t\tSofaResponse sofaResponse = new SofaResponse();\n\t\tsofaResponse.setAppResponse(\"success\");\n\n\t\tMethodInfo methodInfo = MethodInfo.builder()\n\t\t\t\t.invoker(providerInvoker)\n\t\t\t\t.args(allArguments)\n\t\t\t\t.retValue(sofaResponse)\n\t\t\t\t.build();\n\n\t\tContext context = EaseAgent.getContext();\n\t\tsofaRpcProviderTraceInterceptor.before(methodInfo, context);\n\t\tsofaRpcProviderTraceInterceptor.after(methodInfo, context);\n\n\t\tassertProviderTrace(sofaRequest, sofaResponse.getAppResponse());\n\t}\n\n\t@Test\n\tpublic void testProviderWithResultHasException() {\n\t\tRuntimeException runtimeException = new RuntimeException(\"call exception\");\n\t\tSofaResponse sofaResponse = new SofaResponse();\n\t\tsofaResponse.setAppResponse(runtimeException);\n\t\tsofaResponse.setErrorMsg(runtimeException.getMessage());\n\n\t\tMethodInfo methodInfo = MethodInfo.builder()\n\t\t\t\t.invoker(providerInvoker)\n\t\t\t\t.args(allArguments)\n\t\t\t\t.retValue(sofaResponse)\n\t\t\t\t.build();\n\n\t\tContext context = EaseAgent.getContext();\n\t\tsofaRpcProviderTraceInterceptor.before(methodInfo, context);\n\t\tsofaRpcProviderTraceInterceptor.after(methodInfo, context);\n\n\t\tassertProviderTrace(sofaRequest, sofaResponse.getAppResponse());\n\t}\n\n\t@Test\n\tpublic void testProviderWithExecuteException() {\n\t\tRuntimeException executeException = new RuntimeException(\"provider call exception\");\n\n\t\tMethodInfo methodInfo = MethodInfo.builder()\n\t\t\t\t.invoker(providerInvoker)\n\t\t\t\t.args(allArguments)\n\t\t\t\t.throwable(executeException)\n\t\t\t\t.build();\n\n\t\tContext context = EaseAgent.getContext();\n\t\tsofaRpcProviderTraceInterceptor.before(methodInfo, context);\n\t\tsofaRpcProviderTraceInterceptor.after(methodInfo, context);\n\n\t\tassertProviderTrace(sofaRequest, executeException);\n\t}\n\n\t@Test(expected = NullPointerException.class)\n\tpublic void testProviderNoObtainedRequestContext() {\n\n\t\tMethodInfo methodInfo = MethodInfo.builder()\n\t\t\t\t.invoker(providerInvoker)\n\t\t\t\t.args(allArguments)\n\t\t\t\t.build();\n\n\t\tContext context = EaseAgent.getContext();\n\t\tsofaRpcProviderTraceInterceptor.before(methodInfo, context);\n\t\tRequestContext requestContext = ContextUtils.removeFromContext(context,SofaRpcCtxUtils.SERVER_REQUEST_CONTEXT_KEY);\n\t\trequestContext.span().finish();\n\t\trequestContext.scope().close();\n\t\tsofaRpcProviderTraceInterceptor.after(methodInfo, context);\n\t}\n}\n"
  },
  {
    "path": "plugins/sofarpc/src/test/java/com/megaease/easeagent/plugin/sofarpc/interceptor/trace/future/SofaRpcResponseFutureTraceInterceptorTest.java",
    "content": "package com.megaease.easeagent.plugin.sofarpc.interceptor.trace.future;\n\nimport com.alipay.sofa.rpc.client.ProviderInfo;\nimport com.alipay.sofa.rpc.common.RpcConstants;\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.AsyncContext;\nimport com.megaease.easeagent.plugin.api.context.RequestContext;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.field.AgentDynamicFieldAccessor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.sofarpc.SofaRpcCtxUtils;\nimport com.megaease.easeagent.plugin.sofarpc.interceptor.BaseInterceptorTest;\nimport com.megaease.easeagent.plugin.sofarpc.interceptor.MockBoltResponseFuture;\nimport com.megaease.easeagent.plugin.sofarpc.interceptor.initalize.SofaRpcFutureInvokeCallbackConstructInterceptor;\nimport com.megaease.easeagent.plugin.sofarpc.interceptor.trace.SofaRpcTraceBaseInterceptor;\nimport com.megaease.easeagent.plugin.sofarpc.interceptor.trace.common.SofaRpcConsumerTraceInterceptor;\nimport lombok.SneakyThrows;\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static org.mockito.Mockito.when;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class SofaRpcResponseFutureTraceInterceptorTest extends BaseInterceptorTest {\n\tprivate final SofaRpcConsumerTraceInterceptor sofaRpcConsumerTraceInterceptor = new SofaRpcConsumerTraceInterceptor();\n\tprivate final SofaRpcResponseFutureTraceInterceptor sofaRpcResponseFutureTraceInterceptor = new SofaRpcResponseFutureTraceInterceptor();\n\tprivate final SofaRpcFutureInvokeCallbackConstructInterceptor sofaRpcFutureInvokeCallbackConstructInterceptor = new SofaRpcFutureInvokeCallbackConstructInterceptor();\n\tprivate final Object[] futureInvokeCallbackConstructTracingInterceptorArgs = new Object[6];\n\n\n\t@Override\n\tprotected SofaRpcTraceBaseInterceptor getInterceptor() {\n\t\treturn sofaRpcResponseFutureTraceInterceptor;\n\t}\n\n\t@Before\n\tpublic void setup() {\n\t\tfutureInvokeCallbackConstructTracingInterceptorArgs[2] = new MockBoltResponseFuture();\n\t\twhen(sofaRequest.getInvokeType()).thenReturn(RpcConstants.INVOKER_TYPE_FUTURE);\n\n\t\tProviderInfo providerInfo = new ProviderInfo();\n\t\tproviderInfo.setHost(\"127.0.0.1\");\n\t\tproviderInfo.setPort(12200);\n\t\trpcContext.setProviderInfo(providerInfo);\n\t}\n\n\n\t@Test\n\tpublic void testConsumerFutureInvokeSuccess() throws InterruptedException {\n\n\t\tMethodInfo methodInfo = MethodInfo.builder()\n\t\t\t\t.invoker(consumerInvoker)\n\t\t\t\t.args(allArguments)\n\t\t\t\t.build();\n\n\t\tContext context = EaseAgent.getContext();\n\t\tsofaRpcConsumerTraceInterceptor.before(methodInfo, context);\n\t\tsofaRpcConsumerTraceInterceptor.after(methodInfo, context);\n\t\tmethodInfo.setArgs(futureInvokeCallbackConstructTracingInterceptorArgs);\n\t\tsofaRpcFutureInvokeCallbackConstructInterceptor.before(methodInfo, context);\n\t\tString result = \"success\";\n\t\tThread asyncThread = new Thread(new Runnable() {\n\t\t\t@Override\n\t\t\tpublic void run() {\n\t\t\t\tmethodInfo.setInvoker(futureInvokeCallbackConstructTracingInterceptorArgs[2]);\n\t\t\t\tmethodInfo.setArgs(new Object[]{result});\n\t\t\t\tsofaRpcResponseFutureTraceInterceptor.after(methodInfo, context);\n\t\t\t}\n\t\t});\n\t\tasyncThread.start();\n\t\tasyncThread.join();\n\n\t\tassertConsumerTrace(sofaRequest, result);\n\t}\n\n\t@Test\n\t@SneakyThrows\n\tpublic void testConsumerFutureInvokeFailure() {\n\t\tThrowable throwable = new Throwable(\"Unknown Exception\");\n\n\t\tMethodInfo methodInfo = MethodInfo.builder()\n\t\t\t\t.invoker(consumerInvoker)\n\t\t\t\t.args(allArguments)\n\t\t\t\t.build();\n\n\t\tContext context = EaseAgent.getContext();\n\t\tsofaRpcConsumerTraceInterceptor.before(methodInfo, context);\n\t\tsofaRpcConsumerTraceInterceptor.after(methodInfo, context);\n\t\tmethodInfo.setArgs(futureInvokeCallbackConstructTracingInterceptorArgs);\n\t\tsofaRpcFutureInvokeCallbackConstructInterceptor.before(methodInfo, context);\n\t\tThread asyncThread = new Thread(new Runnable() {\n\t\t\t@Override\n\t\t\tpublic void run() {\n\t\t\t\tmethodInfo.setInvoker(futureInvokeCallbackConstructTracingInterceptorArgs[2]);\n\t\t\t\tmethodInfo.setArgs(new Object[]{throwable});\n\t\t\t\tsofaRpcResponseFutureTraceInterceptor.after(methodInfo, context);\n\t\t\t}\n\t\t});\n\t\tasyncThread.start();\n\t\tasyncThread.join();\n\n\t\tassertConsumerTrace(sofaRequest, throwable);\n\t}\n\n\t@Test\n\t@SneakyThrows\n\tpublic void testConsumerFutureInvokeException() {\n\t\tIllegalStateException illegalStateException = new IllegalStateException(\"complete already\");\n\n\t\tMethodInfo methodInfo = MethodInfo.builder()\n\t\t\t\t.invoker(consumerInvoker)\n\t\t\t\t.args(allArguments)\n\t\t\t\t.build();\n\n\t\tContext context = EaseAgent.getContext();\n\t\tsofaRpcConsumerTraceInterceptor.before(methodInfo, context);\n\t\tsofaRpcConsumerTraceInterceptor.after(methodInfo, context);\n\t\tmethodInfo.setArgs(futureInvokeCallbackConstructTracingInterceptorArgs);\n\t\tsofaRpcFutureInvokeCallbackConstructInterceptor.before(methodInfo, context);\n\t\tThread asyncThread = new Thread(new Runnable() {\n\t\t\t@Override\n\t\t\tpublic void run() {\n\t\t\t\tmethodInfo.throwable(illegalStateException);\n\t\t\t\tmethodInfo.setInvoker(futureInvokeCallbackConstructTracingInterceptorArgs[2]);\n\t\t\t\tsofaRpcResponseFutureTraceInterceptor.after(methodInfo, context);\n\t\t\t}\n\t\t});\n\t\tasyncThread.start();\n\t\tasyncThread.join();\n\n\t\tassertConsumerTrace(sofaRequest, illegalStateException);\n\t}\n\n\t@Test(expected = NullPointerException.class)\n\tpublic void testNotObtainedAsyncContext() {\n\n\t\tMethodInfo methodInfo = MethodInfo.builder()\n\t\t\t\t.invoker(consumerInvoker)\n\t\t\t\t.args(allArguments)\n\t\t\t\t.build();\n\n\t\tContext context = EaseAgent.getContext();\n\t\tmethodInfo.setInvoker(futureInvokeCallbackConstructTracingInterceptorArgs[2]);\n\t\tsofaRpcResponseFutureTraceInterceptor.after(methodInfo, context);\n\t}\n\n\t@Test\n\t@SneakyThrows\n\tpublic void testNotObtainedRequestContext() {\n\t\tMethodInfo methodInfo = MethodInfo.builder()\n\t\t\t\t.invoker(consumerInvoker)\n\t\t\t\t.args(allArguments)\n\t\t\t\t.build();\n\n\t\tContext context = EaseAgent.getContext();\n\t\tsofaRpcConsumerTraceInterceptor.before(methodInfo, context);\n\t\tsofaRpcConsumerTraceInterceptor.after(methodInfo, context);\n\t\tmethodInfo.setArgs(futureInvokeCallbackConstructTracingInterceptorArgs);\n\t\tsofaRpcFutureInvokeCallbackConstructInterceptor.before(methodInfo, context);\n\t\tMockBoltResponseFuture mockBoltResponseFuture = (MockBoltResponseFuture) futureInvokeCallbackConstructTracingInterceptorArgs[2];\n\n\t\tThread asyncThread = new Thread(new Runnable() {\n\t\t\t@Override\n\t\t\tpublic void run() {\n\t\t\t\tAsyncContext asyncContext = AgentDynamicFieldAccessor.getDynamicFieldValue(mockBoltResponseFuture);\n\t\t\t\tAssert.assertNotNull(asyncContext);\n\t\t\t\tRequestContext requestContext = asyncContext.get(SofaRpcCtxUtils.CLIENT_REQUEST_CONTEXT_KEY);\n\t\t\t\trequestContext.span().finish();\n\t\t\t\tasyncContext.put(SofaRpcCtxUtils.CLIENT_REQUEST_CONTEXT_KEY, null);\n\t\t\t\tmethodInfo.setInvoker(futureInvokeCallbackConstructTracingInterceptorArgs[2]);\n\t\t\t\ttry {\n\t\t\t\t\tsofaRpcResponseFutureTraceInterceptor.after(methodInfo, context);\n\t\t\t\t} catch (NullPointerException ignore) {\n\t\t\t\t    //Must be throw NullPointerException\n\t\t\t\t    return;\n\t\t\t\t}\n\t\t\t\tthrow new RuntimeException(\"Must be throw NullPointerException\");\n\t\t\t}\n\t\t});\n\t\tasyncThread.start();\n\t\tasyncThread.join();\n\t}\n\n}\n"
  },
  {
    "path": "plugins/sofarpc/src/test/resources/mock_agent.properties",
    "content": "## sofarpc arguments collect switch\nplugin.observability.sofarpc.tracing.args.collect.enabled=true\nplugin.observability.sofarpc.tracing.result.collect.enabled=true\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright (c) 2021, MegaEase\n  ~ All rights reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~   http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>plugins</artifactId>\n        <groupId>com.megaease.easeagent</groupId>\n        <version>2.3.0</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>spring-boot-3.5.3</artifactId>\n\n    <packaging>pom</packaging>\n\n\n\n    <modules>\n        <module>spring-boot-rest-template-3.5.3</module>\n        <module>spring-boot-gateway-3.5.3</module>\n        <module>spring-boot-servicename-3.5.3</module>\n    </modules>\n\n    <properties>\n        <java.version.17>17</java.version.17>\n        <spring-cloud.version>2025.0.0</spring-cloud.version>\n        <spring-boot.version>3.5.3</spring-boot.version>\n    </properties>\n\n    <dependencyManagement>\n        <dependencies>\n            <dependency>\n                <groupId>org.springframework.cloud</groupId>\n                <artifactId>spring-cloud-dependencies</artifactId>\n                <version>${spring-cloud.version}</version>\n                <type>pom</type>\n                <scope>import</scope>\n            </dependency>\n        </dependencies>\n    </dependencyManagement>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-compiler-plugin</artifactId>\n                <version>3.11.0</version>\n                <configuration>\n                    <source>${java.version.17}</source>\n                    <target>${java.version.17}</target>\n                </configuration>\n            </plugin>\n        </plugins>\n    </build>\n\n</project>\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright (c) 2021, MegaEase\n  ~ All rights reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~   http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>spring-boot-3.5.3</artifactId>\n        <groupId>com.megaease.easeagent</groupId>\n        <version>2.3.0</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>spring-boot-gateway-3.5.3</artifactId>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>plugin-api</artifactId>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.cloud</groupId>\n            <artifactId>spring-cloud-starter-gateway</artifactId>\n            <exclusions>\n                <exclusion>\n                    <groupId>org.springframework.boot</groupId>\n                    <artifactId>spring-boot-starter-logging</artifactId>\n                </exclusion>\n            </exclusions>\n            <scope>provided</scope>\n        </dependency>\n\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>plugin-api-mock</artifactId>\n            <version>${project.version}</version>\n            <scope>test</scope>\n        </dependency>\n\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-test</artifactId>\n            <version>3.5.3</version>\n            <exclusions>\n                <exclusion>\n                    <groupId>org.springframework.boot</groupId>\n                    <artifactId>spring-boot-starter-logging</artifactId>\n                </exclusion>\n            </exclusions>\n            <scope>test</scope>\n        </dependency>\n\n        <dependency>\n            <groupId>com.fasterxml.jackson.core</groupId>\n            <artifactId>jackson-core</artifactId>\n            <version>2.19.1</version>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.fasterxml.jackson.core</groupId>\n            <artifactId>jackson-databind</artifactId>\n            <version>2.19.1</version>\n            <scope>test</scope>\n        </dependency>\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/AccessPlugin.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring353.gateway;\n\nimport com.megaease.easeagent.plugin.AgentPlugin;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\n\npublic class AccessPlugin implements AgentPlugin {\n    @Override\n    public String getNamespace() {\n        return ConfigConst.Namespace.ACCESS;\n    }\n\n    @Override\n    public String getDomain() {\n        return ConfigConst.OBSERVABILITY;\n    }\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/ForwardedPlugin.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring353.gateway;\n\nimport com.megaease.easeagent.plugin.AgentPlugin;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\n\npublic class ForwardedPlugin implements AgentPlugin {\n    @Override\n    public String getNamespace() {\n        return ConfigConst.Namespace.FORWARDED;\n    }\n\n    @Override\n    public String getDomain() {\n        return ConfigConst.INTEGRABILITY;\n    }\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/GatewayCons.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring353.gateway;\n\nimport com.megaease.easeagent.plugin.CodeVersion;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\n\npublic interface GatewayCons {\n    String SPAN_KEY = GatewayCons.class.getName() + \".SPAN\";\n    String CHILD_SPAN_KEY = GatewayCons.class.getName() + \".CHILD_SPAN\";\n    String CLIENT_RECEIVE_CALLBACK_KEY = GatewayCons.class.getName() + \".CLIENT_RECEIVE_CALLBACK\";\n\n    CodeVersion VERSIONS = CodeVersion.builder()\n        .key(ConfigConst.CodeVersion.KEY_SPRING_BOOT)\n        .add(ConfigConst.CodeVersion.VERSION_SPRING_BOOT3).build();\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/SpringGatewayPlugin.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring353.gateway;\n\nimport com.megaease.easeagent.plugin.AgentPlugin;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\n\npublic class SpringGatewayPlugin implements AgentPlugin {\n\n    @Override\n    public String getNamespace() {\n        return ConfigConst.Namespace.SPRING_GATEWAY;\n    }\n\n    @Override\n    public String getDomain() {\n        return ConfigConst.OBSERVABILITY;\n    }\n\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/advice/AgentGlobalFilterAdvice.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring353.gateway.advice;\n\nimport com.megaease.easeagent.plugin.CodeVersion;\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\nimport easeagent.plugin.spring353.gateway.GatewayCons;\n\nimport java.util.Set;\n\npublic class AgentGlobalFilterAdvice implements Points {\n    @Override\n    public CodeVersion codeVersions() {\n        return GatewayCons.VERSIONS;\n    }\n\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return ClassMatcher.builder()\n            .hasClassName(\"easeagent.plugin.spring353.gateway.interceptor.initialize.AgentGlobalFilter\")\n            .build();\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.builder()\n            .named(\"filter\")\n            .build().toSet();\n    }\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/advice/HttpHeadersFilterAdvice.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring353.gateway.advice;\n\nimport com.megaease.easeagent.plugin.CodeVersion;\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\nimport easeagent.plugin.spring353.gateway.GatewayCons;\n\nimport java.util.Set;\n\npublic class HttpHeadersFilterAdvice implements Points {\n    @Override\n    public CodeVersion codeVersions() {\n        return GatewayCons.VERSIONS;\n    }\n\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return ClassMatcher.builder()\n            .hasClassName(\"org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter\")\n            .build();\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.multiBuilder()\n            .match(MethodMatcher.builder().named(\"filterRequest\")\n                .build())\n            .build();\n    }\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/advice/InitGlobalFilterAdvice.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring353.gateway.advice;\n\nimport com.megaease.easeagent.plugin.CodeVersion;\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\nimport easeagent.plugin.spring353.gateway.GatewayCons;\n\nimport java.util.Set;\n\npublic class InitGlobalFilterAdvice implements Points {\n    @Override\n    public CodeVersion codeVersions() {\n        return GatewayCons.VERSIONS;\n    }\n\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return ClassMatcher.builder()\n            .hasClassName(\"org.springframework.cloud.gateway.config.GatewayAutoConfiguration\")\n            .or().hasClassName(\"org.springframework.cloud.gateway.config.GatewayAutoConfiguration$GatewayActuatorConfiguration\")\n            .build();\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.multiBuilder()\n            .match(MethodMatcher.builder()\n                .named(\"filteringWebHandler\")\n                .or().named(\"gatewayControllerEndpoint\")\n                .or().named(\"gatewayLegacyControllerEndpoint\")\n                .build())\n            .build();\n    }\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/interceptor/TimeUtils.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring353.gateway.interceptor;\n\nimport com.megaease.easeagent.plugin.api.Context;\n\npublic class TimeUtils {\n    public static long startTime(Context context, Object key) {\n        Long start = context.get(key);\n        if (start == null) {\n            start = System.currentTimeMillis();\n            context.put(key, start);\n        }\n        return start;\n    }\n\n    public static Long removeStartTime(Context context, Object key) {\n        return context.remove(key);\n    }\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/interceptor/forwarded/GatewayServerForwardedInterceptor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring353.gateway.interceptor.forwarded;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Cleaner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.interceptor.NonReentrantInterceptor;\nimport easeagent.plugin.spring353.gateway.ForwardedPlugin;\nimport easeagent.plugin.spring353.gateway.advice.AgentGlobalFilterAdvice;\nimport easeagent.plugin.spring353.gateway.interceptor.tracing.FluxHttpServerRequest;\nimport org.springframework.web.server.ServerWebExchange;\n\n@AdviceTo(value = AgentGlobalFilterAdvice.class, plugin = ForwardedPlugin.class)\npublic class GatewayServerForwardedInterceptor implements NonReentrantInterceptor {\n    private static final Object FORWARDED_KEY = new Object();\n\n\n    @Override\n    public void doBefore(MethodInfo methodInfo, Context context) {\n        ServerWebExchange exchange = (ServerWebExchange) methodInfo.getArgs()[0];\n        FluxHttpServerRequest httpServerRequest = new FluxHttpServerRequest(exchange.getRequest());\n        Cleaner cleaner = context.importForwardedHeaders(httpServerRequest);\n        context.put(FORWARDED_KEY, cleaner);\n    }\n\n    @Override\n    public void doAfter(MethodInfo methodInfo, Context context) {\n        Cleaner cleaner = context.remove(FORWARDED_KEY);\n        if (cleaner != null) {\n            cleaner.close();\n        }\n    }\n\n    @Override\n    public String getType() {\n        return ConfigConst.PluginID.FORWARDED;\n    }\n\n    @Override\n    public int order() {\n        return Order.FORWARDED.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/interceptor/initialize/AgentGlobalFilter.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring353.gateway.interceptor.initialize;\n\nimport org.springframework.cloud.gateway.filter.GatewayFilterChain;\nimport org.springframework.cloud.gateway.filter.GlobalFilter;\nimport org.springframework.web.server.ServerWebExchange;\nimport reactor.core.publisher.Mono;\n\npublic class AgentGlobalFilter implements GlobalFilter {\n    public AgentGlobalFilter() {\n    }\n\n    @Override\n    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {\n        return chain.filter(exchange);\n    }\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/interceptor/initialize/GlobalFilterInterceptor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring353.gateway.interceptor.initialize;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport easeagent.plugin.spring353.gateway.SpringGatewayPlugin;\nimport easeagent.plugin.spring353.gateway.advice.InitGlobalFilterAdvice;\nimport org.springframework.cloud.gateway.filter.GlobalFilter;\n\nimport java.util.List;\n\n@AdviceTo(value = InitGlobalFilterAdvice.class, plugin = SpringGatewayPlugin.class)\npublic class GlobalFilterInterceptor implements Interceptor {\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public void before(MethodInfo methodInfo, Context context) {\n        List<GlobalFilter> list = null;\n        switch (methodInfo.getMethod()) {\n            case \"filteringWebHandler\":\n            case \"gatewayControllerEndpoint\":\n                list = (List<GlobalFilter>) methodInfo.getArgs()[0];\n                break;\n            case \"gatewayLegacyControllerEndpoint\":\n                list = (List<GlobalFilter>) methodInfo.getArgs()[1];\n                break;\n        }\n        if (list == null || hasAgentFilter(list)) {\n            return;\n        }\n        list.add(0, new AgentGlobalFilter());\n    }\n\n    private boolean hasAgentFilter(List<GlobalFilter> list) {\n        for (GlobalFilter globalFilter : list) {\n            if (globalFilter instanceof AgentGlobalFilter) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    @Override\n    public String getType() {\n        return Order.INIT.getName();\n    }\n\n    @Override\n    public int order() {\n        return Order.INIT.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/interceptor/log/GatewayAccessLogInterceptor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring353.gateway.interceptor.log;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.AsyncContext;\nimport com.megaease.easeagent.plugin.api.context.RequestContext;\nimport com.megaease.easeagent.plugin.api.logging.AccessLogInfo;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.tools.metrics.AccessLogServerInfo;\nimport com.megaease.easeagent.plugin.tools.metrics.HttpLog;\nimport easeagent.plugin.spring353.gateway.AccessPlugin;\nimport easeagent.plugin.spring353.gateway.advice.AgentGlobalFilterAdvice;\nimport easeagent.plugin.spring353.gateway.GatewayCons;\nimport easeagent.plugin.spring353.gateway.reactor.AgentMono;\nimport org.springframework.web.server.ServerWebExchange;\nimport reactor.core.publisher.Mono;\n\nimport static easeagent.plugin.spring353.gateway.interceptor.TimeUtils.removeStartTime;\nimport static easeagent.plugin.spring353.gateway.interceptor.TimeUtils.startTime;\n\n\n@AdviceTo(value = AgentGlobalFilterAdvice.class, plugin = AccessPlugin.class)\npublic class GatewayAccessLogInterceptor implements Interceptor {\n    private static final Object START_TIME = new Object();\n    private final HttpLog httpLog = new HttpLog();\n\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n        ServerWebExchange exchange = (ServerWebExchange) methodInfo.getArgs()[0];\n        AccessLogServerInfo serverInfo = this.serverInfo(exchange);\n        Long beginTime = startTime(context, START_TIME);\n        AccessLogInfo accessLog = this.httpLog.prepare(getSystem(),\n            getServiceName(), beginTime, getSpan(exchange), serverInfo);\n        exchange.getAttributes().put(AccessLogInfo.class.getName(), accessLog);\n    }\n\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public void after(MethodInfo methodInfo, Context context) {\n        try {\n            // async\n            Mono<Void> mono = (Mono<Void>) methodInfo.getRetValue();\n            methodInfo.setRetValue(new AgentMono(mono, methodInfo, context.exportAsync(), this::finishCallback));\n        } finally {\n            removeStartTime(context, START_TIME);\n        }\n    }\n\n    Span getSpan(ServerWebExchange exchange) {\n        RequestContext pCtx = exchange.getAttribute(GatewayCons.SPAN_KEY);\n        if (pCtx == null) {\n            return null;\n        }\n        return pCtx.span();\n    }\n\n    private void finishCallback(MethodInfo methodInfo, AsyncContext ctx) {\n        ServerWebExchange exchange = (ServerWebExchange) methodInfo.getArgs()[0];\n        AccessLogInfo accessLog = exchange.getAttribute(AccessLogInfo.class.getName());\n        if (accessLog == null) {\n            return;\n        }\n        Long beginTime = ctx.get(START_TIME);\n        AccessLogServerInfo serverInfo = this.serverInfo(exchange);\n        this.httpLog.finish(accessLog, methodInfo.isSuccess(), beginTime, serverInfo);\n        EaseAgent.getAgentReport().report(accessLog);\n    }\n\n    AccessLogServerInfo serverInfo(ServerWebExchange exchange) {\n        SpringGatewayAccessLogServerInfo serverInfo = new SpringGatewayAccessLogServerInfo();\n        serverInfo.load(exchange);\n        return serverInfo;\n    }\n\n    String getSystem() {\n        return EaseAgent.getConfig(\"system\");\n    }\n\n    String getServiceName() {\n        return EaseAgent.getConfig(\"name\");\n    }\n\n    @Override\n    public String getType() {\n        return Order.LOG.getName();\n    }\n\n    @Override\n    public int order() {\n        return Order.LOG.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/interceptor/log/SpringGatewayAccessLogServerInfo.java",
    "content": " /*\n  * Copyright (c) 2021, MegaEase\n  * All rights reserved.\n  *\n  * Licensed under the Apache License, Version 2.0 (the \"License\");\n  * you may not use this file except in compliance with the License.\n  * You may obtain a copy of the License at\n  *\n  *     http://www.apache.org/licenses/LICENSE-2.0\n  *\n  * Unless required by applicable law or agreed to in writing, software\n  * distributed under the License is distributed on an \"AS IS\" BASIS,\n  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  * See the License for the specific language governing permissions and\n  * limitations under the License.\n  */\n\npackage easeagent.plugin.spring353.gateway.interceptor.log;\n\nimport com.megaease.easeagent.plugin.tools.metrics.AccessLogServerInfo;\nimport org.springframework.cloud.gateway.route.Route;\nimport org.springframework.cloud.gateway.support.ServerWebExchangeUtils;\nimport org.springframework.http.HttpMethod;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.HttpStatusCode;\nimport org.springframework.web.server.ServerWebExchange;\n\nimport java.net.InetSocketAddress;\nimport java.util.Map;\nimport java.util.Optional;\n\npublic class SpringGatewayAccessLogServerInfo implements AccessLogServerInfo {\n    private ServerWebExchange exchange;\n\n    public void load(ServerWebExchange exchange) {\n        this.exchange = exchange;\n    }\n\n    @Override\n    public String getMethod() {\n        return exchange.getRequest().getMethod().name();\n    }\n\n    @Override\n    public String getHeader(String key) {\n        return exchange.getRequest().getHeaders().getFirst(key);\n    }\n\n    @Override\n    public String getRemoteAddr() {\n        InetSocketAddress remoteAddress = exchange.getRequest().getRemoteAddress();\n        return remoteAddress == null ? null : remoteAddress.getHostString();\n    }\n\n    @Override\n    public String getRequestURI() {\n        return exchange.getRequest().getURI().toString();\n    }\n\n    @Override\n    public int getResponseBufferSize() {\n        return 0;\n    }\n\n    @Override\n    public String getMatchURL() {\n        HttpMethod httpMethod = exchange.getRequest().getMethod();\n        if (httpMethod == null) {\n            return \"\";\n        }\n        Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);\n        if (route != null && route.getUri() != null) {\n            return httpMethod.name() + \" \" + route.getUri().toString();\n        }\n        return httpMethod.name() + \" \" + getRequestURI();\n    }\n\n    @Override\n    public Map<String, String> findHeaders() {\n        return exchange.getRequest().getHeaders().toSingleValueMap();\n    }\n\n    @Override\n    public Map<String, String> findQueries() {\n        return exchange.getRequest().getQueryParams().toSingleValueMap();\n    }\n\n    @Override\n    public String getStatusCode() {\n        HttpStatusCode rawStatusCode = exchange.getResponse().getStatusCode();\n        return Optional.ofNullable(rawStatusCode).map(e -> e.value() + \"\").orElse(\"0\");\n    }\n}\n\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/interceptor/metric/GatewayMetricsInterceptor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring353.gateway.interceptor.metric;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.context.AsyncContext;\nimport com.megaease.easeagent.plugin.api.metric.ServiceMetricRegistry;\nimport com.megaease.easeagent.plugin.api.metric.name.Tags;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.tools.metrics.ServerMetric;\nimport com.megaease.easeagent.plugin.utils.SystemClock;\nimport easeagent.plugin.spring353.gateway.SpringGatewayPlugin;\nimport easeagent.plugin.spring353.gateway.advice.AgentGlobalFilterAdvice;\nimport easeagent.plugin.spring353.gateway.reactor.AgentMono;\nimport org.springframework.cloud.gateway.route.Route;\nimport org.springframework.cloud.gateway.support.ServerWebExchangeUtils;\nimport org.springframework.http.HttpMethod;\nimport org.springframework.http.HttpStatusCode;\nimport org.springframework.web.server.ServerWebExchange;\nimport reactor.core.publisher.Mono;\n\nimport static easeagent.plugin.spring353.gateway.interceptor.TimeUtils.removeStartTime;\nimport static easeagent.plugin.spring353.gateway.interceptor.TimeUtils.startTime;\n\n\n@AdviceTo(value = AgentGlobalFilterAdvice.class, plugin = SpringGatewayPlugin.class)\npublic class GatewayMetricsInterceptor implements Interceptor {\n    private static Object START_TIME = new Object();\n    private static volatile ServerMetric SERVER_METRIC = null;\n\n    @Override\n    public void init(IPluginConfig config, String className, String methodName, String methodDescriptor) {\n        SERVER_METRIC = ServiceMetricRegistry.getOrCreate(config,\n            new Tags(\"application\", \"http-request\", \"url\"), ServerMetric.SERVICE_METRIC_SUPPLIER);\n    }\n\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n        startTime(context, START_TIME);\n        // context.put(START, SystemClock.now());\n    }\n\n\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public void after(MethodInfo methodInfo, Context context) {\n        try {\n            ServerWebExchange exchange = (ServerWebExchange) methodInfo.getArgs()[0];\n            if (!methodInfo.isSuccess()) {\n                String key = getKey(exchange);\n                Long start = startTime(context, START_TIME);\n                long end = System.currentTimeMillis();\n                SERVER_METRIC.collectMetric(key, 500, methodInfo.getThrowable(), start, end);\n                return;\n            }\n            // async\n            Mono<Void> mono = (Mono<Void>) methodInfo.getRetValue();\n            methodInfo.setRetValue(new AgentMono(mono, methodInfo, context.exportAsync(), this::finishCallback));\n        } finally {\n            removeStartTime(context, START_TIME);\n        }\n    }\n\n    void finishCallback(MethodInfo methodInfo, AsyncContext ctx) {\n        ServerWebExchange exchange = (ServerWebExchange) methodInfo.getArgs()[0];\n        String key = getKey(exchange);\n        HttpStatusCode statusCode = exchange.getResponse().getStatusCode();\n        int code = 0;\n        if (statusCode != null) {\n            code = statusCode.value();\n        }\n        SERVER_METRIC.collectMetric(key, code, methodInfo.getThrowable(),\n            ctx.get(START_TIME), SystemClock.now());\n    }\n\n    public static String getKey(ServerWebExchange exchange) {\n        HttpMethod httpMethod = exchange.getRequest().getMethod();\n        if (httpMethod == null) {\n            return \"\";\n        }\n        Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);\n        String key;\n        if (route != null && route.getUri() != null) {\n            key = httpMethod.name() + \" \" + route.getUri().toString();\n        } else {\n            key = httpMethod.name() + \" \" + exchange.getRequest().getURI();\n        }\n        return key;\n    }\n\n    @Override\n    public String getType() {\n        return Order.METRIC.getName();\n    }\n\n    @Override\n    public int order() {\n        return Order.METRIC.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/interceptor/tracing/FluxHttpServerRequest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring353.gateway.interceptor.tracing;\n\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.tools.trace.HttpRequest;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.http.server.reactive.ServerHttpRequest;\n\npublic class FluxHttpServerRequest implements HttpRequest {\n    private final ServerHttpRequest request;\n\n    public FluxHttpServerRequest(ServerHttpRequest request) {\n        this.request = request;\n    }\n\n    @Override\n    public Span.Kind kind() {\n        return Span.Kind.SERVER;\n    }\n\n    @Override\n    public String header(String name) {\n        HttpHeaders headers = this.request.getHeaders();\n        return headers.getFirst(name);\n    }\n\n    @Override\n    public boolean cacheScope() {\n        return false;\n    }\n\n    @Override\n    public void setHeader(String name, String value) {\n    }\n\n    @Override\n    public String method() {\n        return this.request.getMethod().name();\n    }\n\n    @Override\n    public String path() {\n        return this.request.getPath().value();\n    }\n\n    @Override\n    public String route() {\n        return null;\n    }\n\n    @Override\n    public String getRemoteAddr() {\n        if (this.request != null && this.request.getRemoteAddress() != null) {\n            return this.request.getRemoteAddress().getAddress().getHostAddress();\n        } else {\n            return \"Unknown\";\n        }\n    }\n\n    @Override\n    public int getRemotePort() {\n        if (this.request != null && this.request.getRemoteAddress() != null) {\n            return this.request.getRemoteAddress().getPort();\n        } else {\n            return 0;\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/interceptor/tracing/FluxHttpServerResponse.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring353.gateway.interceptor.tracing;\n\nimport com.megaease.easeagent.plugin.tools.trace.HttpResponse;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.http.server.reactive.ServerHttpResponse;\nimport org.springframework.web.reactive.HandlerMapping;\nimport org.springframework.web.server.ServerWebExchange;\nimport org.springframework.web.util.pattern.PathPattern;\n\npublic class FluxHttpServerResponse implements HttpResponse {\n    private final FluxHttpServerRequest request;\n    private final ServerHttpResponse response;\n    private final String route;\n    private final Throwable error;\n\n    public FluxHttpServerResponse(FluxHttpServerRequest request,\n                                  ServerHttpResponse response, String route, Throwable error) {\n        this.request = request;\n        this.response = response;\n        this.route = route;\n        this.error = error;\n    }\n\n    public FluxHttpServerResponse(ServerWebExchange exchange, Throwable error) {\n        this.request = new FluxHttpServerRequest(exchange.getRequest());\n        this.response = exchange.getResponse();\n\n        PathPattern bestPattern = exchange.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);\n        String route = null;\n        if (bestPattern != null) {\n            route = bestPattern.getPatternString();\n        }\n\n        this.route = route;\n        this.error = error;\n    }\n\n    @Override\n    public String header(String name) {\n        if (this.response == null) {\n            return null;\n        }\n        HttpHeaders hs = this.response.getHeaders();\n        return hs.getFirst(name);\n    }\n\n    @Override\n    public String method() {\n        return this.request.method();\n    }\n\n    @Override\n    public String route() {\n        return this.route;\n    }\n\n    @Override\n    public int statusCode() {\n        if (this.response != null && this.response.getStatusCode() != null) {\n            return this.response.getStatusCode().value();\n        } else {\n            return 0;\n        }\n    }\n\n    @Override\n    public Throwable maybeError() {\n        return this.error;\n    }\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/interceptor/tracing/GatewayServerTracingInterceptor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring353.gateway.interceptor.tracing;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Cleaner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.AsyncContext;\nimport com.megaease.easeagent.plugin.api.context.RequestContext;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.tools.trace.HttpResponse;\nimport com.megaease.easeagent.plugin.tools.trace.HttpUtils;\nimport easeagent.plugin.spring353.gateway.SpringGatewayPlugin;\nimport easeagent.plugin.spring353.gateway.advice.AgentGlobalFilterAdvice;\nimport easeagent.plugin.spring353.gateway.GatewayCons;\nimport easeagent.plugin.spring353.gateway.reactor.AgentMono;\nimport org.springframework.web.reactive.HandlerMapping;\nimport org.springframework.web.server.ServerWebExchange;\nimport org.springframework.web.util.pattern.PathPattern;\nimport reactor.core.publisher.Mono;\n\nimport java.util.function.BiConsumer;\n\n@AdviceTo(value = AgentGlobalFilterAdvice.class, plugin = SpringGatewayPlugin.class)\npublic class GatewayServerTracingInterceptor implements Interceptor {\n    static final String SPAN_CONTEXT_KEY = GatewayServerTracingInterceptor.class.getName() + \"-P-CTX\";\n\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n        ServerWebExchange exchange = (ServerWebExchange) methodInfo.getArgs()[0];\n        FluxHttpServerRequest httpServerRequest = new FluxHttpServerRequest(exchange.getRequest());\n        RequestContext pCtx = context.serverReceive(httpServerRequest);\n        HttpUtils.handleReceive(pCtx.span(), httpServerRequest);\n        context.put(SPAN_CONTEXT_KEY, pCtx);\n        context.put(FluxHttpServerRequest.class, httpServerRequest);\n        exchange.getAttributes().put(GatewayCons.SPAN_KEY, pCtx);\n    }\n\n    private void cleanContext(Context context) {\n        context.remove(FluxHttpServerRequest.class);\n        context.remove(SPAN_CONTEXT_KEY);\n    }\n\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public void after(MethodInfo methodInfo, Context context) {\n        RequestContext pCtx = context.get(SPAN_CONTEXT_KEY);\n        if (pCtx == null) {\n            return;\n        }\n        try {\n            if (!methodInfo.isSuccess()) {\n                pCtx.span().error(methodInfo.getThrowable());\n                pCtx.span().finish();\n                return;\n            }\n\n            // async\n            Mono<Void> mono = (Mono<Void>) methodInfo.getRetValue();\n            methodInfo.setRetValue(new AgentMono(mono, methodInfo, context.exportAsync(), this::finishCallback));\n        } finally {\n            cleanContext(context);\n            pCtx.scope().close();\n        }\n\n    }\n\n    void finishCallback(MethodInfo methodInfo, AsyncContext ctx) {\n        try (Cleaner ignored = ctx.importToCurrent()) {\n            RequestContext pCtx = EaseAgent.getContext().get(SPAN_CONTEXT_KEY);\n            ServerWebExchange exchange = (ServerWebExchange) methodInfo.getArgs()[0];\n            BiConsumer<ServerWebExchange, MethodInfo> consumer = exchange.getAttribute(GatewayCons.CLIENT_RECEIVE_CALLBACK_KEY);\n            if (consumer != null) {\n                consumer.accept(exchange, methodInfo);\n            }\n\n            FluxHttpServerRequest httpServerRequest = EaseAgent.getContext().get(FluxHttpServerRequest.class);\n            PathPattern bestPattern = exchange.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);\n            String route = null;\n            if (bestPattern != null) {\n                route = bestPattern.getPatternString();\n            }\n            HttpResponse response = new FluxHttpServerResponse(httpServerRequest,\n                exchange.getResponse(), route, methodInfo.getThrowable());\n            HttpUtils.finish(pCtx.span(), response);\n            exchange.getAttributes().remove(GatewayCons.SPAN_KEY);\n        }\n    }\n\n    @Override\n    public String getType() {\n        return Order.TRACING.getName();\n    }\n\n    @Override\n    public int order() {\n        return Order.TRACING.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/interceptor/tracing/HttpHeadersFilterTracingInterceptor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring353.gateway.interceptor.tracing;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.RequestContext;\nimport com.megaease.easeagent.plugin.api.trace.Scope;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.interceptor.NonReentrantInterceptor;\nimport com.megaease.easeagent.plugin.tools.trace.HttpUtils;\nimport easeagent.plugin.spring353.gateway.SpringGatewayPlugin;\nimport easeagent.plugin.spring353.gateway.advice.HttpHeadersFilterAdvice;\nimport easeagent.plugin.spring353.gateway.GatewayCons;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.http.server.reactive.ServerHttpRequest;\nimport org.springframework.web.server.ServerWebExchange;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.function.BiConsumer;\n\n@AdviceTo(value = HttpHeadersFilterAdvice.class, plugin = SpringGatewayPlugin.class)\npublic class HttpHeadersFilterTracingInterceptor implements NonReentrantInterceptor {\n    // org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter\n    static final String CLIENT_HEADER_ATTR = HttpHeadersFilterTracingInterceptor.class.getName() + \".Headers\";\n\n    @Override\n    public void doAfter(MethodInfo methodInfo, Context context) {\n        ServerWebExchange exchange = (ServerWebExchange) methodInfo.getArgs()[1];\n        HttpHeaders retHttpHeaders = (HttpHeaders) methodInfo.getRetValue();\n        RequestContext pCtx = exchange.getAttribute(GatewayCons.SPAN_KEY);\n        if (pCtx == null) {\n            return;\n        }\n        FluxHttpServerRequest request = new HeaderFilterRequest(exchange.getRequest());\n\n        RequestContext pnCtx = context.clientRequest(request);\n        try (Scope ignored = pnCtx.scope()) {\n            pnCtx.span().start();\n            exchange.getAttributes().put(GatewayCons.CHILD_SPAN_KEY, pnCtx);\n            Map<String, String> map = getHeadersFromExchange(exchange);\n            map.putAll(retHttpHeaders.toSingleValueMap());\n            map.putAll(pnCtx.getHeaders());\n            HttpHeaders httpHeaders = new HttpHeaders();\n            httpHeaders.setAll(map);\n            methodInfo.setRetValue(httpHeaders);\n\n            BiConsumer<ServerWebExchange, MethodInfo> consumer = (serverWebExchange, info) -> {\n                RequestContext p = serverWebExchange.getAttribute(GatewayCons.CHILD_SPAN_KEY);\n                if (p == null) {\n                    return;\n                }\n                FluxHttpServerResponse response = new FluxHttpServerResponse(serverWebExchange, info.getThrowable());\n                HttpUtils.save(p.span(), response);\n                p.finish(response);\n            };\n            exchange.getAttributes().put(GatewayCons.CLIENT_RECEIVE_CALLBACK_KEY, consumer);\n        }\n    }\n\n    @Override\n    public int order() {\n        return Order.HIGH.getOrder();\n    }\n\n    private Map<String, String> getHeadersFromExchange(ServerWebExchange exchange) {\n        Map<String, String> headers = exchange.getAttribute(CLIENT_HEADER_ATTR);\n        if (headers == null) {\n            headers = new HashMap<>();\n            exchange.getAttributes().put(CLIENT_HEADER_ATTR, headers);\n        }\n        return headers;\n    }\n\n    static class HeaderFilterRequest extends FluxHttpServerRequest {\n        public HeaderFilterRequest(ServerHttpRequest request) {\n            super(request);\n        }\n\n\n        @Override\n        public Span.Kind kind() {\n            return Span.Kind.CLIENT;\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/reactor/AgentCoreSubscriber.java",
    "content": " /*\n  * Copyright (c) 2021, MegaEase\n  * All rights reserved.\n  *\n  * Licensed under the Apache License, Version 2.0 (the \"License\");\n  * you may not use this file except in compliance with the License.\n  * You may obtain a copy of the License at\n  *\n  *     http://www.apache.org/licenses/LICENSE-2.0\n  *\n  * Unless required by applicable law or agreed to in writing, software\n  * distributed under the License is distributed on an \"AS IS\" BASIS,\n  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  * See the License for the specific language governing permissions and\n  * limitations under the License.\n  */\n\n package easeagent.plugin.spring353.gateway.reactor;\n\n import com.megaease.easeagent.plugin.api.context.AsyncContext;\n import com.megaease.easeagent.plugin.interceptor.MethodInfo;\n import org.reactivestreams.Subscription;\n import reactor.core.CoreSubscriber;\n\n import javax.annotation.Nonnull;\n import java.util.function.BiConsumer;\n\n public class AgentCoreSubscriber implements CoreSubscriber<Void> {\n\n     private final CoreSubscriber<Void> actual;\n     private final MethodInfo methodInfo;\n     private final AsyncContext asyncContext;\n     private final BiConsumer<MethodInfo, AsyncContext> finish;\n\n     @SuppressWarnings(\"unchecked\")\n     public AgentCoreSubscriber(CoreSubscriber<? super Void> actual,\n                                MethodInfo methodInfo,\n                                // Object context,\n                                AsyncContext async,\n                                BiConsumer<MethodInfo, AsyncContext> finish) {\n         this.actual = (CoreSubscriber<Void>) actual;\n         this.methodInfo = methodInfo;\n         this.finish = finish;\n         this.asyncContext = async;\n     }\n\n     @Nonnull\n     @Override\n     public reactor.util.context.Context currentContext() {\n         return actual.currentContext();\n     }\n\n     @Override\n     public void onSubscribe(@Nonnull Subscription s) {\n         actual.onSubscribe(s);\n     }\n\n     @Override\n     public void onNext(Void t) {\n         actual.onNext(t);\n     }\n\n     @Override\n     public void onError(Throwable t) {\n         actual.onError(t);\n         methodInfo.setThrowable(t);\n         finish.accept(this.methodInfo, asyncContext);\n     }\n\n     @Override\n     public void onComplete() {\n         actual.onComplete();\n         finish.accept(this.methodInfo, asyncContext);\n     }\n }\n\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/reactor/AgentMono.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring353.gateway.reactor;\n\nimport com.megaease.easeagent.plugin.api.Cleaner;\nimport com.megaease.easeagent.plugin.api.context.AsyncContext;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport reactor.core.CoreSubscriber;\nimport reactor.core.publisher.Mono;\n\nimport javax.annotation.Nonnull;\nimport java.util.function.BiConsumer;\n\npublic class AgentMono extends Mono<Void> {\n    private final Mono<Void> source;\n    private final MethodInfo methodInfo;\n    private final AsyncContext asyncContext;\n    private final BiConsumer<MethodInfo, AsyncContext> finish;\n\n    public AgentMono(Mono<Void> mono, MethodInfo methodInfo,\n                     AsyncContext async,\n                     BiConsumer<MethodInfo, AsyncContext> consumer) {\n        this.source = mono;\n        this.methodInfo = methodInfo;\n        this.finish = consumer;\n        this.asyncContext = async;\n    }\n\n    @Override\n    public void subscribe(@Nonnull CoreSubscriber<? super Void> actual) {\n        try (Cleaner ignored = asyncContext.importToCurrent()) {\n            this.source.subscribe(new AgentCoreSubscriber(actual, methodInfo,\n                asyncContext, finish));\n        }\n    }\n\n    public MethodInfo getMethodInfo() {\n        return methodInfo;\n    }\n\n    public AsyncContext getAsyncContext() {\n        return asyncContext;\n    }\n\n    public BiConsumer<MethodInfo, AsyncContext> getFinish() {\n        return finish;\n    }\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/TestConst.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring353.gateway;\n\npublic class TestConst {\n    public static final String FORWARDED_NAME = \"X-Forwarded-For\";\n    public static final String FORWARDED_VALUE = \"testForwarded\";\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/TestServerWebExchangeUtils.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring353.gateway;\n\nimport org.springframework.http.HttpStatus;\nimport org.springframework.mock.http.server.reactive.MockServerHttpRequest;\nimport org.springframework.mock.web.server.MockServerWebExchange;\nimport org.springframework.web.reactive.HandlerMapping;\nimport org.springframework.web.util.pattern.PathPattern;\nimport org.springframework.web.util.pattern.PathPatternParser;\n\nimport java.net.InetSocketAddress;\n\npublic class TestServerWebExchangeUtils {\n\n    public static final MockServerHttpRequest.BaseBuilder<?> builder() {\n        return MockServerHttpRequest.get(\"http://192.168.0.12:8080/test\", \"a=b\")\n            .header(TestConst.FORWARDED_NAME, TestConst.FORWARDED_VALUE)\n            .queryParam(\"a\", \"b\");\n    }\n\n    public static final MockServerWebExchange build(MockServerHttpRequest.BaseBuilder<?> requestBuilder) {\n        MockServerHttpRequest mockServerHttpRequest = requestBuilder.build();\n        return MockServerWebExchange.builder(mockServerHttpRequest).build();\n    }\n\n\n    public static final MockServerWebExchange mockServerWebExchange() {\n        MockServerHttpRequest mockServerHttpRequest = TestServerWebExchangeUtils.builder()\n            .remoteAddress(new InetSocketAddress(\"192.168.0.12\", 8080)).build();\n        MockServerWebExchange mockServerWebExchange = MockServerWebExchange.builder(mockServerHttpRequest).build();\n        mockServerWebExchange.getResponse().setStatusCode(HttpStatus.OK);\n        PathPatternParser parser = new PathPatternParser();\n        PathPattern pathPattern = parser.parse(\"/test\");\n        mockServerWebExchange.getAttributes().put(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE, pathPattern);\n        return mockServerWebExchange;\n    }\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/interceptor/TimeUtilsTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring353.gateway.interceptor;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport easeagent.plugin.spring353.gateway.interceptor.TimeUtils;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static org.junit.Assert.*;\n\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class TimeUtilsTest {\n\n    @Test\n    public void startTime() throws InterruptedException {\n        Object key = new Object();\n        long startTime = TimeUtils.startTime(EaseAgent.getContext(), key);\n        Thread.sleep(10);\n        assertEquals(startTime, TimeUtils.startTime(EaseAgent.getContext(), key));\n        Object key2 = new Object();\n        assertNotEquals(startTime, TimeUtils.startTime(EaseAgent.getContext(), key2));\n    }\n\n    @Test\n    public void removeStartTime() throws InterruptedException {\n        Object key = new Object();\n        long startTime = TimeUtils.startTime(EaseAgent.getContext(), key);\n        Long startObj = TimeUtils.removeStartTime(EaseAgent.getContext(), key);\n        assertNotNull(startObj);\n        assertEquals(startTime, (long) startObj);\n        Thread.sleep(10);\n        assertNotEquals(startTime, TimeUtils.startTime(EaseAgent.getContext(), key));\n    }\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/interceptor/forwarded/GatewayServerForwardedInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring353.gateway.interceptor.forwarded;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport easeagent.plugin.spring353.gateway.TestConst;\nimport easeagent.plugin.spring353.gateway.TestServerWebExchangeUtils;\nimport static easeagent.plugin.spring353.gateway.TestServerWebExchangeUtils.builder;\n\nimport easeagent.plugin.spring353.gateway.interceptor.forwarded.GatewayServerForwardedInterceptor;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.mock.web.server.MockServerWebExchange;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNull;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class GatewayServerForwardedInterceptorTest {\n\n    @Test\n    public void doBefore() {\n        GatewayServerForwardedInterceptor interceptor = new GatewayServerForwardedInterceptor();\n        MockServerWebExchange mockServerWebExchange = TestServerWebExchangeUtils.build(builder().header(TestConst.FORWARDED_NAME, TestConst.FORWARDED_VALUE));\n        Context context = EaseAgent.getContext();\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{mockServerWebExchange}).build();\n        interceptor.doBefore(methodInfo, context);\n        assertEquals(TestConst.FORWARDED_VALUE, context.get(TestConst.FORWARDED_NAME));\n        interceptor.doAfter(methodInfo, context);\n        assertNull(context.get(TestConst.FORWARDED_NAME));\n    }\n\n    @Test\n    public void doAfter() {\n        doBefore();\n    }\n\n    @Test\n    public void getType() {\n        GatewayServerForwardedInterceptor interceptor = new GatewayServerForwardedInterceptor();\n        assertEquals(ConfigConst.PluginID.FORWARDED, interceptor.getType());\n    }\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/interceptor/initialize/AgentGlobalFilterTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring353.gateway.interceptor.initialize;\n\nimport org.junit.Test;\nimport org.springframework.mock.web.server.MockServerWebExchange;\n\nimport java.util.concurrent.atomic.AtomicBoolean;\n\nimport static easeagent.plugin.spring353.gateway.TestServerWebExchangeUtils.mockServerWebExchange;\nimport static org.junit.Assert.assertSame;\nimport static org.junit.Assert.assertTrue;\n\npublic class AgentGlobalFilterTest {\n\n    @Test\n    public void filter() {\n        AgentGlobalFilter agentGlobalFilter = new AgentGlobalFilter();\n        AtomicBoolean ran = new AtomicBoolean(true);\n        MockServerWebExchange mockServerWebExchange = mockServerWebExchange();\n        agentGlobalFilter.filter(mockServerWebExchange, exchange -> {\n            assertSame(mockServerWebExchange, exchange);\n            ran.set(true);\n            return null;\n        });\n        assertTrue(ran.get());\n    }\n\n\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/interceptor/initialize/GlobalFilterInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring353.gateway.interceptor.initialize;\n\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport org.junit.Test;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport static org.junit.Assert.assertEquals;\n\npublic class GlobalFilterInterceptorTest {\n\n    @Test\n    public void before() {\n        GlobalFilterInterceptor interceptor = new GlobalFilterInterceptor();\n        List arg = new ArrayList();\n        MethodInfo methodInfo = MethodInfo.builder().method(\"filteringWebHandler\").args(new Object[]{arg}).build();\n        interceptor.before(methodInfo, null);\n        assertEquals(1, arg.size());\n        assertEquals(1, arg.size());\n        arg.clear();\n        methodInfo = MethodInfo.builder().method(\"gatewayControllerEndpoint\").args(new Object[]{arg}).build();\n        interceptor.before(methodInfo, null);\n        assertEquals(1, arg.size());\n        arg.clear();\n        methodInfo = MethodInfo.builder().method(\"gatewayLegacyControllerEndpoint\").args(new Object[]{null, arg}).build();\n        interceptor.before(methodInfo, null);\n        assertEquals(1, arg.size());\n\n    }\n\n    @Test\n    public void getType() {\n        GlobalFilterInterceptor interceptor = new GlobalFilterInterceptor();\n        assertEquals(ConfigConst.PluginID.INIT, interceptor.getType());\n    }\n\n    @Test\n    public void order() {\n        GlobalFilterInterceptor interceptor = new GlobalFilterInterceptor();\n        assertEquals(Order.INIT.getOrder(), interceptor.order());\n    }\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/interceptor/log/GatewayAccessLogInfoInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring353.gateway.interceptor.log;\n\nimport com.megaease.easeagent.mock.plugin.api.MockEaseAgent;\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.mock.plugin.api.utils.InterceptorTestUtils;\nimport com.megaease.easeagent.mock.plugin.api.utils.TagVerifier;\nimport com.megaease.easeagent.mock.report.MockReport;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.api.context.RequestContext;\nimport com.megaease.easeagent.plugin.api.logging.AccessLogInfo;\nimport com.megaease.easeagent.plugin.api.trace.Scope;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.utils.common.HostAddress;\nimport easeagent.plugin.spring353.gateway.AccessPlugin;\nimport easeagent.plugin.spring353.gateway.TestConst;\nimport easeagent.plugin.spring353.gateway.TestServerWebExchangeUtils;\nimport easeagent.plugin.spring353.gateway.interceptor.log.GatewayAccessLogInterceptor;\nimport easeagent.plugin.spring353.gateway.interceptor.TimeUtils;\nimport easeagent.plugin.spring353.gateway.interceptor.tracing.GatewayServerTracingInterceptorTest;\nimport easeagent.plugin.spring353.gateway.reactor.AgentMono;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.mock.web.server.MockServerWebExchange;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class GatewayAccessLogInfoInterceptorTest {\n    private Object startTime = AgentFieldReflectAccessor.getStaticFieldValue(GatewayAccessLogInterceptor.class, \"START_TIME\");\n\n    @Test\n    public void before() {\n        GatewayAccessLogInterceptor interceptor = new GatewayAccessLogInterceptor();\n        Context context = EaseAgent.getContext();\n        MockServerWebExchange mockServerWebExchange = TestServerWebExchangeUtils.mockServerWebExchange();\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{mockServerWebExchange}).build();\n        interceptor.before(methodInfo, context);\n        AccessLogInfo accessLog = (AccessLogInfo) mockServerWebExchange.getAttributes().get(AccessLogInfo.class.getName());\n        assertNotNull(accessLog);\n        verify(accessLog, TimeUtils.startTime(context, startTime));\n        assertNull(accessLog.getTraceId());\n        assertNull(accessLog.getSpanId());\n        assertNull(accessLog.getParentSpanId());\n\n        mockServerWebExchange = TestServerWebExchangeUtils.mockServerWebExchange();\n        methodInfo = MethodInfo.builder().args(new Object[]{mockServerWebExchange}).build();\n        interceptor.before(methodInfo, context);\n        RequestContext requestContext = GatewayServerTracingInterceptorTest.beforeGatewayServerTracing(mockServerWebExchange);\n        Span span = requestContext.span();\n        try (Scope ignored = requestContext.scope()) {\n            interceptor.before(methodInfo, context);\n            accessLog = (AccessLogInfo) mockServerWebExchange.getAttributes().get(AccessLogInfo.class.getName());\n            assertNotNull(accessLog);\n            verify(accessLog, TimeUtils.startTime(context, startTime));\n            assertEquals(span.traceIdString(), accessLog.getTraceId());\n            assertEquals(span.spanIdString(), accessLog.getSpanId());\n            assertEquals(span.parentIdString(), accessLog.getParentSpanId());\n        }\n        span.finish();\n    }\n\n\n    public void verify(AccessLogInfo accessLog, long startTime) {\n        assertEquals(\"test-gateway-system\", accessLog.getSystem());\n        assertEquals(\"test-gateway-service\", accessLog.getService());\n        assertEquals(HostAddress.localhost(), accessLog.getHostName());\n        assertEquals(HostAddress.getHostIpv4(), accessLog.getHostIpv4());\n        assertEquals(\"GET http://192.168.0.12:8080/test?a=b\", accessLog.getUrl());\n        assertEquals(\"GET\", accessLog.getMethod());\n        assertEquals(TestConst.FORWARDED_VALUE, accessLog.getHeaders().get(TestConst.FORWARDED_NAME));\n        assertEquals(startTime, accessLog.getBeginTime());\n        assertEquals(\"b\", accessLog.getQueries().get(\"a\"));\n        assertEquals(TestConst.FORWARDED_VALUE, accessLog.getClientIP());\n        assertTrue(accessLog.getBeginCpuTime() > 0);\n    }\n\n    @Test\n    public void after() throws InterruptedException {\n        EaseAgent.agentReport = MockReport.getAgentReport();\n        GatewayAccessLogInterceptor interceptor = new GatewayAccessLogInterceptor();\n        InterceptorTestUtils.init(interceptor, new AccessPlugin());\n        Context context = EaseAgent.getContext();\n        MockServerWebExchange mockServerWebExchange = TestServerWebExchangeUtils.mockServerWebExchange();\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{mockServerWebExchange}).build();\n        interceptor.before(methodInfo, context);\n        Long start = context.get(startTime);\n        assertNotNull(start);\n        interceptor.after(methodInfo, context);\n        assertNull(context.get(startTime));\n        assertTrue(methodInfo.getRetValue() instanceof AgentMono);\n        AgentMono agentMono = (AgentMono) methodInfo.getRetValue();\n        TagVerifier tagVerifier = new TagVerifier().add(\"type\", \"access-log\").add(\"system\", \"test-gateway-system\");\n        // LastJsonReporter lastJsonReporter = MockEaseAgent.lastMetricJsonReporter(tagVerifier::verifyAnd);\n        Thread thread = new Thread(() -> agentMono.getFinish().accept(agentMono.getMethodInfo(), agentMono.getAsyncContext()));\n        thread.start();\n        thread.join();\n        AccessLogInfo accessLog = MockEaseAgent.getLastLog();\n        verify(accessLog, start);\n    }\n\n    @Test\n    public void serverInfo() {\n        MockServerWebExchange exchange = TestServerWebExchangeUtils.mockServerWebExchange();\n        GatewayAccessLogInterceptor interceptor = new GatewayAccessLogInterceptor();\n        assertNotNull(interceptor.serverInfo(exchange));\n    }\n\n    @Test\n    public void getSystem() {\n        GatewayAccessLogInterceptor interceptor = new GatewayAccessLogInterceptor();\n        assertEquals(\"test-gateway-system\", interceptor.getSystem());\n    }\n\n    @Test\n    public void getServiceName() {\n        GatewayAccessLogInterceptor interceptor = new GatewayAccessLogInterceptor();\n        assertEquals(\"test-gateway-service\", interceptor.getServiceName());\n    }\n\n    @Test\n    public void getType() {\n        GatewayAccessLogInterceptor interceptor = new GatewayAccessLogInterceptor();\n        assertEquals(ConfigConst.PluginID.LOG, interceptor.getType());\n    }\n\n    @Test\n    public void order() {\n        GatewayAccessLogInterceptor interceptor = new GatewayAccessLogInterceptor();\n        assertEquals(Order.LOG.getOrder(), interceptor.order());\n    }\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/interceptor/log/SpringGatewayAccessLogInfoServerInfoTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring353.gateway.interceptor.log;\n\nimport easeagent.plugin.spring353.gateway.TestServerWebExchangeUtils;\nimport easeagent.plugin.spring353.gateway.interceptor.log.SpringGatewayAccessLogServerInfo;\nimport easeagent.plugin.spring353.gateway.interceptor.metric.MockRouteBuilder;\nimport org.junit.Test;\nimport org.springframework.cloud.gateway.support.ServerWebExchangeUtils;\nimport org.springframework.mock.http.server.reactive.MockServerHttpRequest;\nimport org.springframework.mock.web.server.MockServerWebExchange;\n\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.util.Map;\n\nimport static org.junit.Assert.assertEquals;\n\npublic class SpringGatewayAccessLogInfoServerInfoTest {\n\n    @Test\n    public void load() {\n        getMethod();\n    }\n\n    @Test\n    public void getMethod() {\n        SpringGatewayAccessLogServerInfo springGatewayAccessLogServerInfo = new SpringGatewayAccessLogServerInfo();\n        springGatewayAccessLogServerInfo.load(TestServerWebExchangeUtils.mockServerWebExchange());\n        assertEquals(\"GET\", springGatewayAccessLogServerInfo.getMethod());\n    }\n\n    @Test\n    public void getHeader() {\n        String key = \"testKey\";\n        String value = \"testValue\";\n        MockServerHttpRequest.BaseBuilder<?> baseBuilder = TestServerWebExchangeUtils.builder().header(key, value);\n        SpringGatewayAccessLogServerInfo springGatewayAccessLogServerInfo = new SpringGatewayAccessLogServerInfo();\n        springGatewayAccessLogServerInfo.load(TestServerWebExchangeUtils.build(baseBuilder));\n        assertEquals(value, springGatewayAccessLogServerInfo.getHeader(key));\n    }\n\n    @Test\n    public void getRemoteAddr() {\n        SpringGatewayAccessLogServerInfo springGatewayAccessLogServerInfo = new SpringGatewayAccessLogServerInfo();\n        springGatewayAccessLogServerInfo.load(TestServerWebExchangeUtils.mockServerWebExchange());\n        assertEquals(\"192.168.0.12\", springGatewayAccessLogServerInfo.getRemoteAddr());\n    }\n\n    @Test\n    public void getRequestURI() {\n        SpringGatewayAccessLogServerInfo springGatewayAccessLogServerInfo = new SpringGatewayAccessLogServerInfo();\n        springGatewayAccessLogServerInfo.load(TestServerWebExchangeUtils.mockServerWebExchange());\n        assertEquals(\"http://192.168.0.12:8080/test?a=b\", springGatewayAccessLogServerInfo.getRequestURI());\n    }\n\n    @Test\n    public void getResponseBufferSize() {\n        SpringGatewayAccessLogServerInfo springGatewayAccessLogServerInfo = new SpringGatewayAccessLogServerInfo();\n        assertEquals(0, springGatewayAccessLogServerInfo.getResponseBufferSize());\n    }\n\n\n    @Test\n    public void getMatchURL() throws URISyntaxException {\n        SpringGatewayAccessLogServerInfo springGatewayAccessLogServerInfo = new SpringGatewayAccessLogServerInfo();\n        MockServerWebExchange exchange = TestServerWebExchangeUtils.mockServerWebExchange();\n        springGatewayAccessLogServerInfo.load(exchange);\n        assertEquals(\"GET http://192.168.0.12:8080/test?a=b\", springGatewayAccessLogServerInfo.getMatchURL());\n        String url = \"http://192.168.0.12:8080/\";\n        exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR, new MockRouteBuilder().uri(new URI(url)).id(\"t\").build());\n        assertEquals(\"GET \" + url, springGatewayAccessLogServerInfo.getMatchURL());\n    }\n\n    @Test\n    public void findHeaders() {\n        String key = \"testKey\";\n        String value = \"testValue\";\n        MockServerHttpRequest.BaseBuilder<?> baseBuilder = TestServerWebExchangeUtils.builder().header(key, value);\n        SpringGatewayAccessLogServerInfo springGatewayAccessLogServerInfo = new SpringGatewayAccessLogServerInfo();\n        springGatewayAccessLogServerInfo.load(TestServerWebExchangeUtils.build(baseBuilder));\n        Map<String, String> headers = springGatewayAccessLogServerInfo.findHeaders();\n        assertEquals(value, headers.get(key));\n    }\n\n    @Test\n    public void findQueries() {\n        SpringGatewayAccessLogServerInfo springGatewayAccessLogServerInfo = new SpringGatewayAccessLogServerInfo();\n        springGatewayAccessLogServerInfo.load(TestServerWebExchangeUtils.mockServerWebExchange());\n        Map<String, String> queries = springGatewayAccessLogServerInfo.findQueries();\n        assertEquals(1, queries.size());\n        assertEquals(\"b\", queries.get(\"a\"));\n    }\n\n    @Test\n    public void getStatusCode() {\n        SpringGatewayAccessLogServerInfo springGatewayAccessLogServerInfo = new SpringGatewayAccessLogServerInfo();\n        springGatewayAccessLogServerInfo.load(TestServerWebExchangeUtils.mockServerWebExchange());\n        assertEquals(\"200\", springGatewayAccessLogServerInfo.getStatusCode());\n    }\n\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/interceptor/metric/GatewayMetricsInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring353.gateway.interceptor.metric;\n\nimport com.megaease.easeagent.mock.plugin.api.MockEaseAgent;\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.mock.plugin.api.utils.InterceptorTestUtils;\nimport com.megaease.easeagent.mock.plugin.api.utils.TagVerifier;\nimport com.megaease.easeagent.mock.report.impl.LastJsonReporter;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.api.metric.name.MetricField;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport easeagent.plugin.spring353.gateway.SpringGatewayPlugin;\nimport easeagent.plugin.spring353.gateway.TestServerWebExchangeUtils;\nimport easeagent.plugin.spring353.gateway.reactor.AgentMono;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.cloud.gateway.support.ServerWebExchangeUtils;\nimport org.springframework.http.server.reactive.ServerHttpRequest;\nimport org.springframework.mock.web.server.MockServerWebExchange;\nimport org.springframework.web.server.ServerWebExchange;\n\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.util.Map;\n\nimport static org.junit.Assert.*;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class GatewayMetricsInterceptorTest {\n    private Object startTime = AgentFieldReflectAccessor.getStaticFieldValue(GatewayMetricsInterceptor.class, \"START_TIME\");\n\n    @Test\n    public void init() {\n        GatewayMetricsInterceptor interceptor = new GatewayMetricsInterceptor();\n        InterceptorTestUtils.init(interceptor, new SpringGatewayPlugin());\n        assertNotNull(AgentFieldReflectAccessor.getStaticFieldValue(GatewayMetricsInterceptor.class, \"SERVER_METRIC\"));\n    }\n\n    @Test\n    public void before() {\n        GatewayMetricsInterceptor interceptor = new GatewayMetricsInterceptor();\n        Context context = EaseAgent.getContext();\n        interceptor.before(null, context);\n        assertNotNull(context.get(startTime));\n    }\n\n    public Map<String, Object> getMetric(LastJsonReporter lastJsonReporter) {\n        return lastJsonReporter.flushAndOnlyOne();\n    }\n\n    @Test\n    public void after() throws InterruptedException {\n        GatewayMetricsInterceptor interceptor = new GatewayMetricsInterceptor();\n        InterceptorTestUtils.init(interceptor, new SpringGatewayPlugin());\n        Context context = EaseAgent.getContext();\n        interceptor.before(null, context);\n        MockServerWebExchange mockServerWebExchange = TestServerWebExchangeUtils.mockServerWebExchange();\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{mockServerWebExchange}).build();\n        methodInfo.throwable(new RuntimeException(\"test error\"));\n        interceptor.after(methodInfo, context);\n        assertNull(methodInfo.getRetValue());\n\n        TagVerifier tagVerifier = new TagVerifier()\n            .add(\"category\", \"application\")\n            .add(\"type\", \"http-request\")\n            .add(\"url\", GatewayMetricsInterceptor.getKey(mockServerWebExchange));\n        LastJsonReporter lastJsonReporter = MockEaseAgent.lastMetricJsonReporter(tagVerifier::verifyAnd);\n        Map<String, Object> metric = getMetric(lastJsonReporter);\n        assertEquals(1, metric.get(MetricField.EXECUTION_COUNT.getField()));\n        assertEquals(1, metric.get(MetricField.EXECUTION_ERROR_COUNT.getField()));\n\n        methodInfo = MethodInfo.builder().args(new Object[]{mockServerWebExchange}).build();\n        interceptor.before(null, context);\n        interceptor.after(methodInfo, context);\n        assertTrue(methodInfo.getRetValue() instanceof AgentMono);\n        final AgentMono agentMono2 = (AgentMono) methodInfo.getRetValue();\n\n        Thread thread = new Thread(() -> agentMono2.getFinish().accept(agentMono2.getMethodInfo(), agentMono2.getAsyncContext()));\n        thread.start();\n        thread.join();\n\n        lastJsonReporter.clean();\n        metric = getMetric(lastJsonReporter);\n        assertEquals(2, metric.get(MetricField.EXECUTION_COUNT.getField()));\n        assertEquals(1, metric.get(MetricField.EXECUTION_ERROR_COUNT.getField()));\n\n    }\n\n    @Test\n    public void finishCallback() {\n    }\n\n    @Test\n    public void getKey() throws URISyntaxException {\n        ServerWebExchange webExchange = mock(ServerWebExchange.class);\n        when(webExchange.getRequest()).thenReturn(mock(ServerHttpRequest.class));\n        assertEquals(\"\", GatewayMetricsInterceptor.getKey(webExchange));\n\n        MockServerWebExchange exchange = TestServerWebExchangeUtils.mockServerWebExchange();\n        assertEquals(\"GET http://192.168.0.12:8080/test?a=b\", GatewayMetricsInterceptor.getKey(exchange));\n\n        String url = \"http://loca:8080/test\";\n        exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR, new MockRouteBuilder().uri(new URI(url)).id(\"t\").build());\n        assertEquals(\"GET \" + url, GatewayMetricsInterceptor.getKey(exchange));\n    }\n\n    @Test\n    public void getType() {\n        GatewayMetricsInterceptor interceptor = new GatewayMetricsInterceptor();\n        assertEquals(ConfigConst.PluginID.METRIC, interceptor.getType());\n    }\n\n    @Test\n    public void order() {\n        GatewayMetricsInterceptor interceptor = new GatewayMetricsInterceptor();\n        assertEquals(Order.METRIC.getOrder(), interceptor.order());\n    }\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/interceptor/metric/MockRouteBuilder.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring353.gateway.interceptor.metric;\n\nimport org.springframework.cloud.gateway.route.Route;\n\npublic class MockRouteBuilder  extends Route.Builder  {\n    public MockRouteBuilder() {\n        predicate = serverWebExchange -> true;\n    }\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/interceptor/tracing/FluxHttpServerRequestTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring353.gateway.interceptor.tracing;\n\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport easeagent.plugin.spring353.gateway.TestServerWebExchangeUtils;\nimport org.junit.Test;\n\nimport java.net.InetSocketAddress;\n\nimport static org.junit.Assert.*;\n\npublic class FluxHttpServerRequestTest {\n\n    @Test\n    public void kind() {\n        FluxHttpServerRequest fluxHttpServerRequest = new FluxHttpServerRequest(TestServerWebExchangeUtils.builder().build());\n        assertEquals(Span.Kind.SERVER, fluxHttpServerRequest.kind());\n    }\n\n    @Test\n    public void header() {\n        String key = \"testKey\";\n        String value = \"testValue\";\n        FluxHttpServerRequest fluxHttpServerRequest = new FluxHttpServerRequest(TestServerWebExchangeUtils.builder()\n            .header(key, value).build());\n        assertEquals(value, fluxHttpServerRequest.header(key));\n    }\n\n    @Test\n    public void cacheScope() {\n        FluxHttpServerRequest fluxHttpServerRequest = new FluxHttpServerRequest(TestServerWebExchangeUtils.builder().build());\n        assertFalse(fluxHttpServerRequest.cacheScope());\n    }\n\n    @Test\n    public void setHeader() {\n        String key = \"testKey\";\n        String value = \"testValue\";\n        FluxHttpServerRequest fluxHttpServerRequest = new FluxHttpServerRequest(TestServerWebExchangeUtils.builder().build());\n        fluxHttpServerRequest.setHeader(key, value);\n        assertNull(fluxHttpServerRequest.header(key));\n    }\n\n    @Test\n    public void method() {\n        FluxHttpServerRequest fluxHttpServerRequest = new FluxHttpServerRequest(TestServerWebExchangeUtils.builder().build());\n        assertEquals(\"GET\", fluxHttpServerRequest.method());\n    }\n\n    @Test\n    public void path() {\n        FluxHttpServerRequest fluxHttpServerRequest = new FluxHttpServerRequest(TestServerWebExchangeUtils.builder().build());\n        assertEquals(\"/test\", fluxHttpServerRequest.path());\n    }\n\n    @Test\n    public void route() {\n        FluxHttpServerRequest fluxHttpServerRequest = new FluxHttpServerRequest(TestServerWebExchangeUtils.builder().build());\n        assertEquals(null, fluxHttpServerRequest.route());\n    }\n\n    @Test\n    public void getRemoteAddr() {\n\n        FluxHttpServerRequest fluxHttpServerRequest = new FluxHttpServerRequest(TestServerWebExchangeUtils.builder()\n            .remoteAddress(new InetSocketAddress(\"192.168.0.12\", 8080)).build());\n        assertEquals(\"192.168.0.12\", fluxHttpServerRequest.getRemoteAddr());\n    }\n\n    @Test\n    public void getRemotePort() {\n        FluxHttpServerRequest fluxHttpServerRequest = new FluxHttpServerRequest(TestServerWebExchangeUtils.builder()\n            .remoteAddress(new InetSocketAddress(\"192.168.0.12\", 8080)).build());\n        assertEquals(8080, fluxHttpServerRequest.getRemotePort());\n\n    }\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/interceptor/tracing/FluxHttpServerResponseTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring353.gateway.interceptor.tracing;\n\nimport easeagent.plugin.spring353.gateway.TestServerWebExchangeUtils;\nimport org.junit.Test;\nimport org.springframework.mock.web.server.MockServerWebExchange;\n\nimport static org.junit.Assert.*;\n\npublic class FluxHttpServerResponseTest {\n\n\n    @Test\n    public void header() {\n        MockServerWebExchange mockServerWebExchange = TestServerWebExchangeUtils.mockServerWebExchange();\n        String key = \"testKey\";\n        String value = \"testValue\";\n        mockServerWebExchange.getResponse().getHeaders().add(key, value);\n        FluxHttpServerResponse fluxHttpServerResponse = new FluxHttpServerResponse(mockServerWebExchange, null);\n        assertEquals(value, fluxHttpServerResponse.header(key));\n\n    }\n\n    @Test\n    public void method() {\n        MockServerWebExchange mockServerWebExchange = TestServerWebExchangeUtils.mockServerWebExchange();\n        FluxHttpServerResponse fluxHttpServerResponse = new FluxHttpServerResponse(mockServerWebExchange, null);\n        assertEquals(\"GET\", fluxHttpServerResponse.method());\n    }\n\n    @Test\n    public void route() {\n        MockServerWebExchange mockServerWebExchange = TestServerWebExchangeUtils.mockServerWebExchange();\n        FluxHttpServerResponse fluxHttpServerResponse = new FluxHttpServerResponse(mockServerWebExchange, null);\n        assertEquals(\"/test\", fluxHttpServerResponse.route());\n    }\n\n    @Test\n    public void statusCode() {\n        MockServerWebExchange mockServerWebExchange = TestServerWebExchangeUtils.mockServerWebExchange();\n        FluxHttpServerResponse fluxHttpServerResponse = new FluxHttpServerResponse(mockServerWebExchange, null);\n        assertEquals(200, fluxHttpServerResponse.statusCode());\n\n    }\n\n    @Test\n    public void maybeError() {\n        MockServerWebExchange mockServerWebExchange = TestServerWebExchangeUtils.mockServerWebExchange();\n        FluxHttpServerResponse fluxHttpServerResponse = new FluxHttpServerResponse(mockServerWebExchange, null);\n        assertNull(fluxHttpServerResponse.maybeError());\n        RuntimeException err = new RuntimeException();\n        fluxHttpServerResponse = new FluxHttpServerResponse(mockServerWebExchange, err);\n        assertSame(err, fluxHttpServerResponse.maybeError());\n    }\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/interceptor/tracing/GatewayServerTracingInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring353.gateway.interceptor.tracing;\n\nimport com.megaease.easeagent.mock.plugin.api.MockEaseAgent;\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.mock.plugin.api.utils.SpanTestUtils;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.api.context.RequestContext;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport easeagent.plugin.spring353.gateway.TestServerWebExchangeUtils;\nimport easeagent.plugin.spring353.gateway.GatewayCons;\nimport easeagent.plugin.spring353.gateway.reactor.AgentMono;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.mock.http.server.reactive.MockServerHttpRequest;\nimport org.springframework.mock.web.server.MockServerWebExchange;\nimport org.springframework.web.server.ServerWebExchange;\n\nimport java.util.Map;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.function.BiConsumer;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class GatewayServerTracingInterceptorTest {\n\n    @Test\n    public void before() {\n        GatewayServerTracingInterceptor interceptor = new GatewayServerTracingInterceptor();\n        Context context = EaseAgent.getContext();\n        MockServerWebExchange mockServerWebExchange = TestServerWebExchangeUtils.mockServerWebExchange();\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{mockServerWebExchange}).build();\n        interceptor.before(methodInfo, context);\n        assertNotNull(context.get(GatewayServerTracingInterceptor.SPAN_CONTEXT_KEY));\n        assertNotNull(context.get(FluxHttpServerRequest.class));\n        RequestContext requestContext = context.remove(GatewayServerTracingInterceptor.SPAN_CONTEXT_KEY);\n        assertTrue(context.currentTracing().hasCurrentSpan());\n        requestContext.scope().close();\n        assertFalse(context.currentTracing().hasCurrentSpan());\n        context.remove(FluxHttpServerRequest.class);\n        requestContext.span().abandon();\n\n\n        MockServerHttpRequest.BaseBuilder<?> baseBuilder = TestServerWebExchangeUtils.builder();\n        for (Map.Entry<String, String> entry : requestContext.getHeaders().entrySet()) {\n            baseBuilder.header(entry.getKey(), entry.getValue());\n        }\n        mockServerWebExchange = TestServerWebExchangeUtils.build(baseBuilder);\n        methodInfo = MethodInfo.builder().args(new Object[]{mockServerWebExchange}).build();\n        interceptor.before(methodInfo, context);\n        RequestContext requestContext2 = context.remove(GatewayServerTracingInterceptor.SPAN_CONTEXT_KEY);\n        assertTrue(context.currentTracing().hasCurrentSpan());\n        requestContext2.scope().close();\n        assertFalse(context.currentTracing().hasCurrentSpan());\n        context.remove(FluxHttpServerRequest.class);\n        requestContext2.span().finish();\n        ReportSpan mockSpan = MockEaseAgent.getLastSpan();\n        SpanTestUtils.sameId(requestContext.span(), mockSpan);\n    }\n\n    @Test\n    public void after() {\n        GatewayServerTracingInterceptor interceptor = new GatewayServerTracingInterceptor();\n        Context context = EaseAgent.getContext();\n        MockServerWebExchange mockServerWebExchange = TestServerWebExchangeUtils.mockServerWebExchange();\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{mockServerWebExchange}).build();\n        interceptor.before(methodInfo, context);\n        String errorInfo = \"test error\";\n        methodInfo.throwable(new RuntimeException(errorInfo));\n        interceptor.after(methodInfo, context);\n        ReportSpan reportSpan = MockEaseAgent.getLastSpan();\n        assertNotNull(reportSpan);\n        assertTrue(reportSpan.hasError());\n        assertEquals(errorInfo, reportSpan.errorInfo());\n        assertNull(context.get(GatewayServerTracingInterceptor.SPAN_CONTEXT_KEY));\n        assertNull(context.get(FluxHttpServerRequest.class));\n        assertNull(methodInfo.getRetValue());\n\n\n        mockServerWebExchange = TestServerWebExchangeUtils.mockServerWebExchange();\n        methodInfo = MethodInfo.builder().args(new Object[]{mockServerWebExchange}).build();\n        interceptor.before(methodInfo, context);\n        RequestContext requestContext = context.get(GatewayServerTracingInterceptor.SPAN_CONTEXT_KEY);\n        interceptor.after(methodInfo, context);\n        assertNull(context.get(GatewayServerTracingInterceptor.SPAN_CONTEXT_KEY));\n        assertNull(context.get(FluxHttpServerRequest.class));\n        assertNotNull(methodInfo.getRetValue());\n        assertTrue(methodInfo.getRetValue() instanceof AgentMono);\n\n        assertFalse(context.currentTracing().hasCurrentSpan());\n        requestContext.span().abandon();\n    }\n\n\n    @Test\n    public void finishCallback() throws InterruptedException {\n        GatewayServerTracingInterceptor interceptor = new GatewayServerTracingInterceptor();\n        Context context = EaseAgent.getContext();\n        MockServerWebExchange mockServerWebExchange = TestServerWebExchangeUtils.mockServerWebExchange();\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{mockServerWebExchange}).build();\n        interceptor.before(methodInfo, context);\n        RequestContext requestContext = context.get(GatewayServerTracingInterceptor.SPAN_CONTEXT_KEY);\n        interceptor.after(methodInfo, context);\n        assertNotNull(methodInfo.getRetValue());\n        assertTrue(methodInfo.getRetValue() instanceof AgentMono);\n        assertNull(MockEaseAgent.getLastSpan());\n\n        AtomicBoolean atomicBoolean = new AtomicBoolean(false);\n        BiConsumer<ServerWebExchange, MethodInfo> consumer = (methodInfo1, exchange) -> atomicBoolean.set(true);\n        mockServerWebExchange.getAttributes().put(GatewayCons.CLIENT_RECEIVE_CALLBACK_KEY, consumer);\n        AgentMono agentMono = (AgentMono) methodInfo.getRetValue();\n        Thread thread = new Thread(() -> agentMono.getFinish().accept(agentMono.getMethodInfo(), agentMono.getAsyncContext()));\n        thread.start();\n        thread.join();\n        assertTrue(atomicBoolean.get());\n        ReportSpan reportSpan = MockEaseAgent.getLastSpan();\n        assertTrue(reportSpan.name().contains(\"/test\"));\n        SpanTestUtils.sameId(requestContext.span(), reportSpan);\n    }\n\n    public static RequestContext beforeGatewayServerTracing(MockServerWebExchange mockServerWebExchange) {\n        GatewayServerTracingInterceptor interceptor = new GatewayServerTracingInterceptor();\n        Context context = EaseAgent.getContext();\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{mockServerWebExchange}).build();\n        interceptor.before(methodInfo, context);\n        assertNotNull(context.get(GatewayServerTracingInterceptor.SPAN_CONTEXT_KEY));\n        return context.get(GatewayServerTracingInterceptor.SPAN_CONTEXT_KEY);\n    }\n\n\n    @Test\n    public void getType() {\n        GatewayServerTracingInterceptor interceptor = new GatewayServerTracingInterceptor();\n        assertEquals(ConfigConst.PluginID.TRACING, interceptor.getType());\n    }\n\n    @Test\n    public void order() {\n        GatewayServerTracingInterceptor interceptor = new GatewayServerTracingInterceptor();\n        assertEquals(Order.TRACING.getOrder(), interceptor.order());\n    }\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/interceptor/tracing/HttpHeadersFilterTracingInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring353.gateway.interceptor.tracing;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.context.RequestContext;\nimport com.megaease.easeagent.plugin.api.trace.Scope;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport easeagent.plugin.spring353.gateway.TestServerWebExchangeUtils;\nimport easeagent.plugin.spring353.gateway.GatewayCons;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.mock.web.server.MockServerWebExchange;\n\nimport java.util.Collection;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class HttpHeadersFilterTracingInterceptorTest {\n\n    @Test\n    public void doAfter() {\n        HttpHeadersFilterTracingInterceptor interceptor = new HttpHeadersFilterTracingInterceptor();\n\n        MockServerWebExchange mockServerWebExchange = TestServerWebExchangeUtils.mockServerWebExchange();\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{null, mockServerWebExchange}).retValue(new HttpHeaders()).build();\n        interceptor.doAfter(methodInfo, EaseAgent.getContext());\n        assertNull(mockServerWebExchange.getAttribute(GatewayCons.CHILD_SPAN_KEY));\n\n\n\n        mockServerWebExchange = TestServerWebExchangeUtils.mockServerWebExchange();\n        methodInfo = MethodInfo.builder().args(new Object[]{null, mockServerWebExchange}).retValue(new HttpHeaders()).build();\n\n        RequestContext requestContext = GatewayServerTracingInterceptorTest.beforeGatewayServerTracing(mockServerWebExchange);\n        Span span = requestContext.span();\n        try (Scope ignored = requestContext.scope()) {\n            interceptor.doAfter(methodInfo, EaseAgent.getContext());\n            RequestContext clientContext = mockServerWebExchange.getAttribute(GatewayCons.CHILD_SPAN_KEY);\n            HttpHeaders ret = (HttpHeaders) methodInfo.getRetValue();\n            Collection<String> headers = ret.toSingleValueMap().values();\n            assertTrue(headers.contains(clientContext.span().traceIdString()));\n            assertTrue(headers.contains(clientContext.span().spanIdString()));\n            assertTrue(headers.contains(clientContext.span().parentIdString()));\n            assertEquals(span.traceIdString(), clientContext.span().traceIdString());\n            assertEquals(span.spanIdString(), clientContext.span().parentIdString());\n            clientContext.scope().close();\n            clientContext.span().abandon();\n        }\n        span.abandon();\n    }\n\n    @Test\n    public void testHeaderFilterRequest() {\n        HttpHeadersFilterTracingInterceptor.HeaderFilterRequest headerFilterRequest = new HttpHeadersFilterTracingInterceptor.HeaderFilterRequest(null);\n        assertEquals(Span.Kind.CLIENT, headerFilterRequest.kind());\n    }\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/reactor/AgentCoreSubscriberTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring353.gateway.reactor;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.context.AsyncContext;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.reactivestreams.Subscription;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class AgentCoreSubscriberTest {\n\n    @Test\n    public void currentContext() {\n        MockCoreSubscriber mockCoreSubscriber = new MockCoreSubscriber();\n        AgentCoreSubscriber agentCoreSubscriber = new AgentCoreSubscriber(mockCoreSubscriber, null, null, null);\n        agentCoreSubscriber.currentContext();\n        assertTrue(mockCoreSubscriber.currentContext.get());\n    }\n\n    @Test\n    public void onSubscribe() {\n        MockCoreSubscriber mockCoreSubscriber = new MockCoreSubscriber();\n        AgentCoreSubscriber agentCoreSubscriber = new AgentCoreSubscriber(mockCoreSubscriber, null, null, null);\n        agentCoreSubscriber.onSubscribe(new Subscription() {\n            @Override\n            public void request(long l) {\n\n            }\n\n            @Override\n            public void cancel() {\n\n            }\n        });\n        assertTrue(mockCoreSubscriber.onSubscribe.get());\n\n    }\n\n    @Test\n    public void onNext() {\n        MockCoreSubscriber mockCoreSubscriber = new MockCoreSubscriber();\n        AgentCoreSubscriber agentCoreSubscriber = new AgentCoreSubscriber(mockCoreSubscriber, null, null, null);\n        agentCoreSubscriber.onNext(null);\n        assertTrue(mockCoreSubscriber.onNext.get());\n    }\n\n    @Test\n    public void onError() {\n        MockCoreSubscriber mockCoreSubscriber = new MockCoreSubscriber();\n        MethodInfo errorMethodInfo = MethodInfo.builder().build();\n        AsyncContext errorAsyncContext = EaseAgent.getContext().exportAsync();\n        RuntimeException runtimeException = new RuntimeException();\n        AgentCoreSubscriber agentCoreSubscriber = new AgentCoreSubscriber(mockCoreSubscriber, errorMethodInfo, errorAsyncContext, (methodInfo, asyncContext) -> {\n            assertNotNull(methodInfo);\n            assertNotNull(asyncContext);\n            assertSame(errorMethodInfo, methodInfo);\n            assertSame(errorAsyncContext, asyncContext);\n            assertFalse(methodInfo.isSuccess());\n            assertSame(runtimeException, methodInfo.getThrowable());\n        });\n        agentCoreSubscriber.onError(runtimeException);\n        assertTrue(mockCoreSubscriber.onError.get());\n    }\n\n    @Test\n    public void onComplete() {\n        MockCoreSubscriber mockCoreSubscriber = new MockCoreSubscriber();\n        MethodInfo completeMethodInfo = MethodInfo.builder().build();\n        AsyncContext completeAsyncContext = EaseAgent.getContext().exportAsync();\n        AgentCoreSubscriber agentCoreSubscriber = new AgentCoreSubscriber(mockCoreSubscriber, completeMethodInfo, completeAsyncContext, (methodInfo, asyncContext) -> {\n            assertNotNull(methodInfo);\n            assertNotNull(asyncContext);\n            assertSame(completeMethodInfo, methodInfo);\n            assertSame(completeAsyncContext, asyncContext);\n            assertTrue(methodInfo.isSuccess());\n        });\n        agentCoreSubscriber.onComplete();\n        assertTrue(mockCoreSubscriber.onComplete.get());\n\n    }\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/reactor/AgentMonoTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring353.gateway.reactor;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Cleaner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.AsyncContext;\nimport com.megaease.easeagent.plugin.api.trace.Scope;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport reactor.core.CoreSubscriber;\nimport reactor.core.publisher.Mono;\n\nimport java.util.concurrent.atomic.AtomicBoolean;\n\nimport static org.junit.Assert.assertFalse;\nimport static org.junit.Assert.assertTrue;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class AgentMonoTest {\n\n    @Test\n    public void subscribe() {\n        AtomicBoolean ran = new AtomicBoolean(false);\n        Mono<Void> mono = new Mono<Void>() {\n            @Override\n            public void subscribe(CoreSubscriber<? super Void> coreSubscriber) {\n                ran.set(true);\n            }\n        };\n\n        MethodInfo methodInfo = MethodInfo.builder().build();\n        AgentMono agentMono = new AgentMono(mono, methodInfo, EaseAgent.getContext().exportAsync(), null);\n        agentMono.subscribe(new MockCoreSubscriber());\n        assertTrue(ran.get());\n    }\n\n    @Test\n    public void testImportToCurrent() throws InterruptedException {\n        Context context = EaseAgent.getContext();\n        Span span = context.nextSpan();\n        Thread thread;\n        try (Scope ignored4 = span.maybeScope()) {\n            AsyncContext asyncContext1 = context.exportAsync();\n            AsyncContext asyncContext2 = context.exportAsync();\n            AsyncContext asyncContext3 = context.exportAsync();\n            thread = new Thread(() -> {\n                Context asyncContext = EaseAgent.getContext();\n                assertFalse(asyncContext.currentTracing().hasCurrentSpan());\n                try (Cleaner ignored = asyncContext1.importToCurrent()) {\n                    assertTrue(asyncContext.currentTracing().hasCurrentSpan());\n                    try (Cleaner ignored1 = asyncContext2.importToCurrent()) {\n                        assertTrue(asyncContext.currentTracing().hasCurrentSpan());\n                        try (Cleaner ignored2 = asyncContext3.importToCurrent()) {\n                            assertTrue(asyncContext.currentTracing().hasCurrentSpan());\n                        }\n                        assertTrue(asyncContext.currentTracing().hasCurrentSpan());\n                    }\n                    assertTrue(asyncContext.currentTracing().hasCurrentSpan());\n                }\n                assertFalse(asyncContext.currentTracing().hasCurrentSpan());\n            });\n        }\n        thread.start();\n        thread.join();\n        span.finish();\n    }\n\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/reactor/MockCoreSubscriber.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring353.gateway.reactor;\n\nimport org.reactivestreams.Subscription;\nimport reactor.core.CoreSubscriber;\nimport reactor.util.context.Context;\n\nimport java.util.Collections;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\npublic class MockCoreSubscriber implements CoreSubscriber<Void> {\n    AtomicBoolean currentContext = new AtomicBoolean(false);\n    AtomicBoolean onSubscribe = new AtomicBoolean(false);\n    AtomicBoolean onNext = new AtomicBoolean(false);\n    AtomicBoolean onError = new AtomicBoolean(false);\n    AtomicBoolean onComplete = new AtomicBoolean(false);\n\n\n    @Override\n    public Context currentContext() {\n        currentContext.set(true);\n        return Context.of(Collections.emptyMap());\n    }\n\n    @Override\n    public void onSubscribe(Subscription subscription) {\n        onSubscribe.set(true);\n    }\n\n    @Override\n    public void onNext(Void aVoid) {\n        onNext.set(true);\n    }\n\n    @Override\n    public void onError(Throwable throwable) {\n        onError.set(true);\n    }\n\n    @Override\n    public void onComplete() {\n        onComplete.set(true);\n    }\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/resources/mock_agent.properties",
    "content": "name=test-gateway-service\nsystem=test-gateway-system\nplugin.observability.springGateway.metric.interval=200\nplugin.observability.springGateway.metric.intervalUnit=MILLISECONDS\neaseagent.progress.forwarded.headers=X-Forwarded-For\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-rest-template-3.5.3/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright (c) 2021, MegaEase\n  ~ All rights reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~   http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>spring-boot-3.5.3</artifactId>\n        <groupId>com.megaease.easeagent</groupId>\n        <version>2.3.0</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>spring-boot-rest-template-3.5.3</artifactId>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>plugin-api</artifactId>\n            <scope>provided</scope>\n        </dependency>\n\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-web</artifactId>\n            <scope>provided</scope>\n            <version>3.5.3</version>\n            <exclusions>\n                <exclusion>\n                    <groupId>org.springframework.boot</groupId>\n                    <artifactId>spring-boot-starter-logging</artifactId>\n                </exclusion>\n            </exclusions>\n        </dependency>\n\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>plugin-api-mock</artifactId>\n            <version>${project.version}</version>\n            <scope>test</scope>\n        </dependency>\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-rest-template-3.5.3/src/main/java/com/megaease/easeagent/plugin/rest/template/ForwardedPlugin.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.rest.template;\n\nimport com.megaease.easeagent.plugin.AgentPlugin;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\n\npublic class ForwardedPlugin implements AgentPlugin {\n    @Override\n    public String getNamespace() {\n        return ConfigConst.Namespace.FORWARDED;\n    }\n\n    @Override\n    public String getDomain() {\n        return ConfigConst.INTEGRABILITY;\n    }\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-rest-template-3.5.3/src/main/java/com/megaease/easeagent/plugin/rest/template/RestTemplatePlugin.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.rest.template;\n\nimport com.megaease.easeagent.plugin.AgentPlugin;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\n\npublic class RestTemplatePlugin implements AgentPlugin {\n    @Override\n    public String getNamespace() {\n        return ConfigConst.Namespace.REST_TEMPLATE;\n    }\n\n    @Override\n    public String getDomain() {\n        return ConfigConst.OBSERVABILITY;\n    }\n\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-rest-template-3.5.3/src/main/java/com/megaease/easeagent/plugin/rest/template/advice/ClientHttpRequestAdvice.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.rest.template.advice;\n\nimport com.megaease.easeagent.plugin.CodeVersion;\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.Set;\n\npublic class ClientHttpRequestAdvice implements Points {\n\n    private final static CodeVersion VERSIONS = CodeVersion.builder()\n        .key(ConfigConst.CodeVersion.KEY_SPRING_BOOT)\n        .add(ConfigConst.CodeVersion.VERSION_SPRING_BOOT3).build();\n\n    @Override\n    public CodeVersion codeVersions() {\n        return VERSIONS;\n    }\n\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return ClassMatcher.builder().hasInterface(\"org.springframework.http.client.ClientHttpRequest\").notInterface()\n            .build();\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.multiBuilder()\n            .match(MethodMatcher.builder().named(\"execute\")\n                .returnType(\"org.springframework.http.client.ClientHttpResponse\")\n                .qualifier(\"default\")\n                .build())\n            .build();\n    }\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-rest-template-3.5.3/src/main/java/com/megaease/easeagent/plugin/rest/template/interceptor/forwarded/RestTemplateForwardedInterceptor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.rest.template.interceptor.forwarded;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.rest.template.ForwardedPlugin;\nimport com.megaease.easeagent.plugin.rest.template.advice.ClientHttpRequestAdvice;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.http.client.ClientHttpRequest;\n\n@AdviceTo(value = ClientHttpRequestAdvice.class, plugin = ForwardedPlugin.class)\npublic class RestTemplateForwardedInterceptor implements Interceptor {\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n        ClientHttpRequest clientHttpRequest = (ClientHttpRequest) methodInfo.getInvoker();\n        HttpHeaders httpHeaders = clientHttpRequest.getHeaders();\n        context.injectForwardedHeaders(httpHeaders::add);\n    }\n\n\n    @Override\n    public String getType() {\n        return ConfigConst.PluginID.FORWARDED;\n    }\n\n    @Override\n    public int order() {\n        return Order.FORWARDED.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-rest-template-3.5.3/src/main/java/com/megaease/easeagent/plugin/rest/template/interceptor/tracing/ClientHttpRequestInterceptor.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.rest.template.interceptor.tracing;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.rest.template.RestTemplatePlugin;\nimport com.megaease.easeagent.plugin.rest.template.advice.ClientHttpRequestAdvice;\nimport com.megaease.easeagent.plugin.tools.trace.BaseHttpClientTracingInterceptor;\nimport com.megaease.easeagent.plugin.tools.trace.HttpRequest;\nimport com.megaease.easeagent.plugin.tools.trace.HttpResponse;\nimport lombok.SneakyThrows;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.http.client.ClientHttpRequest;\nimport org.springframework.http.client.ClientHttpResponse;\n\nimport java.util.List;\n\n@AdviceTo(value = ClientHttpRequestAdvice.class, plugin = RestTemplatePlugin.class)\npublic class ClientHttpRequestInterceptor extends BaseHttpClientTracingInterceptor {\n    private static final Object PROGRESS_CONTEXT = new Object();\n\n    @Override\n    public Object getProgressKey() {\n        return PROGRESS_CONTEXT;\n    }\n\n    @Override\n    protected HttpRequest getRequest(MethodInfo methodInfo, Context context) {\n        ClientHttpRequest clientHttpRequest = (ClientHttpRequest) methodInfo.getInvoker();\n        return new ClientRequestWrapper(clientHttpRequest);\n    }\n\n    @Override\n    protected HttpResponse getResponse(MethodInfo methodInfo, Context context) {\n        ClientHttpRequest clientHttpRequest = (ClientHttpRequest) methodInfo.getInvoker();\n        ClientHttpResponse response = (ClientHttpResponse) methodInfo.getRetValue();\n        return new ClientResponseWrapper(methodInfo.getThrowable(), clientHttpRequest, response);\n    }\n\n    private static String getFirstHeaderValue(HttpHeaders headers, String name) {\n        List<String> values = headers.get(name);\n        if (values == null || values.isEmpty()) {\n            return null;\n        }\n        return values.get(0);\n    }\n\n    static class ClientRequestWrapper implements HttpRequest {\n\n        private final ClientHttpRequest request;\n\n        public ClientRequestWrapper(ClientHttpRequest request) {\n            this.request = request;\n        }\n\n        @Override\n        public Span.Kind kind() {\n            return Span.Kind.CLIENT;\n        }\n\n\n        @Override\n        public String method() {\n            return request.getMethod().name();\n        }\n\n        @Override\n        public String path() {\n            return request.getURI().getPath();\n        }\n\n        @Override\n        public String route() {\n            return null;\n        }\n\n        @Override\n        public String getRemoteAddr() {\n            return request.getURI().getHost();\n        }\n\n        @Override\n        public int getRemotePort() {\n            return request.getURI().getPort();\n        }\n\n        @Override\n        public String header(String name) {\n            return getFirstHeaderValue(request.getHeaders(), name);\n        }\n\n\n        @Override\n        public boolean cacheScope() {\n            return false;\n        }\n\n        @Override\n        public void setHeader(String name, String value) {\n            request.getHeaders().add(name, value);\n        }\n    }\n\n    static class ClientResponseWrapper implements HttpResponse {\n        private final Throwable caught;\n        private final ClientHttpRequest request;\n        private final ClientHttpResponse response;\n\n        public ClientResponseWrapper(Throwable caught, ClientHttpRequest request, ClientHttpResponse response) {\n            this.caught = caught;\n            this.request = request;\n            this.response = response;\n        }\n\n        @Override\n        public String method() {\n            return request.getMethod().name();\n        }\n\n        @Override\n        public String route() {\n            return null;\n        }\n\n        @SneakyThrows\n        @Override\n        public int statusCode() {\n            if (response == null) {\n                return 500;\n            }\n            return response.getStatusCode().value();\n        }\n\n        @Override\n        public Throwable maybeError() {\n            return caught;\n        }\n\n        @Override\n        public String header(String name) {\n            if (response == null) {\n                return \"\";\n            }\n            return getFirstHeaderValue(response.getHeaders(), name);\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-rest-template-3.5.3/src/test/java/com/megaease/easeagent/plugin/rest/template/interceptor/TestConst.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.rest.template.interceptor;\n\npublic class TestConst {\n    public static final String FORWARDED_NAME = \"X-Forwarded-For\";\n    public static final String FORWARDED_VALUE = \"testForwarded\";\n    public static final String RESPONSE_TAG_NAME = \"X-EG-Test\";\n    public static final String RESPONSE_TAG_VALUE = \"X-EG-Test-Value\";\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-rest-template-3.5.3/src/test/java/com/megaease/easeagent/plugin/rest/template/interceptor/forwarded/RestTemplateForwardedInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.rest.template.interceptor.forwarded;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.rest.template.interceptor.TestConst;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.http.HttpMethod;\nimport org.springframework.http.client.ClientHttpRequest;\nimport org.springframework.http.client.ClientHttpRequestFactory;\nimport org.springframework.http.client.SimpleClientHttpRequestFactory;\n\nimport java.io.IOException;\nimport java.net.URI;\nimport java.net.URISyntaxException;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class RestTemplateForwardedInterceptorTest {\n\n    @Test\n    public void before() throws URISyntaxException, IOException {\n        ClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();\n        ClientHttpRequest request = requestFactory.createRequest(new URI(\"http://127.0.0.1:8080/test\"), HttpMethod.GET);\n        RestTemplateForwardedInterceptor restTemplateForwardedInterceptor = new RestTemplateForwardedInterceptor();\n\n        MethodInfo methodInfo = MethodInfo.builder().invoker(request).build();\n        Context context = EaseAgent.getContext();\n        restTemplateForwardedInterceptor.before(methodInfo, context);\n        assertNull(request.getHeaders().getFirst(TestConst.FORWARDED_NAME));\n        context.put(TestConst.FORWARDED_NAME, TestConst.FORWARDED_VALUE);\n        try {\n            restTemplateForwardedInterceptor.before(methodInfo, context);\n            assertNotNull(request.getHeaders().get(TestConst.FORWARDED_NAME));\n            assertEquals(TestConst.FORWARDED_VALUE, request.getHeaders().getFirst(TestConst.FORWARDED_NAME));\n        } finally {\n            context.remove(TestConst.FORWARDED_NAME);\n        }\n\n\n    }\n\n    @Test\n    public void getType() {\n        RestTemplateForwardedInterceptor restTemplateForwardedInterceptor = new RestTemplateForwardedInterceptor();\n        assertEquals(ConfigConst.PluginID.FORWARDED, restTemplateForwardedInterceptor.getType());\n\n    }\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-rest-template-3.5.3/src/test/java/com/megaease/easeagent/plugin/rest/template/interceptor/tracing/ClientHttpRequestInterceptorTest.java",
    "content": "package com.megaease.easeagent.plugin.rest.template.interceptor.tracing;\n\nimport com.megaease.easeagent.mock.plugin.api.MockEaseAgent;\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.trace.Scope;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport com.megaease.easeagent.plugin.rest.template.interceptor.TestConst;\nimport com.megaease.easeagent.plugin.tools.trace.HttpRequest;\nimport com.megaease.easeagent.plugin.tools.trace.HttpResponse;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.http.HttpMethod;\nimport org.springframework.http.client.*;\n\nimport java.io.IOException;\nimport java.net.URI;\nimport java.net.URISyntaxException;\n\nimport static org.junit.Assert.*;\n\n\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class ClientHttpRequestInterceptorTest {\n\n\n    @Test\n    public void testRestTemplate() throws URISyntaxException, IOException {\n        String url = \"http://127.0.0.1:8080/test\";\n\n        ClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();\n        ClientHttpRequest request = requestFactory.createRequest(new URI(url), HttpMethod.GET);\n        ClientHttpResponse clientHttpResponse = SimpleClientHttpResponseFactory.createMockResponse(url);\n        MethodInfo.MethodInfoBuilder methodInfoBuilder = MethodInfo.builder();\n        MethodInfo methodInfo = methodInfoBuilder.invoker(request).build();\n        Context context = EaseAgent.getContext();\n        ClientHttpRequestInterceptor clientHttpRequestInterceptor = new ClientHttpRequestInterceptor();\n        MockEaseAgent.cleanLastSpan();\n\n        clientHttpRequestInterceptor.before(methodInfo, context);\n        methodInfo = methodInfoBuilder.retValue(clientHttpResponse).build();\n        clientHttpRequestInterceptor.after(methodInfo, context);\n\n        ReportSpan mockSpan = MockEaseAgent.getLastSpan();\n        assertNotNull(mockSpan);\n        assertEquals(Span.Kind.CLIENT.name(), mockSpan.kind());\n        assertEquals(TestConst.RESPONSE_TAG_VALUE, mockSpan.tag(TestConst.RESPONSE_TAG_NAME));\n        assertNull(mockSpan.parentId());\n\n        request = requestFactory.createRequest(new URI(url), HttpMethod.GET);\n        methodInfo = methodInfoBuilder.invoker(request).build();\n        clientHttpRequestInterceptor.before(methodInfo, context);\n        methodInfo = methodInfoBuilder.retValue(clientHttpResponse).build();\n        RuntimeException runtimeException = new RuntimeException(\"test error\");\n        methodInfo.throwable(runtimeException);\n        clientHttpRequestInterceptor.after(methodInfo, context);\n\n        mockSpan = MockEaseAgent.getLastSpan();\n        assertNotNull(mockSpan);\n        assertEquals(Span.Kind.CLIENT.name(), mockSpan.kind());\n        assertEquals(TestConst.RESPONSE_TAG_VALUE, mockSpan.tag(TestConst.RESPONSE_TAG_NAME));\n        assertNull(mockSpan.parentId());\n\n\n        request = requestFactory.createRequest(new URI(url), HttpMethod.GET);\n        methodInfo = methodInfoBuilder.invoker(request).build();\n        Span span = context.nextSpan();\n        try (Scope ignored = span.maybeScope()) {\n            clientHttpRequestInterceptor.before(methodInfo, context);\n            methodInfo = methodInfoBuilder.retValue(clientHttpResponse).build();\n            clientHttpRequestInterceptor.after(methodInfo, context);\n            mockSpan = MockEaseAgent.getLastSpan();\n            assertEquals(span.traceIdString(), mockSpan.traceId());\n            assertEquals(span.spanIdString(), mockSpan.parentId());\n            assertNotNull(mockSpan.id());\n        }\n        span.finish();\n    }\n\n    @Test\n    public void getRequest() throws URISyntaxException, IOException {\n        ClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();\n        ClientHttpRequest request = requestFactory.createRequest(new URI(\"http://127.0.0.1:8080/test\"), HttpMethod.GET);\n        MethodInfo methodInfo = MethodInfo.builder().invoker(request).build();\n        Context context = EaseAgent.getContext();\n\n        ClientHttpRequestInterceptor clientHttpRequestInterceptor = new ClientHttpRequestInterceptor();\n        HttpRequest httpRequest = clientHttpRequestInterceptor.getRequest(methodInfo, context);\n        assertEquals(com.megaease.easeagent.plugin.api.trace.Span.Kind.CLIENT, httpRequest.kind());\n        assertEquals(\"GET\", httpRequest.method());\n        assertEquals(\"/test\", httpRequest.path());\n    }\n\n    @Test\n    public void getResponse() throws IOException, URISyntaxException {\n        String url = \"http://127.0.0.1:8080/test\";\n        ClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();\n        ClientHttpRequest request = requestFactory.createRequest(new URI(url), HttpMethod.GET);\n        ClientHttpResponse clientHttpResponse = SimpleClientHttpResponseFactory.createMockResponse(url);\n        MethodInfo.MethodInfoBuilder methodInfoBuilder = MethodInfo.builder().invoker(request).retValue(clientHttpResponse);\n        MethodInfo methodInfo = methodInfoBuilder.build();\n\n        ClientHttpRequestInterceptor clientHttpRequestInterceptor = new ClientHttpRequestInterceptor();\n        HttpResponse httpResponse = clientHttpRequestInterceptor.getResponse(methodInfo, EaseAgent.getContext());\n        assertEquals(\"GET\", httpResponse.method());\n        assertNull(httpResponse.route());\n        assertNull(httpResponse.maybeError());\n        assertEquals(200, httpResponse.statusCode());\n\n        RuntimeException runtimeException = new RuntimeException(\"test error\");\n        methodInfoBuilder.throwable(runtimeException);\n        httpResponse = clientHttpRequestInterceptor.getResponse(methodInfoBuilder.build(), EaseAgent.getContext());\n        assertEquals(runtimeException, httpResponse.maybeError());\n\n\n    }\n\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-rest-template-3.5.3/src/test/java/org/springframework/http/client/SimpleClientHttpResponseFactory.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.http.client;\n\n\nimport com.megaease.easeagent.plugin.rest.template.interceptor.TestConst;\n\nimport java.io.IOException;\nimport java.net.HttpURLConnection;\nimport java.net.URL;\n\npublic class SimpleClientHttpResponseFactory {\n    public static ClientHttpResponse createMockResponse(String urlStr) throws IOException {\n        URL url = new URL(urlStr);\n        HttpURLConnection httpURLConnection = new HttpURLConnection(url) {\n            @Override\n            public void disconnect() {\n\n            }\n\n            @Override\n            public boolean usingProxy() {\n                return false;\n            }\n\n            @Override\n            public void connect() throws IOException {\n\n            }\n\n            @Override\n            public int getResponseCode() throws IOException {\n                return 200;\n            }\n\n            @Override\n            public String getHeaderFieldKey(int n) {\n                if (n == 0) {\n                    return TestConst.RESPONSE_TAG_NAME;\n                }\n                return super.getHeaderFieldKey(n);\n            }\n\n            @Override\n            public String getHeaderField(int n) {\n                if (n == 0) {\n                    return TestConst.RESPONSE_TAG_VALUE;\n                }\n                return super.getHeaderField(n);\n            }\n        };\n        return new SimpleClientHttpResponse(httpURLConnection);\n    }\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-rest-template-3.5.3/src/test/resources/mock_agent.properties",
    "content": "plugin.integrability.global.forwarded.enabled=true\neaseagent.progress.forwarded.headers=X-Forwarded-For\nobservability.tracings.tag.response.headers.eg.0=X-EG-Test\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright (c) 2021, MegaEase\n  ~ All rights reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~   http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>spring-boot-3.5.3</artifactId>\n        <groupId>com.megaease.easeagent</groupId>\n        <version>2.3.0</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>spring-boot-servicename-3.5.3</artifactId>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>plugin-api</artifactId>\n            <scope>provided</scope>\n        </dependency>\n\n\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-web</artifactId>\n            <scope>provided</scope>\n            <version>3.5.3</version>\n            <exclusions>\n                <exclusion>\n                    <groupId>org.springframework.boot</groupId>\n                    <artifactId>spring-boot-starter-logging</artifactId>\n                </exclusion>\n            </exclusions>\n        </dependency>\n\n        <dependency>\n            <groupId>org.springframework.cloud</groupId>\n            <artifactId>spring-cloud-starter-gateway</artifactId>\n            <exclusions>\n                <exclusion>\n                    <groupId>org.springframework.boot</groupId>\n                    <artifactId>spring-boot-starter-logging</artifactId>\n                </exclusion>\n            </exclusions>\n            <scope>provided</scope>\n        </dependency>\n\n        <dependency>\n            <groupId>org.springframework.cloud</groupId>\n            <artifactId>spring-cloud-starter-openfeign</artifactId>\n            <scope>provided</scope>\n            <exclusions>\n                <exclusion>\n                    <groupId>org.springframework.boot</groupId>\n                    <artifactId>spring-boot-starter-logging</artifactId>\n                </exclusion>\n            </exclusions>\n        </dependency>\n\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>plugin-api-mock</artifactId>\n            <version>${project.version}</version>\n            <scope>test</scope>\n        </dependency>\n\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-test</artifactId>\n            <version>3.5.3</version>\n            <exclusions>\n                <exclusion>\n                    <groupId>org.springframework.boot</groupId>\n                    <artifactId>spring-boot-starter-logging</artifactId>\n                </exclusion>\n            </exclusions>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.fasterxml.jackson.core</groupId>\n            <artifactId>jackson-core</artifactId>\n            <version>2.19.1</version>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.fasterxml.jackson.core</groupId>\n            <artifactId>jackson-databind</artifactId>\n            <version>2.19.1</version>\n            <scope>test</scope>\n        </dependency>\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/main/java/com/megaease/easeagent/plugin/servicename/springboot353/Const.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.servicename.springboot353;\n\nimport com.megaease.easeagent.plugin.CodeVersion;\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\n\npublic interface Const {\n    String FeignBlockingLoadBalancerClient = \"org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalancerClient\";\n    String RetryableFeignBlockingLoadBalancerClient = \"org.springframework.cloud.openfeign.loadbalancer.RetryableFeignBlockingLoadBalancerClient\";\n    //----------------- FeignClient end ---------------\n\n    //----------------- RestTemplate begin ---------------\n    String RetryLoadBalancerInterceptor = \"org.springframework.cloud.client.loadbalancer.RetryLoadBalancerInterceptor\";\n\n    String LoadBalancerInterceptor = \"org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor\";\n    //----------------- RestTemplate end ---------------\n\n    //----------------- web client begin ---------------\n    String ReactorLoadBalancerExchangeFilterFunction = \"org.springframework.cloud.client.loadbalancer.reactive.ReactorLoadBalancerExchangeFilterFunction\";\n\n    String RetryableLoadBalancerExchangeFilterFunction = \"org.springframework.cloud.client.loadbalancer.reactive.RetryableLoadBalancerExchangeFilterFunction\";\n    //----------------- web client end ---------------\n\n    //----------------- spring gateway begin ---------------\n    String FilteringWebHandler = \"org.springframework.cloud.gateway.handler.FilteringWebHandler\";\n    //----------------- spring gateway end ---------------\n    String DEFAULT_PROPAGATE_HEAD = \"X-Mesh-RPC-Service\";\n    String PROPAGATE_HEAD_CONFIG = \"propagate.head\";\n\n    CodeVersion VERSIONS = CodeVersion.builder()\n        .key(ConfigConst.CodeVersion.KEY_SPRING_BOOT)\n        .add(Points.DEFAULT_VERSION)\n        .add(ConfigConst.CodeVersion.VERSION_SPRING_BOOT3).build();\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/main/java/com/megaease/easeagent/plugin/servicename/springboot353/ReflectionTool.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.servicename.springboot353;\n\nimport org.springframework.util.ReflectionUtils;\n\nimport java.lang.reflect.Field;\nimport java.lang.reflect.Method;\n\npublic class ReflectionTool {\n    public static Object invokeMethod(Object own, String method, Object... args) throws ReflectiveOperationException {\n        Class<?>[] types = new Class[args.length];\n        for (int i = 0; i < args.length; i++) {\n            types[i] = args[i].getClass();\n        }\n        Method md = ReflectionUtils.findMethod(own.getClass(), method, types);\n        ReflectionUtils.makeAccessible(md);\n        return ReflectionUtils.invokeMethod(md, own, args);\n    }\n\n    public static Object extractField(Object own, String field) throws ReflectiveOperationException {\n        Field fd = ReflectionUtils.findField(own.getClass(), field);\n        ReflectionUtils.makeAccessible(fd);\n        return ReflectionUtils.getField(fd, own);\n    }\n\n\n    public static boolean hasText(String val) {\n        return val != null && val.trim().length() > 0;\n    }\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/main/java/com/megaease/easeagent/plugin/servicename/springboot353/ServiceNamePlugin.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.servicename.springboot353;\n\nimport com.megaease.easeagent.plugin.AgentPlugin;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\n\npublic class ServiceNamePlugin implements AgentPlugin {\n    @Override\n    public String getNamespace() {\n        return ConfigConst.Namespace.SERVICE_NAME;\n    }\n\n    @Override\n    public String getDomain() {\n        return ConfigConst.INTEGRABILITY;\n\n    }\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/main/java/com/megaease/easeagent/plugin/servicename/springboot353/ServiceNamePluginConfig.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.servicename.springboot353;\n\nimport com.megaease.easeagent.plugin.api.config.AutoRefreshConfigSupplier;\nimport com.megaease.easeagent.plugin.api.config.AutoRefreshPluginConfig;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.utils.common.StringUtils;\n\nimport static com.megaease.easeagent.plugin.servicename.springboot353.Const.DEFAULT_PROPAGATE_HEAD;\nimport static com.megaease.easeagent.plugin.servicename.springboot353.Const.PROPAGATE_HEAD_CONFIG;\n\npublic class ServiceNamePluginConfig implements AutoRefreshPluginConfig {\n    public static final AutoRefreshConfigSupplier<ServiceNamePluginConfig> SUPPLIER = new AutoRefreshConfigSupplier<ServiceNamePluginConfig>() {\n        @Override\n        public ServiceNamePluginConfig newInstance() {\n            return new ServiceNamePluginConfig();\n        }\n    };\n\n    private volatile String propagateHead = DEFAULT_PROPAGATE_HEAD;\n\n    public String getPropagateHead() {\n        return propagateHead;\n    }\n\n    @Override\n    public void onChange(IPluginConfig oldConfig, IPluginConfig newConfig) {\n        String propagateHead = newConfig.getString(PROPAGATE_HEAD_CONFIG);\n        if (StringUtils.isEmpty(propagateHead) || StringUtils.isEmpty(propagateHead.trim())) {\n            return;\n        }\n        this.propagateHead = propagateHead.trim();\n    }\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/main/java/com/megaease/easeagent/plugin/servicename/springboot353/advice/FeignClientLoadBalancerClientAdvice.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.servicename.springboot353.advice;\n\nimport com.megaease.easeagent.plugin.CodeVersion;\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\nimport com.megaease.easeagent.plugin.servicename.springboot353.Const;\n\nimport java.util.Set;\n\nimport static com.megaease.easeagent.plugin.tools.matcher.ClassMatcherUtils.name;\n\n// OpenFeign\npublic class FeignClientLoadBalancerClientAdvice implements Points {\n    @Override\n    public CodeVersion codeVersions() {\n        return Const.VERSIONS;\n    }\n\n    //.type(named(FeignBlockingLoadBalancerClient))\n    //            .transform(feignBlockingLoadBalancerClientExecute(named(\"execute\").and(takesArguments(2))\n    //                .and(takesArgument(0, named(\"feign.Request\")))\n    //            ))\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return name(Const.FeignBlockingLoadBalancerClient).or(name(Const.RetryableFeignBlockingLoadBalancerClient));\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.multiBuilder()\n            .match(MethodMatcher.builder().named(\"execute\")\n                .argsLength(2)\n                .arg(0, \"feign.Request\")\n                .qualifier(\"servicename\")\n                .build())\n            .build();\n    }\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/main/java/com/megaease/easeagent/plugin/servicename/springboot353/advice/FilteringWebHandlerAdvice.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.servicename.springboot353.advice;\n\nimport com.megaease.easeagent.plugin.CodeVersion;\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\nimport com.megaease.easeagent.plugin.servicename.springboot353.Const;\n\nimport java.util.Set;\n\nimport static com.megaease.easeagent.plugin.tools.matcher.ClassMatcherUtils.name;\n\n// Spring Cloud Gateway\npublic class FilteringWebHandlerAdvice implements Points {//FilteringWebHandlerInterceptor\n    //            .type(named(FilteringWebHandler))\n    //            .transform(filteringWebHandlerHandle(named(\"handle\").and(takesArguments(1))\n    //                .and(takesArgument(0, named(\"org.springframework.web.server.ServerWebExchange\")))\n    //            ))\n\n    @Override\n    public CodeVersion codeVersions() {\n        return Const.VERSIONS;\n    }\n\n\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return name(Const.FilteringWebHandler);\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.multiBuilder()\n            .match(MethodMatcher.builder().named(\"handle\")\n                .argsLength(1)\n                .arg(0, \"org.springframework.web.server.ServerWebExchange\")\n                .qualifier(\"servicename\")\n                .build())\n            .build();\n    }\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/main/java/com/megaease/easeagent/plugin/servicename/springboot353/advice/RestTemplateInterceptAdvice.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.servicename.springboot353.advice;\n\nimport com.megaease.easeagent.plugin.CodeVersion;\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\nimport com.megaease.easeagent.plugin.servicename.springboot353.Const;\n\nimport java.util.Set;\n\nimport static com.megaease.easeagent.plugin.tools.matcher.ClassMatcherUtils.name;\n\npublic class RestTemplateInterceptAdvice implements Points {\n    @Override\n    public CodeVersion codeVersions() {\n        return Const.VERSIONS;\n    }\n\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return name(Const.RetryLoadBalancerInterceptor).or(name(Const.LoadBalancerInterceptor));\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.multiBuilder()\n            .match(MethodMatcher.builder().named(\"intercept\")\n                .argsLength(3)\n                .arg(0, \"org.springframework.http.HttpRequest\")\n                .qualifier(\"servicename\")\n                .build())\n            .build();\n    }\n\n\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/main/java/com/megaease/easeagent/plugin/servicename/springboot353/advice/WebClientFilterAdvice.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.servicename.springboot353.advice;\n\nimport com.megaease.easeagent.plugin.CodeVersion;\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\nimport com.megaease.easeagent.plugin.servicename.springboot353.Const;\n\nimport java.util.Set;\n\nimport static com.megaease.easeagent.plugin.tools.matcher.ClassMatcherUtils.name;\n\npublic class WebClientFilterAdvice implements Points {\n    @Override\n    public CodeVersion codeVersions() {\n        return Const.VERSIONS;\n    }\n\n    //// WebClient\n    //            .type(namedOneOf(ReactorLoadBalancerExchangeFilterFunction, LoadBalancerExchangeFilterFunction))\n    //            .transform(webClientFilter(named(\"filter\").and(takesArguments(2))\n    //                .and(takesArgument(0, named(\"org.springframework.web.reactive.function.client.ClientRequest\")))\n    //            ))\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return name(Const.ReactorLoadBalancerExchangeFilterFunction).or(name(Const.RetryableLoadBalancerExchangeFilterFunction));\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.multiBuilder()\n            .match(MethodMatcher.builder().named(\"filter\")\n                .argsLength(2)\n                .arg(0, \"org.springframework.web.reactive.function.client.ClientRequest\")\n                .qualifier(\"servicename\")\n                .build())\n            .build();\n    }\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/main/java/com/megaease/easeagent/plugin/servicename/springboot353/interceptor/BaseServiceNameInterceptor.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.servicename.springboot353.interceptor;\n\nimport com.megaease.easeagent.plugin.api.config.AutoRefreshPluginConfigRegistry;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.servicename.springboot353.ServiceNamePluginConfig;\n\npublic abstract class BaseServiceNameInterceptor implements Interceptor {\n    protected static ServiceNamePluginConfig config = null;\n\n    @Override\n    public void init(IPluginConfig pConfig, String className, String methodName, String methodDescriptor) {\n        config = AutoRefreshPluginConfigRegistry.getOrCreate(pConfig.domain(), pConfig.namespace(), pConfig.id(), ServiceNamePluginConfig.SUPPLIER);\n    }\n\n    @Override\n    public int order() {\n        return Order.HIGH.getOrder();\n    }\n\n    @Override\n    public String getType() {\n        return \"addServiceNameHead\";\n    }\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/main/java/com/megaease/easeagent/plugin/servicename/springboot353/interceptor/FeignClientLoadBalancerClientInterceptor.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.servicename.springboot353.interceptor;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.logging.Logger;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.servicename.springboot353.ReflectionTool;\nimport com.megaease.easeagent.plugin.servicename.springboot353.advice.FeignClientLoadBalancerClientAdvice;\nimport feign.Request;\n\nimport java.net.URI;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.HashMap;\n\n@AdviceTo(value = FeignClientLoadBalancerClientAdvice.class, qualifier = \"servicename\")\npublic class FeignClientLoadBalancerClientInterceptor extends BaseServiceNameInterceptor {\n    private static final Logger LOGGER = EaseAgent.getLogger(FeignClientLoadBalancerClientInterceptor.class);\n\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n        String method = methodInfo.getMethod();\n        try {\n            if (LOGGER.isDebugEnabled()) {\n                LOGGER.debug(\"enter method [{}]\", method);\n            }\n            Request request = (Request) methodInfo.getArgs()[0];\n            String url = request.url();\n            String host = URI.create(url).getHost();\n            if (ReflectionTool.hasText(host)) {\n                final HashMap<String, Collection<String>> newHeaders = new HashMap<>(request.headers());\n                newHeaders.put(config.getPropagateHead(), Collections.singleton(host));\n                context.injectForwardedHeaders((name, value) -> newHeaders.put(name, Collections.singleton(value)));\n                final Request newRequest = Request.create(request.httpMethod(), request.url(), newHeaders, request.body(), request.charset(), request.requestTemplate());\n                methodInfo.changeArg(0, newRequest);\n            }\n        } catch (Throwable e) {\n            LOGGER.warn(\"intercept method [{}] failure\", method, e);\n        }\n    }\n\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/main/java/com/megaease/easeagent/plugin/servicename/springboot353/interceptor/FilteringWebHandlerInterceptor.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.servicename.springboot353.interceptor;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.logging.Logger;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.servicename.springboot353.Const;\nimport com.megaease.easeagent.plugin.servicename.springboot353.advice.FilteringWebHandlerAdvice;\nimport org.springframework.cloud.gateway.route.Route;\nimport org.springframework.http.server.reactive.ServerHttpRequest;\nimport org.springframework.util.StringUtils;\nimport org.springframework.web.server.ServerWebExchange;\n\nimport java.net.URI;\nimport java.util.Map;\n\n@AdviceTo(value = FilteringWebHandlerAdvice.class, qualifier = \"servicename\")\npublic class FilteringWebHandlerInterceptor extends BaseServiceNameInterceptor {\n    private static final Logger LOGGER = EaseAgent.getLogger(FilteringWebHandlerInterceptor.class);\n\n    private static volatile String routeAttribute = null;\n\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n        String method = methodInfo.getMethod();\n        try {\n            if (LOGGER.isDebugEnabled()) {\n                LOGGER.debug(\"enter method [{}]\", method);\n            }\n            ServerWebExchange exchange = (ServerWebExchange) methodInfo.getArgs()[0];\n            Route route = getRoute(exchange);\n            if (route == null) {\n                return;\n            }\n            URI uri = route.getUri();\n            String scheme = uri.getScheme();\n            if (!scheme.equals(\"lb\")) {\n                return;\n            }\n            String host = uri.getHost();\n            if (!StringUtils.hasText(host)) {\n                return;\n            }\n            ServerHttpRequest newRequest = exchange.getRequest().mutate().header(config.getPropagateHead(), host).build();\n            ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();\n            methodInfo.changeArg(0, newExchange);\n        } catch (Throwable e) {\n            LOGGER.warn(\"intercept method [{}] failure\", method, e);\n        }\n    }\n\n    public Route getRoute(ServerWebExchange exchange) {\n        if (routeAttribute != null) {\n            return exchange.getAttribute(routeAttribute);\n        }\n        for (Map.Entry<String, Object> entry : exchange.getAttributes().entrySet()) {\n            Object value = entry.getValue();\n            if (value instanceof Route) {\n                routeAttribute = entry.getKey();\n                return (Route) value;\n            }\n        }\n        return null;\n    }\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/main/java/com/megaease/easeagent/plugin/servicename/springboot353/interceptor/RestTemplateInterceptInterceptor.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.servicename.springboot353.interceptor;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.logging.Logger;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.servicename.springboot353.ReflectionTool;\nimport com.megaease.easeagent.plugin.servicename.springboot353.advice.RestTemplateInterceptAdvice;\nimport org.springframework.util.MultiValueMap;\n\nimport java.net.URI;\n\n@AdviceTo(value = RestTemplateInterceptAdvice.class, qualifier = \"servicename\")\npublic class RestTemplateInterceptInterceptor extends BaseServiceNameInterceptor  {\n    private static final Logger LOGGER = EaseAgent.getLogger(RestTemplateInterceptInterceptor.class);\n\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n        String method = methodInfo.getMethod();\n        try {\n            Object request = methodInfo.getArgs()[0];\n            if (LOGGER.isDebugEnabled()) {\n                LOGGER.debug(\"enter method [{}]\", method);\n            }\n            URI uri = (URI) ReflectionTool.invokeMethod(request, \"getURI\");\n            String host = uri.getHost();\n            if (ReflectionTool.hasText(host)) {\n                Object fakeHeaders = ReflectionTool.invokeMethod(request, \"getHeaders\");//org.springframework.http.HttpHeaders\n                @SuppressWarnings(\"unchecked\")\n                MultiValueMap<String, String> headers = (MultiValueMap<String, String>) ReflectionTool.extractField(fakeHeaders, \"headers\");\n                headers.add(config.getPropagateHead(), host);\n                context.injectForwardedHeaders(headers::add);\n            }\n        } catch (Throwable e) {\n            LOGGER.warn(\"intercept method [{}] failure\", method, e);\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/main/java/com/megaease/easeagent/plugin/servicename/springboot353/interceptor/WebClientFilterInterceptor.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.servicename.springboot353.interceptor;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.logging.Logger;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.servicename.springboot353.ReflectionTool;\nimport com.megaease.easeagent.plugin.servicename.springboot353.advice.WebClientFilterAdvice;\nimport org.springframework.util.MultiValueMap;\n\nimport java.net.URI;\n\n@AdviceTo(value = WebClientFilterAdvice.class, qualifier = \"servicename\")\npublic class WebClientFilterInterceptor extends BaseServiceNameInterceptor  {\n    private static final Logger LOGGER = EaseAgent.getLogger(WebClientFilterInterceptor.class);\n\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n        String method = methodInfo.getMethod();\n        try {\n            if (LOGGER.isDebugEnabled()) {\n                LOGGER.debug(\"enter method [{}]\", method);\n            }\n            Object request = methodInfo.getArgs()[0];\n            URI uri = (URI) ReflectionTool.invokeMethod(request, \"url\");\n            String host = uri.getHost();\n            if (ReflectionTool.hasText(host)) {\n                Object fakeHeaders = ReflectionTool.invokeMethod(request, \"headers\");//org.springframework.http.HttpHeaders\n                @SuppressWarnings(\"unchecked\")\n                MultiValueMap<String, String> headers = (MultiValueMap<String, String>) ReflectionTool.extractField(fakeHeaders, \"headers\");\n                headers.add(config.getPropagateHead(), host);\n                context.injectForwardedHeaders(headers::add);\n            }\n        } catch (Throwable e) {\n            LOGGER.warn(\"intercept method [{}] failure\", method, e);\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/test/java/com/megaease/easeagent/plugin/servicename/springboot353/ReflectionToolTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.servicename.springboot353;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\npublic class ReflectionToolTest {\n\n    @Test\n    public void invokeMethod() throws ReflectiveOperationException {\n        String name = \"testName\";\n        TestClass testClass = new TestClass(name);\n        assertEquals(name, ReflectionTool.invokeMethod(testClass, \"get\"));\n        String p = \"ttt\";\n        assertEquals(name + p, ReflectionTool.invokeMethod(testClass, \"get\", p));\n    }\n\n    @Test\n    public void extractField() throws ReflectiveOperationException {\n        String name = \"testName\";\n        TestClass testClass = new TestClass(name);\n        assertEquals(name, ReflectionTool.extractField(testClass, \"name\"));\n    }\n\n    @Test\n    public void hasText() {\n        assertFalse(ReflectionTool.hasText(null));\n        assertFalse(ReflectionTool.hasText(\"\"));\n        assertFalse(ReflectionTool.hasText(\"  \"));\n        assertTrue(ReflectionTool.hasText(\"ab\"));\n        assertTrue(ReflectionTool.hasText(\" ab \"));\n    }\n\n    class TestClass {\n        private final String name;\n\n        public TestClass(String name) {\n            this.name = name;\n        }\n\n        private String get() {\n            return name;\n        }\n\n        private String get(String p) {\n            return name + p;\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/test/java/com/megaease/easeagent/plugin/servicename/springboot353/interceptor/BaseServiceNameInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.servicename.springboot353.interceptor;\n\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.servicename.springboot353.Const;\nimport com.megaease.easeagent.plugin.servicename.springboot353.ServiceNamePlugin;\nimport com.megaease.easeagent.plugin.servicename.springboot353.ServiceNamePluginConfig;\nimport org.junit.Test;\n\nimport static org.junit.Assert.assertEquals;\n\npublic class BaseServiceNameInterceptorTest {\n\n    public static void initInterceptor(Interceptor interceptor) {\n        ServiceNamePlugin plugin = new ServiceNamePlugin();\n        IPluginConfig config = EaseAgent.getConfig(plugin.getDomain(), plugin.getNamespace(), interceptor.getType());\n        interceptor.init(config, \"\", \"\", \"\");\n    }\n\n\n    @Test\n    public void init() {\n        MockBaseServiceNameInterceptor mockBaseServiceNameInterceptor = new MockBaseServiceNameInterceptor();\n        initInterceptor(mockBaseServiceNameInterceptor);\n        ServiceNamePluginConfig serviceNamePluginConfig = mockBaseServiceNameInterceptor.getConfig();\n        assertEquals(Const.DEFAULT_PROPAGATE_HEAD, serviceNamePluginConfig.getPropagateHead());\n    }\n\n    @Test\n    public void order() {\n        MockBaseServiceNameInterceptor mockBaseServiceNameInterceptor = new MockBaseServiceNameInterceptor();\n        assertEquals(Order.HIGH.getOrder(), mockBaseServiceNameInterceptor.order());\n    }\n\n    @Test\n    public void getType() {\n        MockBaseServiceNameInterceptor mockBaseServiceNameInterceptor = new MockBaseServiceNameInterceptor();\n        assertEquals(\"addServiceNameHead\", mockBaseServiceNameInterceptor.getType());\n    }\n\n    static class MockBaseServiceNameInterceptor extends BaseServiceNameInterceptor {\n\n        @Override\n        public void before(MethodInfo methodInfo, Context context) {\n\n        }\n\n        public ServiceNamePluginConfig getConfig() {\n            return config;\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/test/java/com/megaease/easeagent/plugin/servicename/springboot353/interceptor/CheckUtils.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.servicename.springboot353.interceptor;\n\nimport com.megaease.easeagent.plugin.api.trace.Getter;\nimport com.megaease.easeagent.plugin.servicename.springboot353.Const;\n\nimport static org.junit.Assert.assertEquals;\n\npublic class CheckUtils {\n    public static void check(Getter headers, String serviceName) {\n        assertEquals(serviceName, headers.header(Const.DEFAULT_PROPAGATE_HEAD));\n        assertEquals(TestConst.FORWARDED_VALUE, headers.header(TestConst.FORWARDED_NAME));\n    }\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/test/java/com/megaease/easeagent/plugin/servicename/springboot353/interceptor/FeignBlockingLoadBalancerClientInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.servicename.springboot353.interceptor;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport feign.Request;\nimport feign.RequestTemplate;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport java.util.Collection;\n\nimport static org.junit.Assert.assertNotNull;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class FeignBlockingLoadBalancerClientInterceptorTest {\n\n\n    @Test\n    public void before() {\n        FeignClientLoadBalancerClientInterceptor interceptor = new FeignClientLoadBalancerClientInterceptor();\n        BaseServiceNameInterceptorTest.initInterceptor(interceptor);\n        EaseAgent.getContext().put(TestConst.FORWARDED_NAME, TestConst.FORWARDED_VALUE);\n\n\n        RequestTemplate requestTemplate = new RequestTemplate();\n        String host = \"TEST-SERVER\";\n        Request request = Request.create(\n            Request.HttpMethod.GET,\n            \"http://\" + host,\n            requestTemplate.headers(),\n            Request.Body.create(requestTemplate.body()),\n            requestTemplate\n        );\n\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{request}).build();\n\n        interceptor.before(methodInfo, EaseAgent.getContext());\n\n        Request newRequest = (Request) methodInfo.getArgs()[0];\n        CheckUtils.check(name -> {\n            Collection<String> head = newRequest.headers().get(name);\n            assertNotNull(head);\n            return head.iterator().next();\n        }, host);\n    }\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/test/java/com/megaease/easeagent/plugin/servicename/springboot353/interceptor/FilteringWebHandlerInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.servicename.springboot353.interceptor;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.servicename.springboot353.Const;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.cloud.gateway.route.Route;\nimport org.springframework.mock.http.server.reactive.MockServerHttpRequest;\nimport org.springframework.mock.web.server.MockServerWebExchange;\nimport org.springframework.web.server.ServerWebExchange;\n\nimport java.net.URI;\nimport java.net.URISyntaxException;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNull;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class FilteringWebHandlerInterceptorTest {\n\n    @Test\n    public void before() throws URISyntaxException {\n        FilteringWebHandlerInterceptor interceptor = new FilteringWebHandlerInterceptor();\n        BaseServiceNameInterceptorTest.initInterceptor(interceptor);\n        EaseAgent.getContext().put(TestConst.FORWARDED_NAME, TestConst.FORWARDED_VALUE);\n\n        MockServerWebExchange mockServerWebExchange = buildMockServerWebExchange();\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{mockServerWebExchange}).build();\n        interceptor.before(methodInfo, EaseAgent.getContext());\n        assertNull(header(mockServerWebExchange, FilteringWebHandlerInterceptor.config.getPropagateHead()));\n\n        mockServerWebExchange = buildMockServerWebExchange();\n        Route route = new MockRouteBuilder().uri(new URI(\"http://127.0.0.1:8080\")).id(\"1\").build();\n        mockServerWebExchange.getAttributes().put(TestConst.SERVER_WEB_EXCHANGE_ROUTE_ATTRIBUTE, route);\n        methodInfo = MethodInfo.builder().args(new Object[]{mockServerWebExchange}).build();\n        interceptor.before(methodInfo, EaseAgent.getContext());\n        assertNull(header(mockServerWebExchange, FilteringWebHandlerInterceptor.config.getPropagateHead()));\n\n        mockServerWebExchange = buildMockServerWebExchange();\n        route = new MockRouteBuilder().uri(new URI(\"lb://127.0.0.1:8080\")).id(\"11\").build();\n        mockServerWebExchange.getAttributes().put(TestConst.SERVER_WEB_EXCHANGE_ROUTE_ATTRIBUTE, route);\n        methodInfo = MethodInfo.builder().args(new Object[]{mockServerWebExchange}).build();\n        interceptor.before(methodInfo, EaseAgent.getContext());\n        assertEquals(\"127.0.0.1\", header((ServerWebExchange)methodInfo.getArgs()[0], FilteringWebHandlerInterceptor.config.getPropagateHead()));\n\n    }\n\n    private static final String header(ServerWebExchange exchange, String name) {\n        return exchange.getRequest().getHeaders().getFirst(name);\n    }\n\n    private static final MockServerWebExchange buildMockServerWebExchange() {\n        MockServerHttpRequest mockServerHttpRequest = MockServerHttpRequest.get(\"http://127.0.0.1:8080\", \"a=b\").build();\n        return MockServerWebExchange.builder(mockServerHttpRequest).build();\n    }\n\n    static class MockRouteBuilder extends Route.Builder {\n        public MockRouteBuilder() {\n            predicate = serverWebExchange -> true;\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/test/java/com/megaease/easeagent/plugin/servicename/springboot353/interceptor/RestTemplateInterceptInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.servicename.springboot353.interceptor;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.mock.http.client.MockClientHttpRequest;\n\nimport java.net.URI;\nimport java.net.URISyntaxException;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class RestTemplateInterceptInterceptorTest {\n\n    @Test\n    public void before() throws URISyntaxException {\n        RestTemplateInterceptInterceptor interceptor = new RestTemplateInterceptInterceptor();\n        BaseServiceNameInterceptorTest.initInterceptor(interceptor);\n        EaseAgent.getContext().put(TestConst.FORWARDED_NAME, TestConst.FORWARDED_VALUE);\n\n        MockClientHttpRequest httpRequest = new MockClientHttpRequest();\n        String host = \"TEST-SERVER\";\n        httpRequest.setURI(new URI(\"http://\" + host));\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{httpRequest}).build();\n\n        interceptor.before(methodInfo, EaseAgent.getContext());\n\n        MockClientHttpRequest newRequest = (MockClientHttpRequest) methodInfo.getArgs()[0];\n        CheckUtils.check(name -> newRequest.getHeaders().getFirst(name), host);\n\n    }\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/test/java/com/megaease/easeagent/plugin/servicename/springboot353/interceptor/TestConst.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.servicename.springboot353.interceptor;\n\npublic class TestConst {\n    public static final String FORWARDED_NAME = \"X-Forwarded-For\";\n    public static final String FORWARDED_VALUE = \"testForwarded\";\n\n    public static final String SERVER_WEB_EXCHANGE_ROUTE_ATTRIBUTE = \"org.springframework.cloud.gateway.support.ServerWebExchangeUtils.gatewayRoute\";\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/test/java/com/megaease/easeagent/plugin/servicename/springboot353/interceptor/WebClientFilterInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.servicename.springboot353.interceptor;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.http.HttpMethod;\nimport org.springframework.web.reactive.function.client.ClientRequest;\n\nimport java.net.URI;\nimport java.net.URISyntaxException;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class WebClientFilterInterceptorTest {\n\n    @Test\n    public void before() throws URISyntaxException {\n        WebClientFilterInterceptor interceptor = new WebClientFilterInterceptor();\n        BaseServiceNameInterceptorTest.initInterceptor(interceptor);\n        EaseAgent.getContext().put(TestConst.FORWARDED_NAME, TestConst.FORWARDED_VALUE);\n\n        String host = \"TEST-SERVER\";\n        ClientRequest clientRequest = build(new URI(\"http://\" + host));\n\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{clientRequest}).build();\n\n        interceptor.before(methodInfo, EaseAgent.getContext());\n\n        ClientRequest newRequest = (ClientRequest) methodInfo.getArgs()[0];\n        CheckUtils.check(name -> newRequest.headers().getFirst(name), host);\n\n    }\n\n    public static ClientRequest build(URI url) {\n        return ClientRequest.create(HttpMethod.GET, url).build();\n    }\n}\n"
  },
  {
    "path": "plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/test/resources/mock_agent.properties",
    "content": "easeagent.progress.forwarded.headers=X-Forwarded-For\n"
  },
  {
    "path": "plugins/spring-gateway/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright (c) 2021, MegaEase\n  ~ All rights reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~   http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>plugins</artifactId>\n        <groupId>com.megaease.easeagent</groupId>\n        <version>2.3.0</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>spring-gateway-plugin</artifactId>\n\n\n    <dependencies>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>plugin-api</artifactId>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.google.code.findbugs</groupId>\n            <artifactId>jsr305</artifactId>\n            <scope>compile</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.cloud</groupId>\n            <artifactId>spring-cloud-starter-gateway</artifactId>\n            <version>3.0.3</version>\n            <scope>provided</scope>\n        </dependency>\n\n        <dependency>\n            <groupId>org.springframework</groupId>\n            <artifactId>spring-webflux</artifactId>\n            <version>5.3.18</version>\n            <scope>provided</scope>\n        </dependency>\n\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>plugin-api-mock</artifactId>\n            <version>${project.version}</version>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework</groupId>\n            <artifactId>spring-test</artifactId>\n            <version>5.3.18</version>\n            <scope>test</scope>\n        </dependency>\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "plugins/spring-gateway/src/main/java/easeagent/plugin/spring/gateway/AccessPlugin.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring.gateway;\n\nimport com.megaease.easeagent.plugin.AgentPlugin;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\n\npublic class AccessPlugin implements AgentPlugin {\n    @Override\n    public String getNamespace() {\n        return ConfigConst.Namespace.ACCESS;\n    }\n\n    @Override\n    public String getDomain() {\n        return ConfigConst.OBSERVABILITY;\n    }\n}\n"
  },
  {
    "path": "plugins/spring-gateway/src/main/java/easeagent/plugin/spring/gateway/ForwardedPlugin.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring.gateway;\n\nimport com.megaease.easeagent.plugin.AgentPlugin;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\n\npublic class ForwardedPlugin implements AgentPlugin {\n    @Override\n    public String getNamespace() {\n        return ConfigConst.Namespace.FORWARDED;\n    }\n\n    @Override\n    public String getDomain() {\n        return ConfigConst.INTEGRABILITY;\n    }\n}\n"
  },
  {
    "path": "plugins/spring-gateway/src/main/java/easeagent/plugin/spring/gateway/SpringGatewayPlugin.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring.gateway;\n\nimport com.megaease.easeagent.plugin.AgentPlugin;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\n\npublic class SpringGatewayPlugin implements AgentPlugin {\n\n    @Override\n    public String getNamespace() {\n        return ConfigConst.Namespace.SPRING_GATEWAY;\n    }\n\n    @Override\n    public String getDomain() {\n        return ConfigConst.OBSERVABILITY;\n    }\n\n}\n"
  },
  {
    "path": "plugins/spring-gateway/src/main/java/easeagent/plugin/spring/gateway/advice/AgentGlobalFilterAdvice.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring.gateway.advice;\n\nimport com.megaease.easeagent.plugin.CodeVersion;\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.Set;\n\npublic class AgentGlobalFilterAdvice implements Points {\n    @Override\n    public CodeVersion codeVersions() {\n        return CodeCons.VERSIONS;\n    }\n\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return ClassMatcher.builder()\n            .hasClassName(\"easeagent.plugin.spring.gateway.interceptor.initialize.AgentGlobalFilter\")\n            .build();\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.builder()\n            .named(\"filter\")\n            .build().toSet();\n    }\n}\n"
  },
  {
    "path": "plugins/spring-gateway/src/main/java/easeagent/plugin/spring/gateway/advice/CodeCons.java",
    "content": "package easeagent.plugin.spring.gateway.advice;\n\nimport com.megaease.easeagent.plugin.CodeVersion;\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\n\npublic class CodeCons {\n    public final static CodeVersion VERSIONS = CodeVersion.builder()\n        .key(ConfigConst.CodeVersion.KEY_SPRING_BOOT)\n        .add(Points.DEFAULT_VERSION)\n        .add(ConfigConst.CodeVersion.VERSION_SPRING_BOOT2).build();\n}\n"
  },
  {
    "path": "plugins/spring-gateway/src/main/java/easeagent/plugin/spring/gateway/advice/HttpHeadersFilterAdvice.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring.gateway.advice;\n\nimport com.megaease.easeagent.plugin.CodeVersion;\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.Set;\n\npublic class HttpHeadersFilterAdvice implements Points {\n    @Override\n    public CodeVersion codeVersions() {\n        return CodeCons.VERSIONS;\n    }\n\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return ClassMatcher.builder()\n            .hasClassName(\"org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter\")\n            .build();\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.multiBuilder()\n            .match(MethodMatcher.builder().named(\"filterRequest\")\n                .build())\n            .build();\n    }\n}\n"
  },
  {
    "path": "plugins/spring-gateway/src/main/java/easeagent/plugin/spring/gateway/advice/InitGlobalFilterAdvice.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring.gateway.advice;\n\nimport com.megaease.easeagent.plugin.CodeVersion;\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.Set;\n\npublic class InitGlobalFilterAdvice implements Points {\n    @Override\n    public CodeVersion codeVersions() {\n        return CodeCons.VERSIONS;\n    }\n\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return ClassMatcher.builder()\n            .hasClassName(\"org.springframework.cloud.gateway.config.GatewayAutoConfiguration\")\n            .build();\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.multiBuilder()\n            .match(MethodMatcher.builder()\n                .named(\"filteringWebHandler\")\n                .or().named(\"gatewayControllerEndpoint\")\n                .or().named(\"gatewayLegacyControllerEndpoint\")\n                .build())\n            .build();\n    }\n}\n"
  },
  {
    "path": "plugins/spring-gateway/src/main/java/easeagent/plugin/spring/gateway/interceptor/GatewayCons.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring.gateway.interceptor;\n\npublic interface GatewayCons {\n    String SPAN_KEY = GatewayCons.class.getName() + \".SPAN\";\n    String CHILD_SPAN_KEY = GatewayCons.class.getName() + \".CHILD_SPAN\";\n    String CLIENT_RECEIVE_CALLBACK_KEY = GatewayCons.class.getName() + \".CLIENT_RECEIVE_CALLBACK\";\n}\n"
  },
  {
    "path": "plugins/spring-gateway/src/main/java/easeagent/plugin/spring/gateway/interceptor/initialize/AgentGlobalFilter.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring.gateway.interceptor.initialize;\n\nimport org.springframework.cloud.gateway.filter.GatewayFilterChain;\nimport org.springframework.cloud.gateway.filter.GlobalFilter;\nimport org.springframework.web.server.ServerWebExchange;\nimport reactor.core.publisher.Mono;\n\npublic class AgentGlobalFilter implements GlobalFilter {\n    public AgentGlobalFilter() {\n    }\n\n    @Override\n    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {\n        return chain.filter(exchange);\n    }\n}\n"
  },
  {
    "path": "plugins/spring-gateway/src/main/java/easeagent/plugin/spring/gateway/interceptor/initialize/GatewayServerForwardedInterceptor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring.gateway.interceptor.initialize;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Cleaner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.interceptor.NonReentrantInterceptor;\nimport easeagent.plugin.spring.gateway.ForwardedPlugin;\nimport easeagent.plugin.spring.gateway.advice.AgentGlobalFilterAdvice;\nimport easeagent.plugin.spring.gateway.interceptor.tracing.FluxHttpServerRequest;\nimport org.springframework.web.server.ServerWebExchange;\n\n@AdviceTo(value = AgentGlobalFilterAdvice.class, plugin = ForwardedPlugin.class)\npublic class GatewayServerForwardedInterceptor implements NonReentrantInterceptor {\n    private static final Object FORWARDED_KEY = new Object();\n\n\n    @Override\n    public void doBefore(MethodInfo methodInfo, Context context) {\n        ServerWebExchange exchange = (ServerWebExchange) methodInfo.getArgs()[0];\n        FluxHttpServerRequest httpServerRequest = new FluxHttpServerRequest(exchange.getRequest());\n        Cleaner cleaner = context.importForwardedHeaders(httpServerRequest);\n        context.put(FORWARDED_KEY, cleaner);\n    }\n\n    @Override\n    public void doAfter(MethodInfo methodInfo, Context context) {\n        Cleaner cleaner = context.remove(FORWARDED_KEY);\n        if (cleaner != null) {\n            cleaner.close();\n        }\n    }\n\n    @Override\n    public String getType() {\n        return ConfigConst.PluginID.FORWARDED;\n    }\n\n    @Override\n    public int order() {\n        return Order.FORWARDED.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/spring-gateway/src/main/java/easeagent/plugin/spring/gateway/interceptor/initialize/GlobalFilterInterceptor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring.gateway.interceptor.initialize;\n\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport easeagent.plugin.spring.gateway.SpringGatewayPlugin;\nimport easeagent.plugin.spring.gateway.advice.InitGlobalFilterAdvice;\nimport org.springframework.cloud.gateway.filter.GlobalFilter;\n\nimport java.util.List;\n\n@AdviceTo(value = InitGlobalFilterAdvice.class, plugin = SpringGatewayPlugin.class)\npublic class GlobalFilterInterceptor implements Interceptor {\n\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public void before(MethodInfo methodInfo, Context context) {\n        List<GlobalFilter> list = null;\n        switch (methodInfo.getMethod()) {\n            case \"filteringWebHandler\":\n            case \"gatewayControllerEndpoint\":\n                list = (List<GlobalFilter>) methodInfo.getArgs()[0];\n                break;\n            case \"gatewayLegacyControllerEndpoint\":\n                list = (List<GlobalFilter>) methodInfo.getArgs()[1];\n                break;\n        }\n        if (list == null || hasAgentFilter(list)) {\n            return;\n        }\n        list.add(0, new AgentGlobalFilter());\n    }\n\n    private boolean hasAgentFilter(List<GlobalFilter> list) {\n        for (GlobalFilter globalFilter : list) {\n            if (globalFilter instanceof AgentGlobalFilter) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    @Override\n    public String getType() {\n        return Order.INIT.getName();\n    }\n\n    @Override\n    public int order() {\n        return Order.INIT.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/spring-gateway/src/main/java/easeagent/plugin/spring/gateway/interceptor/metric/GatewayMetricsInterceptor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring.gateway.interceptor.metric;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.context.AsyncContext;\nimport com.megaease.easeagent.plugin.api.metric.ServiceMetricRegistry;\nimport com.megaease.easeagent.plugin.api.metric.name.Tags;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.tools.metrics.ServerMetric;\nimport com.megaease.easeagent.plugin.utils.SystemClock;\nimport easeagent.plugin.spring.gateway.SpringGatewayPlugin;\nimport easeagent.plugin.spring.gateway.advice.AgentGlobalFilterAdvice;\nimport easeagent.plugin.spring.gateway.reactor.AgentMono;\nimport org.springframework.cloud.gateway.route.Route;\nimport org.springframework.cloud.gateway.support.ServerWebExchangeUtils;\nimport org.springframework.http.HttpMethod;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.web.server.ServerWebExchange;\nimport reactor.core.publisher.Mono;\n\nimport static easeagent.plugin.spring.gateway.interceptor.metric.TimeUtils.removeStartTime;\nimport static easeagent.plugin.spring.gateway.interceptor.metric.TimeUtils.startTime;\n\n@AdviceTo(value = AgentGlobalFilterAdvice.class, plugin = SpringGatewayPlugin.class)\npublic class GatewayMetricsInterceptor implements Interceptor {\n    private static Object START_TIME = new Object();\n    private static volatile ServerMetric SERVER_METRIC = null;\n\n    @Override\n    public void init(IPluginConfig config, String className, String methodName, String methodDescriptor) {\n        SERVER_METRIC = ServiceMetricRegistry.getOrCreate(config,\n            new Tags(\"application\", \"http-request\", \"url\"), ServerMetric.SERVICE_METRIC_SUPPLIER);\n    }\n\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n        startTime(context, START_TIME);\n        // context.put(START, SystemClock.now());\n    }\n\n\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public void after(MethodInfo methodInfo, Context context) {\n        try {\n            ServerWebExchange exchange = (ServerWebExchange) methodInfo.getArgs()[0];\n            if (!methodInfo.isSuccess()) {\n                String key = getKey(exchange);\n                Long start = startTime(context, START_TIME);\n                long end = System.currentTimeMillis();\n                SERVER_METRIC.collectMetric(key, 500, methodInfo.getThrowable(), start, end);\n                return;\n            }\n            // async\n            Mono<Void> mono = (Mono<Void>) methodInfo.getRetValue();\n            methodInfo.setRetValue(new AgentMono(mono, methodInfo, context.exportAsync(), this::finishCallback));\n        } finally {\n            removeStartTime(context, START_TIME);\n        }\n    }\n\n    void finishCallback(MethodInfo methodInfo, AsyncContext ctx) {\n        ServerWebExchange exchange = (ServerWebExchange) methodInfo.getArgs()[0];\n        String key = getKey(exchange);\n        HttpStatus statusCode = exchange.getResponse().getStatusCode();\n        int code = 0;\n        if (statusCode != null) {\n            code = statusCode.value();\n        }\n        SERVER_METRIC.collectMetric(key, code, methodInfo.getThrowable(),\n            ctx.get(START_TIME), SystemClock.now());\n    }\n\n    public static String getKey(ServerWebExchange exchange) {\n        HttpMethod httpMethod = exchange.getRequest().getMethod();\n        if (httpMethod == null) {\n            return \"\";\n        }\n        Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);\n        String key;\n        if (route != null && route.getUri() != null) {\n            key = httpMethod.name() + \" \" + route.getUri().toString();\n        } else {\n            key = httpMethod.name() + \" \" + exchange.getRequest().getURI();\n        }\n        return key;\n    }\n\n    @Override\n    public String getType() {\n        return Order.METRIC.getName();\n    }\n\n    @Override\n    public int order() {\n        return Order.METRIC.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/spring-gateway/src/main/java/easeagent/plugin/spring/gateway/interceptor/metric/TimeUtils.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring.gateway.interceptor.metric;\n\nimport com.megaease.easeagent.plugin.api.Context;\n\npublic class TimeUtils {\n    public static long startTime(Context context, Object key) {\n        Long start = context.get(key);\n        if (start == null) {\n            start = System.currentTimeMillis();\n            context.put(key, start);\n        }\n        return start;\n    }\n\n    public static Long removeStartTime(Context context, Object key) {\n        return context.remove(key);\n    }\n}\n"
  },
  {
    "path": "plugins/spring-gateway/src/main/java/easeagent/plugin/spring/gateway/interceptor/metric/log/GatewayAccessLogInterceptor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring.gateway.interceptor.metric.log;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.AsyncContext;\nimport com.megaease.easeagent.plugin.api.context.RequestContext;\nimport com.megaease.easeagent.plugin.api.logging.AccessLogInfo;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.tools.metrics.AccessLogServerInfo;\nimport com.megaease.easeagent.plugin.tools.metrics.HttpLog;\nimport easeagent.plugin.spring.gateway.AccessPlugin;\nimport easeagent.plugin.spring.gateway.advice.AgentGlobalFilterAdvice;\nimport easeagent.plugin.spring.gateway.interceptor.GatewayCons;\nimport easeagent.plugin.spring.gateway.reactor.AgentMono;\nimport org.springframework.web.server.ServerWebExchange;\nimport reactor.core.publisher.Mono;\n\nimport static easeagent.plugin.spring.gateway.interceptor.metric.TimeUtils.removeStartTime;\nimport static easeagent.plugin.spring.gateway.interceptor.metric.TimeUtils.startTime;\n\n@AdviceTo(value = AgentGlobalFilterAdvice.class, plugin = AccessPlugin.class)\npublic class GatewayAccessLogInterceptor implements Interceptor {\n    private static final Object START_TIME = new Object();\n    private final HttpLog httpLog = new HttpLog();\n\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n        ServerWebExchange exchange = (ServerWebExchange) methodInfo.getArgs()[0];\n        AccessLogServerInfo serverInfo = this.serverInfo(exchange);\n        Long beginTime = startTime(context, START_TIME);\n        AccessLogInfo accessLog = this.httpLog.prepare(getSystem(),\n            getServiceName(), beginTime, getSpan(exchange), serverInfo);\n        exchange.getAttributes().put(AccessLogInfo.class.getName(), accessLog);\n    }\n\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public void after(MethodInfo methodInfo, Context context) {\n        try {\n            // async\n            Mono<Void> mono = (Mono<Void>) methodInfo.getRetValue();\n            methodInfo.setRetValue(new AgentMono(mono, methodInfo, context.exportAsync(), this::finishCallback));\n        } finally {\n            removeStartTime(context, START_TIME);\n        }\n    }\n\n    Span getSpan(ServerWebExchange exchange) {\n        RequestContext pCtx = exchange.getAttribute(GatewayCons.SPAN_KEY);\n        if (pCtx == null) {\n            return null;\n        }\n        return pCtx.span();\n    }\n\n    private void finishCallback(MethodInfo methodInfo, AsyncContext ctx) {\n        ServerWebExchange exchange = (ServerWebExchange) methodInfo.getArgs()[0];\n        AccessLogInfo accessLog = exchange.getAttribute(AccessLogInfo.class.getName());\n        if (accessLog == null) {\n            return;\n        }\n        Long beginTime = ctx.get(START_TIME);\n        AccessLogServerInfo serverInfo = this.serverInfo(exchange);\n        this.httpLog.finish(accessLog, methodInfo.isSuccess(), beginTime, serverInfo);\n        EaseAgent.getAgentReport().report(accessLog);\n    }\n\n    AccessLogServerInfo serverInfo(ServerWebExchange exchange) {\n        SpringGatewayAccessLogServerInfo serverInfo = new SpringGatewayAccessLogServerInfo();\n        serverInfo.load(exchange);\n        return serverInfo;\n    }\n\n    String getSystem() {\n        return EaseAgent.getConfig(\"system\");\n    }\n\n    String getServiceName() {\n        return EaseAgent.getConfig(\"name\");\n    }\n\n    @Override\n    public String getType() {\n        return Order.LOG.getName();\n    }\n\n    @Override\n    public int order() {\n        return Order.LOG.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/spring-gateway/src/main/java/easeagent/plugin/spring/gateway/interceptor/metric/log/SpringGatewayAccessLogServerInfo.java",
    "content": " /*\n  * Copyright (c) 2021, MegaEase\n  * All rights reserved.\n  *\n  * Licensed under the Apache License, Version 2.0 (the \"License\");\n  * you may not use this file except in compliance with the License.\n  * You may obtain a copy of the License at\n  *\n  *     http://www.apache.org/licenses/LICENSE-2.0\n  *\n  * Unless required by applicable law or agreed to in writing, software\n  * distributed under the License is distributed on an \"AS IS\" BASIS,\n  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  * See the License for the specific language governing permissions and\n  * limitations under the License.\n  */\n\npackage easeagent.plugin.spring.gateway.interceptor.metric.log;\n\nimport com.megaease.easeagent.plugin.tools.metrics.AccessLogServerInfo;\nimport org.springframework.cloud.gateway.route.Route;\nimport org.springframework.cloud.gateway.support.ServerWebExchangeUtils;\nimport org.springframework.http.HttpMethod;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.web.server.ServerWebExchange;\n\nimport java.net.InetSocketAddress;\nimport java.util.Map;\nimport java.util.Optional;\n\npublic class SpringGatewayAccessLogServerInfo implements AccessLogServerInfo {\n    private ServerWebExchange exchange;\n\n    public void load(ServerWebExchange exchange) {\n        this.exchange = exchange;\n    }\n\n    @Override\n    public String getMethod() {\n        return exchange.getRequest().getMethodValue();\n    }\n\n    @Override\n    public String getHeader(String key) {\n        return exchange.getRequest().getHeaders().getFirst(key);\n    }\n\n    @Override\n    public String getRemoteAddr() {\n        InetSocketAddress remoteAddress = exchange.getRequest().getRemoteAddress();\n        return remoteAddress == null ? null : remoteAddress.getHostString();\n    }\n\n    @Override\n    public String getRequestURI() {\n        return exchange.getRequest().getURI().toString();\n    }\n\n    @Override\n    public int getResponseBufferSize() {\n        return 0;\n    }\n\n    @Override\n    public String getMatchURL() {\n        HttpMethod httpMethod = exchange.getRequest().getMethod();\n        if (httpMethod == null) {\n            return \"\";\n        }\n        Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);\n        if (route != null && route.getUri() != null) {\n            return httpMethod.name() + \" \" + route.getUri().toString();\n        }\n        return httpMethod.name() + \" \" + getRequestURI();\n    }\n\n    @Override\n    public Map<String, String> findHeaders() {\n        return exchange.getRequest().getHeaders().toSingleValueMap();\n    }\n\n    @Override\n    public Map<String, String> findQueries() {\n        return exchange.getRequest().getQueryParams().toSingleValueMap();\n    }\n\n    @Override\n    public String getStatusCode() {\n        HttpStatus rawStatusCode = exchange.getResponse().getStatusCode();\n        return Optional.ofNullable(rawStatusCode).map(e -> e.value() + \"\").orElse(\"0\");\n    }\n}\n\n"
  },
  {
    "path": "plugins/spring-gateway/src/main/java/easeagent/plugin/spring/gateway/interceptor/tracing/FluxHttpServerRequest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring.gateway.interceptor.tracing;\n\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.tools.trace.HttpRequest;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.http.server.reactive.ServerHttpRequest;\n\npublic class FluxHttpServerRequest implements HttpRequest {\n    private final ServerHttpRequest request;\n\n    public FluxHttpServerRequest(ServerHttpRequest request) {\n        this.request = request;\n    }\n\n    @Override\n    public Span.Kind kind() {\n        return Span.Kind.SERVER;\n    }\n\n    @Override\n    public String header(String name) {\n        HttpHeaders headers = this.request.getHeaders();\n        return headers.getFirst(name);\n    }\n\n    @Override\n    public boolean cacheScope() {\n        return false;\n    }\n\n    @Override\n    public void setHeader(String name, String value) {\n    }\n\n    @Override\n    public String method() {\n        return this.request.getMethodValue();\n    }\n\n    @Override\n    public String path() {\n        return this.request.getPath().value();\n    }\n\n    @Override\n    public String route() {\n        return null;\n    }\n\n    @Override\n    public String getRemoteAddr() {\n        if (this.request != null && this.request.getRemoteAddress() != null) {\n            return this.request.getRemoteAddress().getAddress().getHostAddress();\n        } else {\n            return \"Unknown\";\n        }\n    }\n\n    @Override\n    public int getRemotePort() {\n        if (this.request != null && this.request.getRemoteAddress() != null) {\n            return this.request.getRemoteAddress().getPort();\n        } else {\n            return 0;\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/spring-gateway/src/main/java/easeagent/plugin/spring/gateway/interceptor/tracing/FluxHttpServerResponse.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring.gateway.interceptor.tracing;\n\nimport com.megaease.easeagent.plugin.tools.trace.HttpResponse;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.http.server.reactive.ServerHttpResponse;\nimport org.springframework.web.reactive.HandlerMapping;\nimport org.springframework.web.server.ServerWebExchange;\nimport org.springframework.web.util.pattern.PathPattern;\n\npublic class FluxHttpServerResponse implements HttpResponse {\n    private final FluxHttpServerRequest request;\n    private final ServerHttpResponse response;\n    private final String route;\n    private final Throwable error;\n\n    public FluxHttpServerResponse(FluxHttpServerRequest request,\n                                  ServerHttpResponse response, String route, Throwable error) {\n        this.request = request;\n        this.response = response;\n        this.route = route;\n        this.error = error;\n    }\n\n    public FluxHttpServerResponse(ServerWebExchange exchange, Throwable error) {\n        this.request = new FluxHttpServerRequest(exchange.getRequest());\n        this.response = exchange.getResponse();\n\n        PathPattern bestPattern = exchange.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);\n        String route = null;\n        if (bestPattern != null) {\n            route = bestPattern.getPatternString();\n        }\n\n        this.route = route;\n        this.error = error;\n    }\n\n    @Override\n    public String header(String name) {\n        if (this.response == null) {\n            return null;\n        }\n        HttpHeaders hs = this.response.getHeaders();\n        return hs.getFirst(name);\n    }\n\n    @Override\n    public String method() {\n        return this.request.method();\n    }\n\n    @Override\n    public String route() {\n        return this.route;\n    }\n\n    @Override\n    public int statusCode() {\n        if (this.response != null && this.response.getStatusCode() != null) {\n            return this.response.getStatusCode().value();\n        } else {\n            return 0;\n        }\n    }\n\n    @Override\n    public Throwable maybeError() {\n        return this.error;\n    }\n}\n"
  },
  {
    "path": "plugins/spring-gateway/src/main/java/easeagent/plugin/spring/gateway/interceptor/tracing/GatewayServerTracingInterceptor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring.gateway.interceptor.tracing;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Cleaner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.AsyncContext;\nimport com.megaease.easeagent.plugin.api.context.RequestContext;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.tools.trace.HttpResponse;\nimport com.megaease.easeagent.plugin.tools.trace.HttpUtils;\nimport easeagent.plugin.spring.gateway.SpringGatewayPlugin;\nimport easeagent.plugin.spring.gateway.advice.AgentGlobalFilterAdvice;\nimport easeagent.plugin.spring.gateway.interceptor.GatewayCons;\nimport easeagent.plugin.spring.gateway.reactor.AgentMono;\nimport org.springframework.web.reactive.HandlerMapping;\nimport org.springframework.web.server.ServerWebExchange;\nimport org.springframework.web.util.pattern.PathPattern;\nimport reactor.core.publisher.Mono;\n\nimport java.util.function.BiConsumer;\n\n@AdviceTo(value = AgentGlobalFilterAdvice.class, plugin = SpringGatewayPlugin.class)\npublic class GatewayServerTracingInterceptor implements Interceptor {\n    static final String SPAN_CONTEXT_KEY = GatewayServerTracingInterceptor.class.getName() + \"-P-CTX\";\n\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n        ServerWebExchange exchange = (ServerWebExchange) methodInfo.getArgs()[0];\n        FluxHttpServerRequest httpServerRequest = new FluxHttpServerRequest(exchange.getRequest());\n        RequestContext pCtx = context.serverReceive(httpServerRequest);\n        HttpUtils.handleReceive(pCtx.span(), httpServerRequest);\n        context.put(SPAN_CONTEXT_KEY, pCtx);\n        context.put(FluxHttpServerRequest.class, httpServerRequest);\n        exchange.getAttributes().put(GatewayCons.SPAN_KEY, pCtx);\n    }\n\n    private void cleanContext(Context context) {\n        context.remove(FluxHttpServerRequest.class);\n        context.remove(SPAN_CONTEXT_KEY);\n    }\n\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public void after(MethodInfo methodInfo, Context context) {\n        RequestContext pCtx = context.get(SPAN_CONTEXT_KEY);\n        if (pCtx == null) {\n            return;\n        }\n        try {\n            if (!methodInfo.isSuccess()) {\n                pCtx.span().error(methodInfo.getThrowable());\n                pCtx.span().finish();\n                return;\n            }\n\n            // async\n            Mono<Void> mono = (Mono<Void>) methodInfo.getRetValue();\n            methodInfo.setRetValue(new AgentMono(mono, methodInfo, context.exportAsync(), this::finishCallback));\n        } finally {\n            cleanContext(context);\n            pCtx.scope().close();\n        }\n\n    }\n\n    void finishCallback(MethodInfo methodInfo, AsyncContext ctx) {\n        try (Cleaner ignored = ctx.importToCurrent()) {\n            RequestContext pCtx = EaseAgent.getContext().get(SPAN_CONTEXT_KEY);\n            ServerWebExchange exchange = (ServerWebExchange) methodInfo.getArgs()[0];\n            BiConsumer<ServerWebExchange, MethodInfo> consumer = exchange.getAttribute(GatewayCons.CLIENT_RECEIVE_CALLBACK_KEY);\n            if (consumer != null) {\n                consumer.accept(exchange, methodInfo);\n            }\n\n            FluxHttpServerRequest httpServerRequest = EaseAgent.getContext().get(FluxHttpServerRequest.class);\n            PathPattern bestPattern = exchange.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);\n            String route = null;\n            if (bestPattern != null) {\n                route = bestPattern.getPatternString();\n            }\n            HttpResponse response = new FluxHttpServerResponse(httpServerRequest,\n                exchange.getResponse(), route, methodInfo.getThrowable());\n            HttpUtils.finish(pCtx.span(), response);\n            exchange.getAttributes().remove(GatewayCons.SPAN_KEY);\n        }\n    }\n\n    @Override\n    public String getType() {\n        return Order.TRACING.getName();\n    }\n\n    @Override\n    public int order() {\n        return Order.TRACING.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/spring-gateway/src/main/java/easeagent/plugin/spring/gateway/interceptor/tracing/HttpHeadersFilterTracingInterceptor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring.gateway.interceptor.tracing;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.RequestContext;\nimport com.megaease.easeagent.plugin.api.trace.Scope;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.interceptor.NonReentrantInterceptor;\nimport com.megaease.easeagent.plugin.tools.trace.HttpUtils;\nimport easeagent.plugin.spring.gateway.SpringGatewayPlugin;\nimport easeagent.plugin.spring.gateway.advice.HttpHeadersFilterAdvice;\nimport easeagent.plugin.spring.gateway.interceptor.GatewayCons;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.http.server.reactive.ServerHttpRequest;\nimport org.springframework.web.server.ServerWebExchange;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.function.BiConsumer;\nimport java.util.function.Consumer;\n\n@AdviceTo(value = HttpHeadersFilterAdvice.class, plugin = SpringGatewayPlugin.class)\npublic class HttpHeadersFilterTracingInterceptor implements NonReentrantInterceptor {\n    // org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter\n    static final String CLIENT_HEADER_ATTR = HttpHeadersFilterTracingInterceptor.class.getName() + \".Headers\";\n\n    @Override\n    public void doAfter(MethodInfo methodInfo, Context context) {\n        ServerWebExchange exchange = (ServerWebExchange) methodInfo.getArgs()[1];\n        HttpHeaders retHttpHeaders = (HttpHeaders) methodInfo.getRetValue();\n        RequestContext pCtx = exchange.getAttribute(GatewayCons.SPAN_KEY);\n        if (pCtx == null) {\n            return;\n        }\n        FluxHttpServerRequest request = new HeaderFilterRequest(exchange.getRequest());\n\n        RequestContext pnCtx = context.clientRequest(request);\n        try (Scope ignored = pnCtx.scope()) {\n            pnCtx.span().start();\n            exchange.getAttributes().put(GatewayCons.CHILD_SPAN_KEY, pnCtx);\n            Map<String, String> map = getHeadersFromExchange(exchange);\n            map.putAll(retHttpHeaders.toSingleValueMap());\n            map.putAll(pnCtx.getHeaders());\n            HttpHeaders httpHeaders = new HttpHeaders();\n            httpHeaders.setAll(map);\n            methodInfo.setRetValue(httpHeaders);\n\n            BiConsumer<ServerWebExchange, MethodInfo> consumer = (serverWebExchange, info) -> {\n                RequestContext p = serverWebExchange.getAttribute(GatewayCons.CHILD_SPAN_KEY);\n                if (p == null) {\n                    return;\n                }\n                FluxHttpServerResponse response = new FluxHttpServerResponse(serverWebExchange, info.getThrowable());\n                HttpUtils.save(p.span(), response);\n                p.finish(response);\n            };\n            exchange.getAttributes().put(GatewayCons.CLIENT_RECEIVE_CALLBACK_KEY, consumer);\n        }\n    }\n\n    @Override\n    public int order() {\n        return Order.HIGH.getOrder();\n    }\n\n    private Map<String, String> getHeadersFromExchange(ServerWebExchange exchange) {\n        Map<String, String> headers = exchange.getAttribute(CLIENT_HEADER_ATTR);\n        if (headers == null) {\n            headers = new HashMap<>();\n            exchange.getAttributes().put(CLIENT_HEADER_ATTR, headers);\n        }\n        return headers;\n    }\n\n    static class HeaderFilterRequest extends FluxHttpServerRequest {\n        public HeaderFilterRequest(ServerHttpRequest request) {\n            super(request);\n        }\n\n\n        @Override\n        public Span.Kind kind() {\n            return Span.Kind.CLIENT;\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/spring-gateway/src/main/java/easeagent/plugin/spring/gateway/reactor/AgentCoreSubscriber.java",
    "content": " /*\n  * Copyright (c) 2021, MegaEase\n  * All rights reserved.\n  *\n  * Licensed under the Apache License, Version 2.0 (the \"License\");\n  * you may not use this file except in compliance with the License.\n  * You may obtain a copy of the License at\n  *\n  *     http://www.apache.org/licenses/LICENSE-2.0\n  *\n  * Unless required by applicable law or agreed to in writing, software\n  * distributed under the License is distributed on an \"AS IS\" BASIS,\n  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  * See the License for the specific language governing permissions and\n  * limitations under the License.\n  */\n\n package easeagent.plugin.spring.gateway.reactor;\n\n import com.megaease.easeagent.plugin.api.context.AsyncContext;\n import com.megaease.easeagent.plugin.interceptor.MethodInfo;\n import org.reactivestreams.Subscription;\n import reactor.core.CoreSubscriber;\n\n import javax.annotation.Nonnull;\n import java.util.function.BiConsumer;\n\n public class AgentCoreSubscriber implements CoreSubscriber<Void> {\n\n     private final CoreSubscriber<Void> actual;\n     private final MethodInfo methodInfo;\n     private final AsyncContext asyncContext;\n     private final BiConsumer<MethodInfo, AsyncContext> finish;\n\n     @SuppressWarnings(\"unchecked\")\n     public AgentCoreSubscriber(CoreSubscriber<? super Void> actual,\n                                MethodInfo methodInfo,\n                                // Object context,\n                                AsyncContext async,\n                                BiConsumer<MethodInfo, AsyncContext> finish) {\n         this.actual = (CoreSubscriber<Void>) actual;\n         this.methodInfo = methodInfo;\n         this.finish = finish;\n         this.asyncContext = async;\n     }\n\n     @Nonnull\n     @Override\n     public reactor.util.context.Context currentContext() {\n         return actual.currentContext();\n     }\n\n     @Override\n     public void onSubscribe(@Nonnull Subscription s) {\n         actual.onSubscribe(s);\n     }\n\n     @Override\n     public void onNext(Void t) {\n         actual.onNext(t);\n     }\n\n     @Override\n     public void onError(Throwable t) {\n         actual.onError(t);\n         methodInfo.setThrowable(t);\n         finish.accept(this.methodInfo, asyncContext);\n     }\n\n     @Override\n     public void onComplete() {\n         actual.onComplete();\n         finish.accept(this.methodInfo, asyncContext);\n     }\n }\n\n"
  },
  {
    "path": "plugins/spring-gateway/src/main/java/easeagent/plugin/spring/gateway/reactor/AgentMono.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring.gateway.reactor;\n\nimport com.megaease.easeagent.plugin.api.Cleaner;\nimport com.megaease.easeagent.plugin.api.context.AsyncContext;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport reactor.core.CoreSubscriber;\nimport reactor.core.publisher.Mono;\n\nimport javax.annotation.Nonnull;\nimport java.util.function.BiConsumer;\n\npublic class AgentMono extends Mono<Void> {\n    private final Mono<Void> source;\n    private final MethodInfo methodInfo;\n    private final AsyncContext asyncContext;\n    private final BiConsumer<MethodInfo, AsyncContext> finish;\n\n    public AgentMono(Mono<Void> mono, MethodInfo methodInfo,\n                     AsyncContext async,\n                     BiConsumer<MethodInfo, AsyncContext> consumer) {\n        this.source = mono;\n        this.methodInfo = methodInfo;\n        this.finish = consumer;\n        this.asyncContext = async;\n    }\n\n    @Override\n    public void subscribe(@Nonnull CoreSubscriber<? super Void> actual) {\n        try (Cleaner ignored = asyncContext.importToCurrent()) {\n            this.source.subscribe(new AgentCoreSubscriber(actual, methodInfo,\n                asyncContext, finish));\n        }\n    }\n\n    public MethodInfo getMethodInfo() {\n        return methodInfo;\n    }\n\n    public AsyncContext getAsyncContext() {\n        return asyncContext;\n    }\n\n    public BiConsumer<MethodInfo, AsyncContext> getFinish() {\n        return finish;\n    }\n}\n"
  },
  {
    "path": "plugins/spring-gateway/src/test/java/easeagent/plugin/spring/gateway/TestConst.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring.gateway;\n\npublic class TestConst {\n    public static final String FORWARDED_NAME = \"X-Forwarded-For\";\n    public static final String FORWARDED_VALUE = \"testForwarded\";\n}\n"
  },
  {
    "path": "plugins/spring-gateway/src/test/java/easeagent/plugin/spring/gateway/TestServerWebExchangeUtils.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring.gateway;\n\nimport org.springframework.http.HttpStatus;\nimport org.springframework.mock.http.server.reactive.MockServerHttpRequest;\nimport org.springframework.mock.web.server.MockServerWebExchange;\nimport org.springframework.web.reactive.HandlerMapping;\nimport org.springframework.web.util.pattern.PathPattern;\nimport org.springframework.web.util.pattern.PathPatternParser;\n\nimport java.net.InetSocketAddress;\n\npublic class TestServerWebExchangeUtils {\n\n    public static final MockServerHttpRequest.BaseBuilder<?> builder() {\n        return MockServerHttpRequest.get(\"http://192.168.0.12:8080/test\", \"a=b\")\n            .header(TestConst.FORWARDED_NAME, TestConst.FORWARDED_VALUE)\n            .queryParam(\"a\", \"b\");\n    }\n\n    public static final MockServerWebExchange build(MockServerHttpRequest.BaseBuilder<?> requestBuilder) {\n        MockServerHttpRequest mockServerHttpRequest = requestBuilder.build();\n        return MockServerWebExchange.builder(mockServerHttpRequest).build();\n    }\n\n\n    public static final MockServerWebExchange mockServerWebExchange() {\n        MockServerHttpRequest mockServerHttpRequest = TestServerWebExchangeUtils.builder()\n            .remoteAddress(new InetSocketAddress(\"192.168.0.12\", 8080)).build();\n        MockServerWebExchange mockServerWebExchange = MockServerWebExchange.builder(mockServerHttpRequest).build();\n        mockServerWebExchange.getResponse().setStatusCode(HttpStatus.OK);\n        PathPatternParser parser = new PathPatternParser();\n        PathPattern pathPattern = parser.parse(\"/test\");\n        mockServerWebExchange.getAttributes().put(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE, pathPattern);\n        return mockServerWebExchange;\n    }\n}\n"
  },
  {
    "path": "plugins/spring-gateway/src/test/java/easeagent/plugin/spring/gateway/interceptor/initialize/AgentGlobalFilterTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring.gateway.interceptor.initialize;\n\nimport org.junit.Test;\nimport org.springframework.mock.web.server.MockServerWebExchange;\n\nimport java.util.concurrent.atomic.AtomicBoolean;\n\nimport static easeagent.plugin.spring.gateway.TestServerWebExchangeUtils.mockServerWebExchange;\nimport static org.junit.Assert.assertSame;\nimport static org.junit.Assert.assertTrue;\n\npublic class AgentGlobalFilterTest {\n\n    @Test\n    public void filter() {\n        AgentGlobalFilter agentGlobalFilter = new AgentGlobalFilter();\n        AtomicBoolean ran = new AtomicBoolean(true);\n        MockServerWebExchange mockServerWebExchange = mockServerWebExchange();\n        agentGlobalFilter.filter(mockServerWebExchange, exchange -> {\n            assertSame(mockServerWebExchange, exchange);\n            ran.set(true);\n            return null;\n        });\n        assertTrue(ran.get());\n    }\n\n\n}\n"
  },
  {
    "path": "plugins/spring-gateway/src/test/java/easeagent/plugin/spring/gateway/interceptor/initialize/GatewayServerForwardedInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring.gateway.interceptor.initialize;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport easeagent.plugin.spring.gateway.TestConst;\nimport easeagent.plugin.spring.gateway.TestServerWebExchangeUtils;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.mock.web.server.MockServerWebExchange;\n\nimport static easeagent.plugin.spring.gateway.TestServerWebExchangeUtils.builder;\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNull;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class GatewayServerForwardedInterceptorTest {\n\n    @Test\n    public void doBefore() {\n        GatewayServerForwardedInterceptor interceptor = new GatewayServerForwardedInterceptor();\n        MockServerWebExchange mockServerWebExchange = TestServerWebExchangeUtils.build(builder().header(TestConst.FORWARDED_NAME, TestConst.FORWARDED_VALUE));\n        Context context = EaseAgent.getContext();\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{mockServerWebExchange}).build();\n        interceptor.doBefore(methodInfo, context);\n        assertEquals(TestConst.FORWARDED_VALUE, context.get(TestConst.FORWARDED_NAME));\n        interceptor.doAfter(methodInfo, context);\n        assertNull(context.get(TestConst.FORWARDED_NAME));\n    }\n\n    @Test\n    public void doAfter() {\n        doBefore();\n    }\n\n    @Test\n    public void getType() {\n        GatewayServerForwardedInterceptor interceptor = new GatewayServerForwardedInterceptor();\n        assertEquals(ConfigConst.PluginID.FORWARDED, interceptor.getType());\n    }\n}\n"
  },
  {
    "path": "plugins/spring-gateway/src/test/java/easeagent/plugin/spring/gateway/interceptor/initialize/GlobalFilterInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring.gateway.interceptor.initialize;\n\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport org.junit.Test;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport static org.junit.Assert.*;\n\npublic class GlobalFilterInterceptorTest {\n\n    @Test\n    public void before() {\n        GlobalFilterInterceptor interceptor = new GlobalFilterInterceptor();\n        List arg = new ArrayList();\n        MethodInfo methodInfo = MethodInfo.builder().method(\"filteringWebHandler\").args(new Object[]{arg}).build();\n        interceptor.before(methodInfo, null);\n        assertEquals(1, arg.size());\n        assertEquals(1, arg.size());\n        arg.clear();\n        methodInfo = MethodInfo.builder().method(\"gatewayControllerEndpoint\").args(new Object[]{arg}).build();\n        interceptor.before(methodInfo, null);\n        assertEquals(1, arg.size());\n        arg.clear();\n        methodInfo = MethodInfo.builder().method(\"gatewayLegacyControllerEndpoint\").args(new Object[]{null, arg}).build();\n        interceptor.before(methodInfo, null);\n        assertEquals(1, arg.size());\n\n    }\n\n    @Test\n    public void getType() {\n        GlobalFilterInterceptor interceptor = new GlobalFilterInterceptor();\n        assertEquals(ConfigConst.PluginID.INIT, interceptor.getType());\n    }\n\n    @Test\n    public void order() {\n        GlobalFilterInterceptor interceptor = new GlobalFilterInterceptor();\n        assertEquals(Order.INIT.getOrder(), interceptor.order());\n    }\n}\n"
  },
  {
    "path": "plugins/spring-gateway/src/test/java/easeagent/plugin/spring/gateway/interceptor/metric/GatewayMetricsInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring.gateway.interceptor.metric;\n\nimport com.megaease.easeagent.mock.plugin.api.MockEaseAgent;\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.mock.plugin.api.utils.InterceptorTestUtils;\nimport com.megaease.easeagent.mock.plugin.api.utils.TagVerifier;\nimport com.megaease.easeagent.mock.report.impl.LastJsonReporter;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.api.metric.name.MetricField;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport easeagent.plugin.spring.gateway.SpringGatewayPlugin;\nimport easeagent.plugin.spring.gateway.TestServerWebExchangeUtils;\nimport easeagent.plugin.spring.gateway.reactor.AgentMono;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.cloud.gateway.support.ServerWebExchangeUtils;\nimport org.springframework.http.server.reactive.ServerHttpRequest;\nimport org.springframework.mock.web.server.MockServerWebExchange;\nimport org.springframework.web.server.ServerWebExchange;\n\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.util.Map;\n\nimport static org.junit.Assert.*;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class GatewayMetricsInterceptorTest {\n    private Object startTime = AgentFieldReflectAccessor.getStaticFieldValue(GatewayMetricsInterceptor.class, \"START_TIME\");\n\n    @Test\n    public void init() {\n        GatewayMetricsInterceptor interceptor = new GatewayMetricsInterceptor();\n        InterceptorTestUtils.init(interceptor, new SpringGatewayPlugin());\n        assertNotNull(AgentFieldReflectAccessor.getStaticFieldValue(GatewayMetricsInterceptor.class, \"SERVER_METRIC\"));\n    }\n\n    @Test\n    public void before() {\n        GatewayMetricsInterceptor interceptor = new GatewayMetricsInterceptor();\n        Context context = EaseAgent.getContext();\n        interceptor.before(null, context);\n        assertNotNull(context.get(startTime));\n    }\n\n    public Map<String, Object> getMetric(LastJsonReporter lastJsonReporter) {\n        return lastJsonReporter.flushAndOnlyOne();\n    }\n\n    @Test\n    public void after() throws InterruptedException {\n        GatewayMetricsInterceptor interceptor = new GatewayMetricsInterceptor();\n        InterceptorTestUtils.init(interceptor, new SpringGatewayPlugin());\n        Context context = EaseAgent.getContext();\n        interceptor.before(null, context);\n        MockServerWebExchange mockServerWebExchange = TestServerWebExchangeUtils.mockServerWebExchange();\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{mockServerWebExchange}).build();\n        methodInfo.throwable(new RuntimeException(\"test error\"));\n        interceptor.after(methodInfo, context);\n        assertNull(methodInfo.getRetValue());\n\n        TagVerifier tagVerifier = new TagVerifier()\n            .add(\"category\", \"application\")\n            .add(\"type\", \"http-request\")\n            .add(\"url\", GatewayMetricsInterceptor.getKey(mockServerWebExchange));\n        LastJsonReporter lastJsonReporter = MockEaseAgent.lastMetricJsonReporter(tagVerifier::verifyAnd);\n        Map<String, Object> metric = getMetric(lastJsonReporter);\n        assertEquals(1, metric.get(MetricField.EXECUTION_COUNT.getField()));\n        assertEquals(1, metric.get(MetricField.EXECUTION_ERROR_COUNT.getField()));\n\n        methodInfo = MethodInfo.builder().args(new Object[]{mockServerWebExchange}).build();\n        interceptor.before(null, context);\n        interceptor.after(methodInfo, context);\n        assertTrue(methodInfo.getRetValue() instanceof AgentMono);\n        final AgentMono agentMono2 = (AgentMono) methodInfo.getRetValue();\n\n        Thread thread = new Thread(() -> agentMono2.getFinish().accept(agentMono2.getMethodInfo(), agentMono2.getAsyncContext()));\n        thread.start();\n        thread.join();\n\n        lastJsonReporter.clean();\n        metric = getMetric(lastJsonReporter);\n        assertEquals(2, metric.get(MetricField.EXECUTION_COUNT.getField()));\n        assertEquals(1, metric.get(MetricField.EXECUTION_ERROR_COUNT.getField()));\n\n    }\n\n    @Test\n    public void finishCallback() {\n    }\n\n    @Test\n    public void getKey() throws URISyntaxException {\n        ServerWebExchange webExchange = mock(ServerWebExchange.class);\n        when(webExchange.getRequest()).thenReturn(mock(ServerHttpRequest.class));\n        assertEquals(\"\", GatewayMetricsInterceptor.getKey(webExchange));\n\n        MockServerWebExchange exchange = TestServerWebExchangeUtils.mockServerWebExchange();\n        assertEquals(\"GET http://192.168.0.12:8080/test?a=b\", GatewayMetricsInterceptor.getKey(exchange));\n\n        String url = \"http://loca:8080/test\";\n        exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR, new MockRouteBuilder().uri(new URI(url)).id(\"t\").build());\n        assertEquals(\"GET \" + url, GatewayMetricsInterceptor.getKey(exchange));\n    }\n\n    @Test\n    public void getType() {\n        GatewayMetricsInterceptor interceptor = new GatewayMetricsInterceptor();\n        assertEquals(ConfigConst.PluginID.METRIC, interceptor.getType());\n    }\n\n    @Test\n    public void order() {\n        GatewayMetricsInterceptor interceptor = new GatewayMetricsInterceptor();\n        assertEquals(Order.METRIC.getOrder(), interceptor.order());\n    }\n}\n"
  },
  {
    "path": "plugins/spring-gateway/src/test/java/easeagent/plugin/spring/gateway/interceptor/metric/MockRouteBuilder.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring.gateway.interceptor.metric;\n\nimport org.springframework.cloud.gateway.route.Route;\n\npublic class MockRouteBuilder  extends Route.Builder  {\n    public MockRouteBuilder() {\n        predicate = serverWebExchange -> true;\n    }\n}\n"
  },
  {
    "path": "plugins/spring-gateway/src/test/java/easeagent/plugin/spring/gateway/interceptor/metric/TimeUtilsTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring.gateway.interceptor.metric;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static org.junit.Assert.*;\n\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class TimeUtilsTest {\n\n    @Test\n    public void startTime() throws InterruptedException {\n        Object key = new Object();\n        long startTime = TimeUtils.startTime(EaseAgent.getContext(), key);\n        Thread.sleep(10);\n        assertEquals(startTime, TimeUtils.startTime(EaseAgent.getContext(), key));\n        Object key2 = new Object();\n        assertNotEquals(startTime, TimeUtils.startTime(EaseAgent.getContext(), key2));\n    }\n\n    @Test\n    public void removeStartTime() throws InterruptedException {\n        Object key = new Object();\n        long startTime = TimeUtils.startTime(EaseAgent.getContext(), key);\n        Long startObj = TimeUtils.removeStartTime(EaseAgent.getContext(), key);\n        assertNotNull(startObj);\n        assertEquals(startTime, (long) startObj);\n        Thread.sleep(10);\n        assertNotEquals(startTime, TimeUtils.startTime(EaseAgent.getContext(), key));\n    }\n}\n"
  },
  {
    "path": "plugins/spring-gateway/src/test/java/easeagent/plugin/spring/gateway/interceptor/metric/log/GatewayAccessLogInfoInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring.gateway.interceptor.metric.log;\n\nimport com.megaease.easeagent.mock.plugin.api.MockEaseAgent;\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.mock.plugin.api.utils.InterceptorTestUtils;\nimport com.megaease.easeagent.mock.plugin.api.utils.TagVerifier;\nimport com.megaease.easeagent.mock.report.MockReport;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.api.context.RequestContext;\nimport com.megaease.easeagent.plugin.api.trace.Scope;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.api.logging.AccessLogInfo;\nimport com.megaease.easeagent.plugin.utils.common.HostAddress;\nimport easeagent.plugin.spring.gateway.AccessPlugin;\nimport easeagent.plugin.spring.gateway.TestConst;\nimport easeagent.plugin.spring.gateway.TestServerWebExchangeUtils;\nimport easeagent.plugin.spring.gateway.interceptor.metric.TimeUtils;\nimport easeagent.plugin.spring.gateway.interceptor.tracing.GatewayServerTracingInterceptorTest;\nimport easeagent.plugin.spring.gateway.reactor.AgentMono;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.mock.web.server.MockServerWebExchange;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class GatewayAccessLogInfoInterceptorTest {\n    private Object startTime = AgentFieldReflectAccessor.getStaticFieldValue(GatewayAccessLogInterceptor.class, \"START_TIME\");\n\n    @Test\n    public void before() {\n        GatewayAccessLogInterceptor interceptor = new GatewayAccessLogInterceptor();\n        Context context = EaseAgent.getContext();\n        MockServerWebExchange mockServerWebExchange = TestServerWebExchangeUtils.mockServerWebExchange();\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{mockServerWebExchange}).build();\n        interceptor.before(methodInfo, context);\n        AccessLogInfo accessLog = (AccessLogInfo) mockServerWebExchange.getAttributes().get(AccessLogInfo.class.getName());\n        assertNotNull(accessLog);\n        verify(accessLog, TimeUtils.startTime(context, startTime));\n        assertNull(accessLog.getTraceId());\n        assertNull(accessLog.getSpanId());\n        assertNull(accessLog.getParentSpanId());\n\n        mockServerWebExchange = TestServerWebExchangeUtils.mockServerWebExchange();\n        methodInfo = MethodInfo.builder().args(new Object[]{mockServerWebExchange}).build();\n        interceptor.before(methodInfo, context);\n        RequestContext requestContext = GatewayServerTracingInterceptorTest.beforeGatewayServerTracing(mockServerWebExchange);\n        Span span = requestContext.span();\n        try (Scope ignored = requestContext.scope()) {\n            interceptor.before(methodInfo, context);\n            accessLog = (AccessLogInfo) mockServerWebExchange.getAttributes().get(AccessLogInfo.class.getName());\n            assertNotNull(accessLog);\n            verify(accessLog, TimeUtils.startTime(context, startTime));\n            assertEquals(span.traceIdString(), accessLog.getTraceId());\n            assertEquals(span.spanIdString(), accessLog.getSpanId());\n            assertEquals(span.parentIdString(), accessLog.getParentSpanId());\n        }\n        span.finish();\n    }\n\n\n    public void verify(AccessLogInfo accessLog, long startTime) {\n        assertEquals(\"test-gateway-system\", accessLog.getSystem());\n        assertEquals(\"test-gateway-service\", accessLog.getService());\n        assertEquals(HostAddress.localhost(), accessLog.getHostName());\n        assertEquals(HostAddress.getHostIpv4(), accessLog.getHostIpv4());\n        assertEquals(\"GET http://192.168.0.12:8080/test?a=b\", accessLog.getUrl());\n        assertEquals(\"GET\", accessLog.getMethod());\n        assertEquals(TestConst.FORWARDED_VALUE, accessLog.getHeaders().get(TestConst.FORWARDED_NAME));\n        assertEquals(startTime, accessLog.getBeginTime());\n        assertEquals(\"b\", accessLog.getQueries().get(\"a\"));\n        assertEquals(TestConst.FORWARDED_VALUE, accessLog.getClientIP());\n        assertTrue(accessLog.getBeginCpuTime() > 0);\n    }\n\n    @Test\n    public void after() throws InterruptedException {\n        EaseAgent.agentReport = MockReport.getAgentReport();\n        GatewayAccessLogInterceptor interceptor = new GatewayAccessLogInterceptor();\n        InterceptorTestUtils.init(interceptor, new AccessPlugin());\n        Context context = EaseAgent.getContext();\n        MockServerWebExchange mockServerWebExchange = TestServerWebExchangeUtils.mockServerWebExchange();\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{mockServerWebExchange}).build();\n        interceptor.before(methodInfo, context);\n        Long start = context.get(startTime);\n        assertNotNull(start);\n        interceptor.after(methodInfo, context);\n        assertNull(context.get(startTime));\n        assertTrue(methodInfo.getRetValue() instanceof AgentMono);\n        AgentMono agentMono = (AgentMono) methodInfo.getRetValue();\n        TagVerifier tagVerifier = new TagVerifier().add(\"type\", \"access-log\").add(\"system\", \"test-gateway-system\");\n        // LastJsonReporter lastJsonReporter = MockEaseAgent.lastMetricJsonReporter(tagVerifier::verifyAnd);\n        Thread thread = new Thread(() -> agentMono.getFinish().accept(agentMono.getMethodInfo(), agentMono.getAsyncContext()));\n        thread.start();\n        thread.join();\n        AccessLogInfo accessLog = MockEaseAgent.getLastLog();\n        verify(accessLog, start);\n    }\n\n    @Test\n    public void serverInfo() {\n        MockServerWebExchange exchange = TestServerWebExchangeUtils.mockServerWebExchange();\n        GatewayAccessLogInterceptor interceptor = new GatewayAccessLogInterceptor();\n        assertNotNull(interceptor.serverInfo(exchange));\n    }\n\n    @Test\n    public void getSystem() {\n        GatewayAccessLogInterceptor interceptor = new GatewayAccessLogInterceptor();\n        assertEquals(\"test-gateway-system\", interceptor.getSystem());\n    }\n\n    @Test\n    public void getServiceName() {\n        GatewayAccessLogInterceptor interceptor = new GatewayAccessLogInterceptor();\n        assertEquals(\"test-gateway-service\", interceptor.getServiceName());\n    }\n\n    @Test\n    public void getType() {\n        GatewayAccessLogInterceptor interceptor = new GatewayAccessLogInterceptor();\n        assertEquals(ConfigConst.PluginID.LOG, interceptor.getType());\n    }\n\n    @Test\n    public void order() {\n        GatewayAccessLogInterceptor interceptor = new GatewayAccessLogInterceptor();\n        assertEquals(Order.LOG.getOrder(), interceptor.order());\n    }\n}\n"
  },
  {
    "path": "plugins/spring-gateway/src/test/java/easeagent/plugin/spring/gateway/interceptor/metric/log/SpringGatewayAccessLogInfoServerInfoTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring.gateway.interceptor.metric.log;\n\nimport easeagent.plugin.spring.gateway.TestServerWebExchangeUtils;\nimport easeagent.plugin.spring.gateway.interceptor.metric.MockRouteBuilder;\nimport org.junit.Test;\nimport org.springframework.cloud.gateway.route.Route;\nimport org.springframework.cloud.gateway.support.ServerWebExchangeUtils;\nimport org.springframework.mock.http.server.reactive.MockServerHttpRequest;\nimport org.springframework.mock.web.server.MockServerWebExchange;\nimport org.springframework.web.server.ServerWebExchange;\n\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.util.Map;\n\nimport static org.junit.Assert.*;\n\npublic class SpringGatewayAccessLogInfoServerInfoTest {\n\n    @Test\n    public void load() {\n        getMethod();\n    }\n\n    @Test\n    public void getMethod() {\n        SpringGatewayAccessLogServerInfo springGatewayAccessLogServerInfo = new SpringGatewayAccessLogServerInfo();\n        springGatewayAccessLogServerInfo.load(TestServerWebExchangeUtils.mockServerWebExchange());\n        assertEquals(\"GET\", springGatewayAccessLogServerInfo.getMethod());\n    }\n\n    @Test\n    public void getHeader() {\n        String key = \"testKey\";\n        String value = \"testValue\";\n        MockServerHttpRequest.BaseBuilder<?> baseBuilder = TestServerWebExchangeUtils.builder().header(key, value);\n        SpringGatewayAccessLogServerInfo springGatewayAccessLogServerInfo = new SpringGatewayAccessLogServerInfo();\n        springGatewayAccessLogServerInfo.load(TestServerWebExchangeUtils.build(baseBuilder));\n        assertEquals(value, springGatewayAccessLogServerInfo.getHeader(key));\n    }\n\n    @Test\n    public void getRemoteAddr() {\n        SpringGatewayAccessLogServerInfo springGatewayAccessLogServerInfo = new SpringGatewayAccessLogServerInfo();\n        springGatewayAccessLogServerInfo.load(TestServerWebExchangeUtils.mockServerWebExchange());\n        assertEquals(\"192.168.0.12\", springGatewayAccessLogServerInfo.getRemoteAddr());\n    }\n\n    @Test\n    public void getRequestURI() {\n        SpringGatewayAccessLogServerInfo springGatewayAccessLogServerInfo = new SpringGatewayAccessLogServerInfo();\n        springGatewayAccessLogServerInfo.load(TestServerWebExchangeUtils.mockServerWebExchange());\n        assertEquals(\"http://192.168.0.12:8080/test?a=b\", springGatewayAccessLogServerInfo.getRequestURI());\n    }\n\n    @Test\n    public void getResponseBufferSize() {\n        SpringGatewayAccessLogServerInfo springGatewayAccessLogServerInfo = new SpringGatewayAccessLogServerInfo();\n        assertEquals(0, springGatewayAccessLogServerInfo.getResponseBufferSize());\n    }\n\n\n    @Test\n    public void getMatchURL() throws URISyntaxException {\n        SpringGatewayAccessLogServerInfo springGatewayAccessLogServerInfo = new SpringGatewayAccessLogServerInfo();\n        MockServerWebExchange exchange = TestServerWebExchangeUtils.mockServerWebExchange();\n        springGatewayAccessLogServerInfo.load(exchange);\n        assertEquals(\"GET http://192.168.0.12:8080/test?a=b\", springGatewayAccessLogServerInfo.getMatchURL());\n        String url = \"http://192.168.0.12:8080/\";\n        exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR, new MockRouteBuilder().uri(new URI(url)).id(\"t\").build());\n        assertEquals(\"GET \" + url, springGatewayAccessLogServerInfo.getMatchURL());\n    }\n\n    @Test\n    public void findHeaders() {\n        String key = \"testKey\";\n        String value = \"testValue\";\n        MockServerHttpRequest.BaseBuilder<?> baseBuilder = TestServerWebExchangeUtils.builder().header(key, value);\n        SpringGatewayAccessLogServerInfo springGatewayAccessLogServerInfo = new SpringGatewayAccessLogServerInfo();\n        springGatewayAccessLogServerInfo.load(TestServerWebExchangeUtils.build(baseBuilder));\n        Map<String, String> headers = springGatewayAccessLogServerInfo.findHeaders();\n        assertEquals(value, headers.get(key));\n    }\n\n    @Test\n    public void findQueries() {\n        SpringGatewayAccessLogServerInfo springGatewayAccessLogServerInfo = new SpringGatewayAccessLogServerInfo();\n        springGatewayAccessLogServerInfo.load(TestServerWebExchangeUtils.mockServerWebExchange());\n        Map<String, String> queries = springGatewayAccessLogServerInfo.findQueries();\n        assertEquals(1, queries.size());\n        assertEquals(\"b\", queries.get(\"a\"));\n    }\n\n    @Test\n    public void getStatusCode() {\n        SpringGatewayAccessLogServerInfo springGatewayAccessLogServerInfo = new SpringGatewayAccessLogServerInfo();\n        springGatewayAccessLogServerInfo.load(TestServerWebExchangeUtils.mockServerWebExchange());\n        assertEquals(\"200\", springGatewayAccessLogServerInfo.getStatusCode());\n    }\n\n}\n"
  },
  {
    "path": "plugins/spring-gateway/src/test/java/easeagent/plugin/spring/gateway/interceptor/tracing/FluxHttpServerRequestTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring.gateway.interceptor.tracing;\n\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport easeagent.plugin.spring.gateway.TestServerWebExchangeUtils;\nimport org.junit.Test;\n\nimport java.net.InetSocketAddress;\n\nimport static org.junit.Assert.*;\n\npublic class FluxHttpServerRequestTest {\n\n    @Test\n    public void kind() {\n        FluxHttpServerRequest fluxHttpServerRequest = new FluxHttpServerRequest(TestServerWebExchangeUtils.builder().build());\n        assertEquals(Span.Kind.SERVER, fluxHttpServerRequest.kind());\n    }\n\n    @Test\n    public void header() {\n        String key = \"testKey\";\n        String value = \"testValue\";\n        FluxHttpServerRequest fluxHttpServerRequest = new FluxHttpServerRequest(TestServerWebExchangeUtils.builder()\n            .header(key, value).build());\n        assertEquals(value, fluxHttpServerRequest.header(key));\n    }\n\n    @Test\n    public void cacheScope() {\n        FluxHttpServerRequest fluxHttpServerRequest = new FluxHttpServerRequest(TestServerWebExchangeUtils.builder().build());\n        assertFalse(fluxHttpServerRequest.cacheScope());\n    }\n\n    @Test\n    public void setHeader() {\n        String key = \"testKey\";\n        String value = \"testValue\";\n        FluxHttpServerRequest fluxHttpServerRequest = new FluxHttpServerRequest(TestServerWebExchangeUtils.builder().build());\n        fluxHttpServerRequest.setHeader(key, value);\n        assertNull(fluxHttpServerRequest.header(key));\n    }\n\n    @Test\n    public void method() {\n        FluxHttpServerRequest fluxHttpServerRequest = new FluxHttpServerRequest(TestServerWebExchangeUtils.builder().build());\n        assertEquals(\"GET\", fluxHttpServerRequest.method());\n    }\n\n    @Test\n    public void path() {\n        FluxHttpServerRequest fluxHttpServerRequest = new FluxHttpServerRequest(TestServerWebExchangeUtils.builder().build());\n        assertEquals(\"/test\", fluxHttpServerRequest.path());\n    }\n\n    @Test\n    public void route() {\n        FluxHttpServerRequest fluxHttpServerRequest = new FluxHttpServerRequest(TestServerWebExchangeUtils.builder().build());\n        assertEquals(null, fluxHttpServerRequest.route());\n    }\n\n    @Test\n    public void getRemoteAddr() {\n\n        FluxHttpServerRequest fluxHttpServerRequest = new FluxHttpServerRequest(TestServerWebExchangeUtils.builder()\n            .remoteAddress(new InetSocketAddress(\"192.168.0.12\", 8080)).build());\n        assertEquals(\"192.168.0.12\", fluxHttpServerRequest.getRemoteAddr());\n    }\n\n    @Test\n    public void getRemotePort() {\n        FluxHttpServerRequest fluxHttpServerRequest = new FluxHttpServerRequest(TestServerWebExchangeUtils.builder()\n            .remoteAddress(new InetSocketAddress(\"192.168.0.12\", 8080)).build());\n        assertEquals(8080, fluxHttpServerRequest.getRemotePort());\n\n    }\n}\n"
  },
  {
    "path": "plugins/spring-gateway/src/test/java/easeagent/plugin/spring/gateway/interceptor/tracing/FluxHttpServerResponseTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring.gateway.interceptor.tracing;\n\nimport easeagent.plugin.spring.gateway.TestServerWebExchangeUtils;\nimport org.junit.Test;\nimport org.springframework.mock.web.server.MockServerWebExchange;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNull;\nimport static org.junit.Assert.assertSame;\n\npublic class FluxHttpServerResponseTest {\n\n\n    @Test\n    public void header() {\n        MockServerWebExchange mockServerWebExchange = TestServerWebExchangeUtils.mockServerWebExchange();\n        String key = \"testKey\";\n        String value = \"testValue\";\n        mockServerWebExchange.getResponse().getHeaders().add(key, value);\n        FluxHttpServerResponse fluxHttpServerResponse = new FluxHttpServerResponse(mockServerWebExchange, null);\n        assertEquals(value, fluxHttpServerResponse.header(key));\n\n    }\n\n    @Test\n    public void method() {\n        MockServerWebExchange mockServerWebExchange = TestServerWebExchangeUtils.mockServerWebExchange();\n        FluxHttpServerResponse fluxHttpServerResponse = new FluxHttpServerResponse(mockServerWebExchange, null);\n        assertEquals(\"GET\", fluxHttpServerResponse.method());\n    }\n\n    @Test\n    public void route() {\n        MockServerWebExchange mockServerWebExchange = TestServerWebExchangeUtils.mockServerWebExchange();\n        FluxHttpServerResponse fluxHttpServerResponse = new FluxHttpServerResponse(mockServerWebExchange, null);\n        assertEquals(\"/test\", fluxHttpServerResponse.route());\n    }\n\n    @Test\n    public void statusCode() {\n        MockServerWebExchange mockServerWebExchange = TestServerWebExchangeUtils.mockServerWebExchange();\n        FluxHttpServerResponse fluxHttpServerResponse = new FluxHttpServerResponse(mockServerWebExchange, null);\n        assertEquals(200, fluxHttpServerResponse.statusCode());\n\n    }\n\n    @Test\n    public void maybeError() {\n        MockServerWebExchange mockServerWebExchange = TestServerWebExchangeUtils.mockServerWebExchange();\n        FluxHttpServerResponse fluxHttpServerResponse = new FluxHttpServerResponse(mockServerWebExchange, null);\n        assertNull(fluxHttpServerResponse.maybeError());\n        RuntimeException err = new RuntimeException();\n        fluxHttpServerResponse = new FluxHttpServerResponse(mockServerWebExchange, err);\n        assertSame(err, fluxHttpServerResponse.maybeError());\n    }\n}\n"
  },
  {
    "path": "plugins/spring-gateway/src/test/java/easeagent/plugin/spring/gateway/interceptor/tracing/GatewayServerTracingInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring.gateway.interceptor.tracing;\n\nimport com.megaease.easeagent.mock.plugin.api.MockEaseAgent;\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.mock.plugin.api.utils.SpanTestUtils;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.api.context.RequestContext;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport easeagent.plugin.spring.gateway.TestServerWebExchangeUtils;\nimport easeagent.plugin.spring.gateway.interceptor.GatewayCons;\nimport easeagent.plugin.spring.gateway.reactor.AgentMono;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.mock.http.server.reactive.MockServerHttpRequest;\nimport org.springframework.mock.web.server.MockServerWebExchange;\nimport org.springframework.web.server.ServerWebExchange;\n\nimport java.util.Map;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.function.BiConsumer;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class GatewayServerTracingInterceptorTest {\n\n    @Test\n    public void before() {\n        GatewayServerTracingInterceptor interceptor = new GatewayServerTracingInterceptor();\n        Context context = EaseAgent.getContext();\n        MockServerWebExchange mockServerWebExchange = TestServerWebExchangeUtils.mockServerWebExchange();\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{mockServerWebExchange}).build();\n        interceptor.before(methodInfo, context);\n        assertNotNull(context.get(GatewayServerTracingInterceptor.SPAN_CONTEXT_KEY));\n        assertNotNull(context.get(FluxHttpServerRequest.class));\n        RequestContext requestContext = context.remove(GatewayServerTracingInterceptor.SPAN_CONTEXT_KEY);\n        assertTrue(context.currentTracing().hasCurrentSpan());\n        requestContext.scope().close();\n        assertFalse(context.currentTracing().hasCurrentSpan());\n        context.remove(FluxHttpServerRequest.class);\n        requestContext.span().abandon();\n\n\n        MockServerHttpRequest.BaseBuilder<?> baseBuilder = TestServerWebExchangeUtils.builder();\n        for (Map.Entry<String, String> entry : requestContext.getHeaders().entrySet()) {\n            baseBuilder.header(entry.getKey(), entry.getValue());\n        }\n        mockServerWebExchange = TestServerWebExchangeUtils.build(baseBuilder);\n        methodInfo = MethodInfo.builder().args(new Object[]{mockServerWebExchange}).build();\n        interceptor.before(methodInfo, context);\n        RequestContext requestContext2 = context.remove(GatewayServerTracingInterceptor.SPAN_CONTEXT_KEY);\n        assertTrue(context.currentTracing().hasCurrentSpan());\n        requestContext2.scope().close();\n        assertFalse(context.currentTracing().hasCurrentSpan());\n        context.remove(FluxHttpServerRequest.class);\n        requestContext2.span().finish();\n        ReportSpan mockSpan = MockEaseAgent.getLastSpan();\n        SpanTestUtils.sameId(requestContext.span(), mockSpan);\n    }\n\n    @Test\n    public void after() {\n        GatewayServerTracingInterceptor interceptor = new GatewayServerTracingInterceptor();\n        Context context = EaseAgent.getContext();\n        MockServerWebExchange mockServerWebExchange = TestServerWebExchangeUtils.mockServerWebExchange();\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{mockServerWebExchange}).build();\n        interceptor.before(methodInfo, context);\n        String errorInfo = \"test error\";\n        methodInfo.throwable(new RuntimeException(errorInfo));\n        interceptor.after(methodInfo, context);\n        ReportSpan reportSpan = MockEaseAgent.getLastSpan();\n        assertNotNull(reportSpan);\n        assertTrue(reportSpan.hasError());\n        assertEquals(errorInfo, reportSpan.errorInfo());\n        assertNull(context.get(GatewayServerTracingInterceptor.SPAN_CONTEXT_KEY));\n        assertNull(context.get(FluxHttpServerRequest.class));\n        assertNull(methodInfo.getRetValue());\n\n\n        mockServerWebExchange = TestServerWebExchangeUtils.mockServerWebExchange();\n        methodInfo = MethodInfo.builder().args(new Object[]{mockServerWebExchange}).build();\n        interceptor.before(methodInfo, context);\n        RequestContext requestContext = context.get(GatewayServerTracingInterceptor.SPAN_CONTEXT_KEY);\n        interceptor.after(methodInfo, context);\n        assertNull(context.get(GatewayServerTracingInterceptor.SPAN_CONTEXT_KEY));\n        assertNull(context.get(FluxHttpServerRequest.class));\n        assertNotNull(methodInfo.getRetValue());\n        assertTrue(methodInfo.getRetValue() instanceof AgentMono);\n\n        assertFalse(context.currentTracing().hasCurrentSpan());\n        requestContext.span().abandon();\n    }\n\n\n    @Test\n    public void finishCallback() throws InterruptedException {\n        GatewayServerTracingInterceptor interceptor = new GatewayServerTracingInterceptor();\n        Context context = EaseAgent.getContext();\n        MockServerWebExchange mockServerWebExchange = TestServerWebExchangeUtils.mockServerWebExchange();\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{mockServerWebExchange}).build();\n        interceptor.before(methodInfo, context);\n        RequestContext requestContext = context.get(GatewayServerTracingInterceptor.SPAN_CONTEXT_KEY);\n        interceptor.after(methodInfo, context);\n        assertNotNull(methodInfo.getRetValue());\n        assertTrue(methodInfo.getRetValue() instanceof AgentMono);\n        assertNull(MockEaseAgent.getLastSpan());\n\n        AtomicBoolean atomicBoolean = new AtomicBoolean(false);\n        BiConsumer<ServerWebExchange, MethodInfo> consumer = (methodInfo1, exchange) -> atomicBoolean.set(true);\n        mockServerWebExchange.getAttributes().put(GatewayCons.CLIENT_RECEIVE_CALLBACK_KEY, consumer);\n        AgentMono agentMono = (AgentMono) methodInfo.getRetValue();\n        Thread thread = new Thread(() -> agentMono.getFinish().accept(agentMono.getMethodInfo(), agentMono.getAsyncContext()));\n        thread.start();\n        thread.join();\n        assertTrue(atomicBoolean.get());\n        ReportSpan reportSpan = MockEaseAgent.getLastSpan();\n        assertTrue(reportSpan.name().contains(\"/test\"));\n        SpanTestUtils.sameId(requestContext.span(), reportSpan);\n    }\n\n    public static RequestContext beforeGatewayServerTracing(MockServerWebExchange mockServerWebExchange) {\n        GatewayServerTracingInterceptor interceptor = new GatewayServerTracingInterceptor();\n        Context context = EaseAgent.getContext();\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{mockServerWebExchange}).build();\n        interceptor.before(methodInfo, context);\n        assertNotNull(context.get(GatewayServerTracingInterceptor.SPAN_CONTEXT_KEY));\n        return context.get(GatewayServerTracingInterceptor.SPAN_CONTEXT_KEY);\n    }\n\n\n    @Test\n    public void getType() {\n        GatewayServerTracingInterceptor interceptor = new GatewayServerTracingInterceptor();\n        assertEquals(ConfigConst.PluginID.TRACING, interceptor.getType());\n    }\n\n    @Test\n    public void order() {\n        GatewayServerTracingInterceptor interceptor = new GatewayServerTracingInterceptor();\n        assertEquals(Order.TRACING.getOrder(), interceptor.order());\n    }\n}\n"
  },
  {
    "path": "plugins/spring-gateway/src/test/java/easeagent/plugin/spring/gateway/interceptor/tracing/HttpHeadersFilterTracingInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring.gateway.interceptor.tracing;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.context.RequestContext;\nimport com.megaease.easeagent.plugin.api.trace.Scope;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport easeagent.plugin.spring.gateway.TestServerWebExchangeUtils;\nimport easeagent.plugin.spring.gateway.interceptor.GatewayCons;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.mock.web.server.MockServerWebExchange;\n\nimport java.util.Collection;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class HttpHeadersFilterTracingInterceptorTest {\n\n    @Test\n    public void doAfter() {\n        HttpHeadersFilterTracingInterceptor interceptor = new HttpHeadersFilterTracingInterceptor();\n\n        MockServerWebExchange mockServerWebExchange = TestServerWebExchangeUtils.mockServerWebExchange();\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{null, mockServerWebExchange}).retValue(new HttpHeaders()).build();\n        interceptor.doAfter(methodInfo, EaseAgent.getContext());\n        assertNull(mockServerWebExchange.getAttribute(GatewayCons.CHILD_SPAN_KEY));\n\n\n\n        mockServerWebExchange = TestServerWebExchangeUtils.mockServerWebExchange();\n        methodInfo = MethodInfo.builder().args(new Object[]{null, mockServerWebExchange}).retValue(new HttpHeaders()).build();\n\n        RequestContext requestContext = GatewayServerTracingInterceptorTest.beforeGatewayServerTracing(mockServerWebExchange);\n        Span span = requestContext.span();\n        try (Scope ignored = requestContext.scope()) {\n            interceptor.doAfter(methodInfo, EaseAgent.getContext());\n            RequestContext clientContext = mockServerWebExchange.getAttribute(GatewayCons.CHILD_SPAN_KEY);\n            HttpHeaders ret = (HttpHeaders) methodInfo.getRetValue();\n            Collection<String> headers = ret.toSingleValueMap().values();\n            assertTrue(headers.contains(clientContext.span().traceIdString()));\n            assertTrue(headers.contains(clientContext.span().spanIdString()));\n            assertTrue(headers.contains(clientContext.span().parentIdString()));\n            assertEquals(span.traceIdString(), clientContext.span().traceIdString());\n            assertEquals(span.spanIdString(), clientContext.span().parentIdString());\n            clientContext.scope().close();\n            clientContext.span().abandon();\n        }\n        span.abandon();\n    }\n\n    @Test\n    public void testHeaderFilterRequest() {\n        HttpHeadersFilterTracingInterceptor.HeaderFilterRequest headerFilterRequest = new HttpHeadersFilterTracingInterceptor.HeaderFilterRequest(null);\n        assertEquals(Span.Kind.CLIENT, headerFilterRequest.kind());\n    }\n}\n"
  },
  {
    "path": "plugins/spring-gateway/src/test/java/easeagent/plugin/spring/gateway/reactor/AgentCoreSubscriberTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring.gateway.reactor;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.context.AsyncContext;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.reactivestreams.Subscription;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class AgentCoreSubscriberTest {\n\n    @Test\n    public void currentContext() {\n        MockCoreSubscriber mockCoreSubscriber = new MockCoreSubscriber();\n        AgentCoreSubscriber agentCoreSubscriber = new AgentCoreSubscriber(mockCoreSubscriber, null, null, null);\n        agentCoreSubscriber.currentContext();\n        assertTrue(mockCoreSubscriber.currentContext.get());\n    }\n\n    @Test\n    public void onSubscribe() {\n        MockCoreSubscriber mockCoreSubscriber = new MockCoreSubscriber();\n        AgentCoreSubscriber agentCoreSubscriber = new AgentCoreSubscriber(mockCoreSubscriber, null, null, null);\n        agentCoreSubscriber.onSubscribe(new Subscription() {\n            @Override\n            public void request(long l) {\n\n            }\n\n            @Override\n            public void cancel() {\n\n            }\n        });\n        assertTrue(mockCoreSubscriber.onSubscribe.get());\n\n    }\n\n    @Test\n    public void onNext() {\n        MockCoreSubscriber mockCoreSubscriber = new MockCoreSubscriber();\n        AgentCoreSubscriber agentCoreSubscriber = new AgentCoreSubscriber(mockCoreSubscriber, null, null, null);\n        agentCoreSubscriber.onNext(null);\n        assertTrue(mockCoreSubscriber.onNext.get());\n    }\n\n    @Test\n    public void onError() {\n        MockCoreSubscriber mockCoreSubscriber = new MockCoreSubscriber();\n        MethodInfo errorMethodInfo = MethodInfo.builder().build();\n        AsyncContext errorAsyncContext = EaseAgent.getContext().exportAsync();\n        RuntimeException runtimeException = new RuntimeException();\n        AgentCoreSubscriber agentCoreSubscriber = new AgentCoreSubscriber(mockCoreSubscriber, errorMethodInfo, errorAsyncContext, (methodInfo, asyncContext) -> {\n            assertNotNull(methodInfo);\n            assertNotNull(asyncContext);\n            assertSame(errorMethodInfo, methodInfo);\n            assertSame(errorAsyncContext, asyncContext);\n            assertFalse(methodInfo.isSuccess());\n            assertSame(runtimeException, methodInfo.getThrowable());\n        });\n        agentCoreSubscriber.onError(runtimeException);\n        assertTrue(mockCoreSubscriber.onError.get());\n    }\n\n    @Test\n    public void onComplete() {\n        MockCoreSubscriber mockCoreSubscriber = new MockCoreSubscriber();\n        MethodInfo completeMethodInfo = MethodInfo.builder().build();\n        AsyncContext completeAsyncContext = EaseAgent.getContext().exportAsync();\n        AgentCoreSubscriber agentCoreSubscriber = new AgentCoreSubscriber(mockCoreSubscriber, completeMethodInfo, completeAsyncContext, (methodInfo, asyncContext) -> {\n            assertNotNull(methodInfo);\n            assertNotNull(asyncContext);\n            assertSame(completeMethodInfo, methodInfo);\n            assertSame(completeAsyncContext, asyncContext);\n            assertTrue(methodInfo.isSuccess());\n        });\n        agentCoreSubscriber.onComplete();\n        assertTrue(mockCoreSubscriber.onComplete.get());\n\n    }\n}\n"
  },
  {
    "path": "plugins/spring-gateway/src/test/java/easeagent/plugin/spring/gateway/reactor/AgentMonoTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring.gateway.reactor;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Cleaner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.AsyncContext;\nimport com.megaease.easeagent.plugin.api.trace.Scope;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport reactor.core.CoreSubscriber;\nimport reactor.core.publisher.Mono;\n\nimport java.util.concurrent.atomic.AtomicBoolean;\n\nimport static org.junit.Assert.assertFalse;\nimport static org.junit.Assert.assertTrue;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class AgentMonoTest {\n\n    @Test\n    public void subscribe() {\n        AtomicBoolean ran = new AtomicBoolean(false);\n        Mono<Void> mono = new Mono<Void>() {\n            @Override\n            public void subscribe(CoreSubscriber<? super Void> coreSubscriber) {\n                ran.set(true);\n            }\n        };\n\n        MethodInfo methodInfo = MethodInfo.builder().build();\n        AgentMono agentMono = new AgentMono(mono, methodInfo, EaseAgent.getContext().exportAsync(), null);\n        agentMono.subscribe(new MockCoreSubscriber());\n        assertTrue(ran.get());\n    }\n\n    @Test\n    public void testImportToCurrent() throws InterruptedException {\n        Context context = EaseAgent.getContext();\n        Span span = context.nextSpan();\n        Thread thread;\n        try (Scope ignored4 = span.maybeScope()) {\n            AsyncContext asyncContext1 = context.exportAsync();\n            AsyncContext asyncContext2 = context.exportAsync();\n            AsyncContext asyncContext3 = context.exportAsync();\n            thread = new Thread(() -> {\n                Context asyncContext = EaseAgent.getContext();\n                assertFalse(asyncContext.currentTracing().hasCurrentSpan());\n                try (Cleaner ignored = asyncContext1.importToCurrent()) {\n                    assertTrue(asyncContext.currentTracing().hasCurrentSpan());\n                    try (Cleaner ignored1 = asyncContext2.importToCurrent()) {\n                        assertTrue(asyncContext.currentTracing().hasCurrentSpan());\n                        try (Cleaner ignored2 = asyncContext3.importToCurrent()) {\n                            assertTrue(asyncContext.currentTracing().hasCurrentSpan());\n                        }\n                        assertTrue(asyncContext.currentTracing().hasCurrentSpan());\n                    }\n                    assertTrue(asyncContext.currentTracing().hasCurrentSpan());\n                }\n                assertFalse(asyncContext.currentTracing().hasCurrentSpan());\n            });\n        }\n        thread.start();\n        thread.join();\n        span.finish();\n    }\n\n}\n"
  },
  {
    "path": "plugins/spring-gateway/src/test/java/easeagent/plugin/spring/gateway/reactor/MockCoreSubscriber.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage easeagent.plugin.spring.gateway.reactor;\n\nimport org.reactivestreams.Subscription;\nimport reactor.core.CoreSubscriber;\nimport reactor.util.context.Context;\n\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\npublic class MockCoreSubscriber implements CoreSubscriber<Void> {\n    AtomicBoolean currentContext = new AtomicBoolean(false);\n    AtomicBoolean onSubscribe = new AtomicBoolean(false);\n    AtomicBoolean onNext = new AtomicBoolean(false);\n    AtomicBoolean onError = new AtomicBoolean(false);\n    AtomicBoolean onComplete = new AtomicBoolean(false);\n\n\n    @Override\n    public Context currentContext() {\n        currentContext.set(true);\n        return Context.of(Collections.emptyMap());\n    }\n\n    @Override\n    public void onSubscribe(Subscription subscription) {\n        onSubscribe.set(true);\n    }\n\n    @Override\n    public void onNext(Void aVoid) {\n        onNext.set(true);\n    }\n\n    @Override\n    public void onError(Throwable throwable) {\n        onError.set(true);\n    }\n\n    @Override\n    public void onComplete() {\n        onComplete.set(true);\n    }\n}\n"
  },
  {
    "path": "plugins/spring-gateway/src/test/resources/mock_agent.properties",
    "content": "name=test-gateway-service\nsystem=test-gateway-system\nplugin.observability.springGateway.metric.interval=200\nplugin.observability.springGateway.metric.intervalUnit=MILLISECONDS\neaseagent.progress.forwarded.headers=X-Forwarded-For\n"
  },
  {
    "path": "plugins/springweb/README.md",
    "content": "# spring-boot http client plugin \n\n## Points\n\n* resTemplate points: `org.springframework.http.client.ClientHttpRequest:execute`\n  * points code version spring-boot:2.x.x config and default\n    ```properties\n    runtime.code.version.points.spring-boot=2.x.x\n    ```\n    when not config `runtime.code.version.points.spring-boot` it is load\n* feignClient points: `feign.Client:execute`\n* webclient points: `org.springframework.web.reactive.function.client.WebClient$Builder:build`\n\n\n## config\n\n### tracing config\n```properties\nplugin.observability.webclient.tracing.enabled=true\nplugin.observability.resTemplate.tracing.enabled=true\nplugin.observability.feignClient.tracing.enabled=true\n```\n\n### support forwarded\n```properties\nplugin.integrability.forwarded.forwarded.enabled=true\n```\n\n\n\n"
  },
  {
    "path": "plugins/springweb/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright (c) 2021, MegaEase\n  ~ All rights reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~   http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>plugins</artifactId>\n        <groupId>com.megaease.easeagent</groupId>\n        <version>2.3.0</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>springweb</artifactId>\n\n\n    <dependencies>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>plugin-api</artifactId>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.google.code.findbugs</groupId>\n            <artifactId>jsr305</artifactId>\n            <scope>compile</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-web</artifactId>\n            <version>2.5.12</version>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.cloud</groupId>\n            <artifactId>spring-cloud-starter-openfeign</artifactId>\n            <version>3.0.4</version>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework</groupId>\n            <artifactId>spring-webflux</artifactId>\n            <version>5.3.18</version>\n            <scope>provided</scope>\n        </dependency>\n\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>plugin-api-mock</artifactId>\n            <version>${project.version}</version>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework</groupId>\n            <artifactId>spring-test</artifactId>\n            <version>5.3.9</version>\n            <scope>test</scope>\n        </dependency>\n\n\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "plugins/springweb/src/main/java/com/megaease/easeagent/plugin/springweb/FeignClientPlugin.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.springweb;\n\nimport com.megaease.easeagent.plugin.AgentPlugin;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\n\npublic class FeignClientPlugin implements AgentPlugin {\n    @Override\n    public String getNamespace() {\n        return ConfigConst.Namespace.FEIGN_CLIENT;\n    }\n\n    @Override\n    public String getDomain() {\n        return ConfigConst.OBSERVABILITY;\n    }\n\n}\n"
  },
  {
    "path": "plugins/springweb/src/main/java/com/megaease/easeagent/plugin/springweb/ForwardedPlugin.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.springweb;\n\nimport com.megaease.easeagent.plugin.AgentPlugin;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\n\npublic class ForwardedPlugin implements AgentPlugin {\n    @Override\n    public String getNamespace() {\n        return ConfigConst.Namespace.FORWARDED;\n    }\n\n    @Override\n    public String getDomain() {\n        return ConfigConst.INTEGRABILITY;\n    }\n}\n"
  },
  {
    "path": "plugins/springweb/src/main/java/com/megaease/easeagent/plugin/springweb/RestTemplatePlugin.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.springweb;\n\nimport com.megaease.easeagent.plugin.AgentPlugin;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\n\npublic class RestTemplatePlugin implements AgentPlugin {\n    @Override\n    public String getNamespace() {\n        return ConfigConst.Namespace.REST_TEMPLATE;\n    }\n\n    @Override\n    public String getDomain() {\n        return ConfigConst.OBSERVABILITY;\n    }\n\n}\n"
  },
  {
    "path": "plugins/springweb/src/main/java/com/megaease/easeagent/plugin/springweb/SpringWebPlugin.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.springweb;\n\nimport com.megaease.easeagent.plugin.AgentPlugin;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\n\npublic class SpringWebPlugin implements AgentPlugin {\n\n    @Override\n    public String getNamespace() {\n        return \"springweb\";\n    }\n\n    @Override\n    public String getDomain() {\n        return ConfigConst.OBSERVABILITY;\n    }\n\n}\n"
  },
  {
    "path": "plugins/springweb/src/main/java/com/megaease/easeagent/plugin/springweb/WebClientPlugin.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.springweb;\n\nimport com.megaease.easeagent.plugin.AgentPlugin;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\n\npublic class WebClientPlugin implements AgentPlugin {\n    @Override\n    public String getNamespace() {\n        return ConfigConst.Namespace.WEB_CLIENT;\n    }\n\n    @Override\n    public String getDomain() {\n        return ConfigConst.OBSERVABILITY;\n    }\n\n}\n"
  },
  {
    "path": "plugins/springweb/src/main/java/com/megaease/easeagent/plugin/springweb/advice/ClientHttpRequestAdvice.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.springweb.advice;\n\nimport com.megaease.easeagent.plugin.CodeVersion;\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.Set;\n\npublic class ClientHttpRequestAdvice implements Points {\n\n    private final static CodeVersion VERSIONS = CodeVersion.builder()\n        .key(ConfigConst.CodeVersion.KEY_SPRING_BOOT)\n        .add(Points.DEFAULT_VERSION)\n        .add(ConfigConst.CodeVersion.VERSION_SPRING_BOOT2).build();\n\n    @Override\n    public CodeVersion codeVersions() {\n        return VERSIONS;\n    }\n\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return ClassMatcher.builder().hasInterface(\"org.springframework.http.client.ClientHttpRequest\").notInterface()\n            .build();\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.multiBuilder()\n            .match(MethodMatcher.builder().named(\"execute\")\n                .returnType(\"org.springframework.http.client.ClientHttpResponse\")\n                .qualifier(\"default\")\n                .build())\n            .build();\n    }\n}\n"
  },
  {
    "path": "plugins/springweb/src/main/java/com/megaease/easeagent/plugin/springweb/advice/FeignClientAdvice.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.springweb.advice;\n\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.Set;\n\npublic class FeignClientAdvice implements Points {\n\n    //return def.type(hasSuperType(named(\"feign.Client\")))\n    //                .transform(execute(named(\"execute\").and(takesArguments(2)\n    //                        .and(takesArgument(0, named(\"feign.Request\")))\n    //                        .and(takesArgument(1, named(\"feign.Request$Options\")))\n    //                )))\n    //                .end();\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return ClassMatcher.builder().hasInterface(\"feign.Client\")\n            .build();\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.multiBuilder()\n            .match(MethodMatcher.builder().named(\"execute\")\n                .isPublic()\n                .argsLength(2)\n                .arg(0, \"feign.Request\")\n                .arg(1, \"feign.Request$Options\")\n                .qualifier(\"default\")\n                .build())\n            .build();\n    }\n}\n"
  },
  {
    "path": "plugins/springweb/src/main/java/com/megaease/easeagent/plugin/springweb/advice/WebClientBuilderAdvice.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.springweb.advice;\n\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.Set;\n\npublic class WebClientBuilderAdvice implements Points {\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return ClassMatcher.builder().hasSuperClass(\"org.springframework.web.reactive.function.client.WebClient$Builder\")\n            .build();\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.multiBuilder()\n            .match(MethodMatcher.builder().named(\"build\")\n                .qualifier(\"default\")\n                .build())\n            .build();\n    }\n}\n"
  },
  {
    "path": "plugins/springweb/src/main/java/com/megaease/easeagent/plugin/springweb/advice/WebClientFilterAdvice.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.springweb.advice;\n\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.Set;\n\npublic class WebClientFilterAdvice implements Points {\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return ClassMatcher.builder()\n            .hasClassName(\"com.megaease.plugin.easeagent.springweb.interceptor.tracing.WebClientTracingFilter\")\n            .build();\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.multiBuilder()\n            .match(MethodMatcher.builder().named(\"filter\")\n                .qualifier(\"default\")\n                .build())\n            .build();\n    }\n}\n"
  },
  {
    "path": "plugins/springweb/src/main/java/com/megaease/easeagent/plugin/springweb/interceptor/HeadersFieldFinder.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.springweb.interceptor;\n\nimport com.megaease.easeagent.plugin.api.logging.Logger;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport feign.Request;\n\nimport java.lang.reflect.Field;\nimport java.util.Collection;\nimport java.util.HashMap;\nimport java.util.Map;\n\npublic class HeadersFieldFinder {\n    private static final Logger logger = EaseAgent.getLogger(HeadersFieldFinder.class);\n\n    private static Field headersField;\n\n    private static Field getHeadersField() {\n        if (headersField != null) {\n            return headersField;\n        }\n        try {\n            headersField = Request.class.getDeclaredField(\"headers\");\n            headersField.setAccessible(true);\n            return headersField;\n        } catch (Exception e) {\n            logger.warn(e.getMessage());\n            return null;\n        }\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    private static Map<String, Collection<String>> getHeadersFieldValue(Field headersField, Object target) {\n        try {\n            return (Map<String, Collection<String>>) headersField.get(target);\n        } catch (IllegalAccessException e) {\n            logger.warn(\"can not get header in FeignClient. {}\", e.getMessage());\n        }\n        return null;\n    }\n\n    private static void setHeadersFieldValue(Field headersField, Object target, Object fieldValue) {\n        try {\n            headersField.set(target, fieldValue);\n        } catch (IllegalAccessException e) {\n            logger.warn(\"can not set header in FeignClient. {}\", e.getMessage());\n        }\n    }\n\n    public static HashMap<String, Collection<String>> getHashMapHeaders(Request request) {\n        Field headersField = HeadersFieldFinder.getHeadersField();\n        if (headersField != null) {\n            Map<String, Collection<String>> originHeaders = HeadersFieldFinder.getHeadersFieldValue(headersField, request);\n            if (originHeaders instanceof HashMap) {\n                return (HashMap<String, Collection<String>>) originHeaders;\n            }\n            HashMap<String, Collection<String>> headers = new HashMap<>();\n            if (originHeaders != null) {\n                headers.putAll(originHeaders);\n            }\n            HeadersFieldFinder.setHeadersFieldValue(headersField, request, headers);\n            return headers;\n        }\n        return new HashMap<>();\n    }\n}\n"
  },
  {
    "path": "plugins/springweb/src/main/java/com/megaease/easeagent/plugin/springweb/interceptor/forwarded/FeignClientForwardedInterceptor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.springweb.interceptor.forwarded;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.api.trace.Setter;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.springweb.ForwardedPlugin;\nimport com.megaease.easeagent.plugin.springweb.advice.FeignClientAdvice;\nimport com.megaease.easeagent.plugin.springweb.interceptor.HeadersFieldFinder;\nimport feign.Request;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Map;\n\n@AdviceTo(value = FeignClientAdvice.class, plugin = ForwardedPlugin.class)\npublic class FeignClientForwardedInterceptor implements Interceptor {\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n        FeignClientRequest request = new FeignClientRequest((Request) methodInfo.getArgs()[0]);\n        context.injectForwardedHeaders(request);\n    }\n\n\n    @Override\n    public String getType() {\n        return ConfigConst.PluginID.FORWARDED;\n    }\n\n    @Override\n    public int order() {\n        return Order.FORWARDED.getOrder();\n    }\n\n    static class FeignClientRequest implements Setter {\n        private final Request request;\n        private Map<String, Collection<String>> headers;\n\n\n        public FeignClientRequest(Request request) {\n            this.request = request;\n        }\n\n        public void initIfNull() {\n            if (headers != null) {\n                return;\n            }\n            this.headers = HeadersFieldFinder.getHashMapHeaders(request);\n        }\n\n        @Override\n        public void setHeader(String name, String value) {\n            //init headers from request if has forwarded\n            initIfNull();\n            Collection<String> values = headers.computeIfAbsent(name, k -> new ArrayList<>());\n            values.add(value);\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/springweb/src/main/java/com/megaease/easeagent/plugin/springweb/interceptor/forwarded/RestTemplateForwardedInterceptor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.springweb.interceptor.forwarded;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.springweb.ForwardedPlugin;\nimport com.megaease.easeagent.plugin.springweb.advice.ClientHttpRequestAdvice;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.http.client.ClientHttpRequest;\n\n@AdviceTo(value = ClientHttpRequestAdvice.class, plugin = ForwardedPlugin.class)\npublic class RestTemplateForwardedInterceptor implements Interceptor {\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n        ClientHttpRequest clientHttpRequest = (ClientHttpRequest) methodInfo.getInvoker();\n        HttpHeaders httpHeaders = clientHttpRequest.getHeaders();\n        context.injectForwardedHeaders(httpHeaders::add);\n    }\n\n\n    @Override\n    public String getType() {\n        return ConfigConst.PluginID.FORWARDED;\n    }\n\n    @Override\n    public int order() {\n        return Order.FORWARDED.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/springweb/src/main/java/com/megaease/easeagent/plugin/springweb/interceptor/forwarded/WebClientFilterForwardedInterceptor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.springweb.interceptor.forwarded;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.AutoRefreshPluginConfigImpl;\nimport com.megaease.easeagent.plugin.api.config.AutoRefreshPluginConfigRegistry;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.trace.Setter;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.springweb.ForwardedPlugin;\nimport com.megaease.easeagent.plugin.springweb.advice.WebClientBuilderAdvice;\nimport com.megaease.easeagent.plugin.utils.common.WeakConcurrentMap;\nimport lombok.NonNull;\nimport org.springframework.web.reactive.function.client.*;\nimport reactor.core.publisher.Mono;\n\n@AdviceTo(value = WebClientBuilderAdvice.class, plugin = ForwardedPlugin.class)\npublic class WebClientFilterForwardedInterceptor implements Interceptor {\n    protected static volatile AutoRefreshPluginConfigImpl AUTO_CONFIG;\n    static WeakConcurrentMap<WebClient.Builder, Boolean> builders = new WeakConcurrentMap<>();\n\n    @Override\n    public void init(IPluginConfig config, int uniqueIndex) {\n        AUTO_CONFIG = AutoRefreshPluginConfigRegistry.getOrCreate(config.domain(), config.namespace(), config.id());\n    }\n\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n        WebClient.Builder builder = (WebClient.Builder) methodInfo.getInvoker();\n        if (builders.putIfProbablyAbsent(builder, Boolean.TRUE) == null) {\n            builder.filter(new WebClientForwardedFilter());\n        }\n    }\n\n    @Override\n    public String getType() {\n        return ConfigConst.PluginID.FORWARDED;\n    }\n\n    @Override\n    public int order() {\n        return Order.FORWARDED.getOrder();\n    }\n\n    public class WebClientForwardedFilter implements ExchangeFilterFunction {\n\n        @NonNull\n        @Override\n        public Mono<ClientResponse> filter(@NonNull ClientRequest clientRequest, @NonNull ExchangeFunction exchangeFunction) {\n            ClientRequest req = clientRequest;\n            if (AUTO_CONFIG.enabled()) {\n                Request request = new Request(clientRequest);\n                EaseAgent.getContext().injectForwardedHeaders(request);\n                req = request.get();\n            }\n            return exchangeFunction.exchange(req);\n        }\n    }\n\n\n    protected static class Request implements Setter {\n        private final ClientRequest request;\n        private ClientRequest.Builder builder;\n\n        protected Request(ClientRequest request) {\n            this.request = request;\n        }\n\n        private ClientRequest.Builder getBuilder() {\n            if (builder != null) {\n                return builder;\n            }\n            builder = ClientRequest.from(request);\n            return builder;\n        }\n\n        public ClientRequest get() {\n            if (builder == null) {\n                return request;\n            }\n            return builder.build();\n        }\n\n        @Override\n        public void setHeader(String name, String value) {\n            getBuilder().header(name, value);\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/springweb/src/main/java/com/megaease/easeagent/plugin/springweb/interceptor/initialize/WebClientBuildInterceptor.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.springweb.interceptor.initialize;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.Interceptor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.springweb.WebClientPlugin;\nimport com.megaease.easeagent.plugin.springweb.advice.WebClientBuilderAdvice;\nimport com.megaease.easeagent.plugin.utils.common.WeakConcurrentMap;\nimport com.megaease.plugin.easeagent.springweb.interceptor.tracing.WebClientTracingFilter;\nimport org.springframework.web.reactive.function.client.WebClient;\n\n@AdviceTo(value = WebClientBuilderAdvice.class, plugin = WebClientPlugin.class)\npublic class WebClientBuildInterceptor implements Interceptor {\n    static WeakConcurrentMap<WebClient.Builder, Boolean> builders = new WeakConcurrentMap<>();\n    @Override\n    public void before(MethodInfo methodInfo, Context context) {\n        WebClient.Builder builder = (WebClient.Builder) methodInfo.getInvoker();\n        if (builders.putIfProbablyAbsent(builder, Boolean.TRUE) == null) {\n            builder.filter(new WebClientTracingFilter());\n        }\n    }\n\n    @Override\n    public int order() {\n        return Order.TRACING_INIT.getOrder();\n    }\n\n    @Override\n    public String getType() {\n        return Order.TRACING.getName();\n    }\n\n}\n"
  },
  {
    "path": "plugins/springweb/src/main/java/com/megaease/easeagent/plugin/springweb/interceptor/tracing/ClientHttpRequestInterceptor.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.springweb.interceptor.tracing;\n\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.springweb.RestTemplatePlugin;\nimport com.megaease.easeagent.plugin.springweb.advice.ClientHttpRequestAdvice;\nimport com.megaease.easeagent.plugin.tools.trace.BaseHttpClientTracingInterceptor;\nimport com.megaease.easeagent.plugin.tools.trace.HttpRequest;\nimport com.megaease.easeagent.plugin.tools.trace.HttpResponse;\nimport lombok.SneakyThrows;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.http.client.ClientHttpRequest;\nimport org.springframework.http.client.ClientHttpResponse;\n\nimport java.util.List;\n\n@AdviceTo(value = ClientHttpRequestAdvice.class, plugin = RestTemplatePlugin.class)\npublic class ClientHttpRequestInterceptor extends BaseHttpClientTracingInterceptor {\n    private static final Object PROGRESS_CONTEXT = new Object();\n\n    @Override\n    public Object getProgressKey() {\n        return PROGRESS_CONTEXT;\n    }\n\n    @Override\n    protected HttpRequest getRequest(MethodInfo methodInfo, Context context) {\n        ClientHttpRequest clientHttpRequest = (ClientHttpRequest) methodInfo.getInvoker();\n        return new ClientRequestWrapper(clientHttpRequest);\n    }\n\n    @Override\n    protected HttpResponse getResponse(MethodInfo methodInfo, Context context) {\n        ClientHttpRequest clientHttpRequest = (ClientHttpRequest) methodInfo.getInvoker();\n        ClientHttpResponse response = (ClientHttpResponse) methodInfo.getRetValue();\n        return new ClientResponseWrapper(methodInfo.getThrowable(), clientHttpRequest, response);\n    }\n\n    private static String getFirstHeaderValue(HttpHeaders headers, String name) {\n        List<String> values = headers.get(name);\n        if (values == null || values.isEmpty()) {\n            return null;\n        }\n        return values.get(0);\n    }\n\n    static class ClientRequestWrapper implements HttpRequest {\n\n        private final ClientHttpRequest request;\n\n        public ClientRequestWrapper(ClientHttpRequest request) {\n            this.request = request;\n        }\n\n        @Override\n        public Span.Kind kind() {\n            return Span.Kind.CLIENT;\n        }\n\n\n        @Override\n        public String method() {\n            return request.getMethodValue();\n        }\n\n        @Override\n        public String path() {\n            return request.getURI().getPath();\n        }\n\n        @Override\n        public String route() {\n            return null;\n        }\n\n        @Override\n        public String getRemoteAddr() {\n            return request.getURI().getHost();\n        }\n\n        @Override\n        public int getRemotePort() {\n            return request.getURI().getPort();\n        }\n\n        @Override\n        public String header(String name) {\n            return getFirstHeaderValue(request.getHeaders(), name);\n        }\n\n\n        @Override\n        public boolean cacheScope() {\n            return false;\n        }\n\n        @Override\n        public void setHeader(String name, String value) {\n            request.getHeaders().add(name, value);\n        }\n    }\n\n    static class ClientResponseWrapper implements HttpResponse {\n        private final Throwable caught;\n        private final ClientHttpRequest request;\n        private final ClientHttpResponse response;\n\n        public ClientResponseWrapper(Throwable caught, ClientHttpRequest request, ClientHttpResponse response) {\n            this.caught = caught;\n            this.request = request;\n            this.response = response;\n        }\n\n        @Override\n        public String method() {\n            return request.getMethodValue();\n        }\n\n        @Override\n        public String route() {\n            return null;\n        }\n\n        @SneakyThrows\n        @Override\n        public int statusCode() {\n            if (response == null) {\n                return 500;\n            }\n            return response.getRawStatusCode();\n        }\n\n        @Override\n        public Throwable maybeError() {\n            return caught;\n        }\n\n        @Override\n        public String header(String name) {\n            if (response == null) {\n                return \"\";\n            }\n            return getFirstHeaderValue(response.getHeaders(), name);\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/springweb/src/main/java/com/megaease/easeagent/plugin/springweb/interceptor/tracing/FeignClientTracingInterceptor.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.springweb.interceptor.tracing;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.springweb.FeignClientPlugin;\nimport com.megaease.easeagent.plugin.springweb.advice.FeignClientAdvice;\nimport com.megaease.easeagent.plugin.springweb.interceptor.HeadersFieldFinder;\nimport com.megaease.easeagent.plugin.tools.trace.BaseHttpClientTracingInterceptor;\nimport com.megaease.easeagent.plugin.tools.trace.HttpRequest;\nimport com.megaease.easeagent.plugin.tools.trace.HttpResponse;\nimport feign.Request;\nimport feign.Response;\nimport lombok.SneakyThrows;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.HashMap;\nimport java.util.Map;\n\n@AdviceTo(value = FeignClientAdvice.class, plugin = FeignClientPlugin.class)\npublic class FeignClientTracingInterceptor extends BaseHttpClientTracingInterceptor {\n    private static final Object PROGRESS_CONTEXT = new Object();\n\n    @Override\n    public Object getProgressKey() {\n        return PROGRESS_CONTEXT;\n    }\n\n    @Override\n    protected HttpRequest getRequest(MethodInfo methodInfo, Context context) {\n        return new FeignClientRequestWrapper((Request) methodInfo.getArgs()[0]);\n    }\n\n    @Override\n    protected HttpResponse getResponse(MethodInfo methodInfo, Context context) {\n        Request request = (Request) methodInfo.getArgs()[0];\n        Response response = (Response) methodInfo.getRetValue();\n        return new FeignClientResponseWrapper(methodInfo.getThrowable(), request, response);\n    }\n\n\n    private static String getFirstHeaderValue(Map<String, Collection<String>> headers, String name) {\n        Collection<String> values = headers.get(name);\n        if (values == null || values.isEmpty()) {\n            return null;\n        }\n        return values.iterator().next();\n    }\n\n    static class FeignClientRequestWrapper implements HttpRequest {\n\n        private final Request request;\n\n        private final HashMap<String, Collection<String>> headers;\n\n        public FeignClientRequestWrapper(Request request) {\n            this.request = request;\n            this.headers = HeadersFieldFinder.getHashMapHeaders(request);\n        }\n\n        @Override\n        public Span.Kind kind() {\n            return Span.Kind.CLIENT;\n        }\n\n\n        @Override\n        public String method() {\n            return request.httpMethod().name();\n        }\n\n        @Override\n        public String path() {\n            return request.url();\n        }\n\n        @Override\n        public String route() {\n            return null;\n        }\n\n        @Override\n        public String getRemoteAddr() {\n            return null;\n        }\n\n        @Override\n        public int getRemotePort() {\n            return 0;\n        }\n\n\n        @Override\n        public String header(String name) {\n            Collection<String> values = headers.get(name);\n            if (values == null || values.isEmpty()) {\n                return null;\n            }\n            return values.iterator().next();\n        }\n\n        @Override\n        public boolean cacheScope() {\n            return false;\n        }\n\n        @Override\n        public void setHeader(String name, String value) {\n            Collection<String> values = headers.computeIfAbsent(name, k -> new ArrayList<>());\n            values.add(value);\n        }\n    }\n\n    static class FeignClientResponseWrapper implements HttpResponse {\n        private final Throwable caught;\n        private final Request request;\n        private final Response response;\n        private final Map<String, Collection<String>> headers;\n\n        public FeignClientResponseWrapper(Throwable caught, Request request, Response response) {\n            this.caught = caught;\n            this.request = request;\n            this.response = response;\n            this.headers = response.headers();\n        }\n\n        @Override\n        public String method() {\n            return request.httpMethod().name();\n        }\n\n        @Override\n        public String route() {\n            return null;\n//            Object maybeRoute = request.getAttribute(TraceConst.HTTP_ATTRIBUTE_ROUTE);\n//            return maybeRoute instanceof String ? (String) maybeRoute : null;\n        }\n\n        @SneakyThrows\n        @Override\n        public int statusCode() {\n            return response.status();\n        }\n\n        @Override\n        public Throwable maybeError() {\n            return caught;\n        }\n\n        @Override\n        public String header(String name) {\n            return getFirstHeaderValue(headers, name);\n        }\n    }\n\n}\n"
  },
  {
    "path": "plugins/springweb/src/main/java/com/megaease/easeagent/plugin/springweb/interceptor/tracing/WebClientFilterTracingInterceptor.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.springweb.interceptor.tracing;\n\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.context.RequestContext;\nimport com.megaease.easeagent.plugin.api.logging.Logger;\nimport com.megaease.easeagent.plugin.api.trace.Scope;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.NonReentrantInterceptor;\nimport com.megaease.easeagent.plugin.springweb.WebClientPlugin;\nimport com.megaease.easeagent.plugin.springweb.advice.WebClientFilterAdvice;\nimport com.megaease.easeagent.plugin.springweb.reactor.AgentMono;\nimport com.megaease.easeagent.plugin.tools.trace.HttpRequest;\nimport com.megaease.easeagent.plugin.tools.trace.HttpResponse;\nimport com.megaease.easeagent.plugin.tools.trace.HttpUtils;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.web.reactive.function.client.ClientRequest;\nimport org.springframework.web.reactive.function.client.ClientResponse;\nimport reactor.core.publisher.Mono;\n\nimport java.util.Collection;\n\n@AdviceTo(value = WebClientFilterAdvice.class, plugin = WebClientPlugin.class)\npublic class WebClientFilterTracingInterceptor implements NonReentrantInterceptor {\n    private static final Object PROGRESS_CONTEXT = new Object();\n\n    public Object getProgressKey() {\n        return PROGRESS_CONTEXT;\n    }\n\n    @Override\n    public void doBefore(MethodInfo methodInfo, Context context) {\n        HttpRequest request = getRequest(methodInfo);\n        RequestContext requestContext = context.clientRequest(request);\n        Span span = requestContext.span();\n        HttpUtils.handleReceive(span.start(), request);\n        context.put(getProgressKey(), requestContext);\n    }\n\n    @Override\n    public void doAfter(MethodInfo methodInfo, Context context) {\n        RequestContext pCtx = context.get(getProgressKey());\n        try (Scope ignored = pCtx.scope()) {\n            @SuppressWarnings(\"unchecked\")\n            Mono<ClientResponse> mono = (Mono<ClientResponse>) methodInfo.getRetValue();\n            methodInfo.setRetValue(new AgentMono(mono, methodInfo, pCtx));\n\n            if (!methodInfo.isSuccess()) {\n                Span span = pCtx.span();\n                span.error(methodInfo.getThrowable());\n                span.finish();\n            }\n        }\n    }\n\n    @Override\n    public int order() {\n        return Order.TRACING.getOrder();\n    }\n\n    protected HttpRequest getRequest(MethodInfo methodInfo) {\n        ClientRequest clientRequest = (ClientRequest) methodInfo.getArgs()[0];\n        ClientRequest.Builder builder = ClientRequest.from(clientRequest);\n        WebClientRequest request = new WebClientRequest(clientRequest, builder);\n        Object[] args = methodInfo.getArgs();\n        args[0] = builder.build();\n        methodInfo.setArgs(args);\n        return request;\n    }\n\n    static class WebClientRequest implements HttpRequest {\n\n        private final ClientRequest clientRequest;\n\n        private final ClientRequest.Builder builder;\n\n        public WebClientRequest(ClientRequest clientRequest, ClientRequest.Builder builder) {\n            this.clientRequest = clientRequest;\n            this.builder = builder;\n        }\n\n\n        @Override\n        public String method() {\n            return clientRequest.method().name();\n        }\n\n        @Override\n        public String path() {\n            return clientRequest.url().getPath();\n        }\n\n        @Override\n        public String route() {\n            return null;\n        }\n\n        @Override\n        public String getRemoteAddr() {\n            return clientRequest.url().toString();\n        }\n\n        @Override\n        public int getRemotePort() {\n            return 0;\n        }\n\n        @Override\n        public Span.Kind kind() {\n            return Span.Kind.CLIENT;\n        }\n\n        @Override\n        public String header(String name) {\n            HttpHeaders headers = clientRequest.headers();\n            return headers.getFirst(name);\n        }\n\n        @Override\n        public boolean cacheScope() {\n            return false;\n        }\n\n        @Override\n        public void setHeader(String name, String value) {\n            builder.header(name, value);\n        }\n    }\n\n    public static class WebClientResponse implements HttpResponse {\n        private final Throwable caught;\n        private final ClientResponse response;\n\n        public WebClientResponse(Throwable caught, ClientResponse response) {\n            this.caught = caught;\n            this.response = response;\n        }\n\n        @Override\n        public String method() {\n            return null;\n        }\n\n        @Override\n        public String route() {\n            return null;\n        }\n\n        @Override\n        public int statusCode() {\n            return response == null ? 0 : response.rawStatusCode();\n        }\n\n        @Override\n        public Throwable maybeError() {\n            return caught;\n        }\n\n        @Override\n        public String header(String name) {\n            if (response == null) {\n                return null;\n            }\n            return getFirstHeaderValue(response.headers(), name);\n        }\n    }\n\n    private static String getFirstHeaderValue(ClientResponse.Headers headers, String name) {\n        Collection<String> values = headers.header(name);\n        if (values.isEmpty()) {\n            return null;\n        }\n        return values.iterator().next();\n    }\n}\n"
  },
  {
    "path": "plugins/springweb/src/main/java/com/megaease/easeagent/plugin/springweb/reactor/AgentCoreSubscriber.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.springweb.reactor;\n\nimport com.megaease.easeagent.plugin.api.context.RequestContext;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.springweb.interceptor.tracing.WebClientFilterTracingInterceptor.WebClientResponse;\nimport com.megaease.easeagent.plugin.tools.trace.HttpUtils;\nimport org.reactivestreams.Subscription;\nimport org.springframework.web.reactive.function.client.ClientResponse;\nimport reactor.core.CoreSubscriber;\n\nimport javax.annotation.Nonnull;\nimport java.util.concurrent.atomic.AtomicReference;\n\npublic class AgentCoreSubscriber implements CoreSubscriber<ClientResponse> {\n\n    private final CoreSubscriber<ClientResponse> actual;\n    private final MethodInfo methodInfo;\n    // private final Integer chain;\n    private final RequestContext requestContext;\n    private AtomicReference<ClientResponse> result = new AtomicReference<>();\n\n    @SuppressWarnings(\"unchecked\")\n    public AgentCoreSubscriber(CoreSubscriber<? super ClientResponse> actual, MethodInfo methodInfo,\n                               RequestContext context) {\n        this.actual = (CoreSubscriber<ClientResponse>) actual;\n        this.methodInfo = methodInfo;\n        // this.chain = chain;\n        this.requestContext = context;\n    }\n\n    @Override\n    public reactor.util.context.Context currentContext() {\n        return actual.currentContext();\n    }\n\n    @Override\n    public void onSubscribe(@Nonnull Subscription s) {\n        actual.onSubscribe(s);\n    }\n\n    @Override\n    public void onNext(ClientResponse t) {\n        actual.onNext(t);\n        if (result.get() == null) {\n            result.set(t);\n        }\n    }\n\n    @Override\n    public void onError(Throwable t) {\n        actual.onError(t);\n        methodInfo.setThrowable(t);\n        finish();\n        // EaseAgent.dispatcher.exit(chain, methodInfo, getContext(), results, t);\n    }\n\n    @Override\n    public void onComplete() {\n        actual.onComplete();\n        finish();\n    }\n\n    private void finish() {\n        ClientResponse resp = result.get();\n        if (resp != null) {\n            WebClientResponse webClientResponse = new WebClientResponse(methodInfo.getThrowable(), resp);\n            HttpUtils.save(requestContext.span(), webClientResponse);\n            this.requestContext.finish(webClientResponse);\n        } else if (methodInfo.isSuccess()) {\n            requestContext.span().finish();\n        } else {\n            Span span = requestContext.span();\n            span.error(methodInfo.getThrowable());\n            span.finish();\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/springweb/src/main/java/com/megaease/easeagent/plugin/springweb/reactor/AgentMono.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.springweb.reactor;\n\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.api.context.RequestContext;\nimport org.springframework.web.reactive.function.client.ClientResponse;\nimport reactor.core.CoreSubscriber;\nimport reactor.core.publisher.Mono;\n\nimport javax.annotation.Nonnull;\n\npublic class AgentMono extends Mono<ClientResponse> {\n    private final Mono<ClientResponse> source;\n    private final MethodInfo methodInfo;\n    private final RequestContext context;\n\n    public AgentMono(Mono<ClientResponse> source, MethodInfo methodInfo, RequestContext context) {\n        this.source = source;\n        this.methodInfo = methodInfo;\n        this.context = context;\n    }\n\n    @Override\n    public void subscribe(@Nonnull CoreSubscriber<? super ClientResponse> actual) {\n        this.source.subscribe(new AgentCoreSubscriber(actual, methodInfo, context));\n    }\n}\n"
  },
  {
    "path": "plugins/springweb/src/main/java/com/megaease/plugin/easeagent/springweb/interceptor/tracing/WebClientTracingFilter.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.plugin.easeagent.springweb.interceptor.tracing;\n\nimport lombok.NonNull;\nimport org.springframework.web.reactive.function.client.ClientRequest;\nimport org.springframework.web.reactive.function.client.ClientResponse;\nimport org.springframework.web.reactive.function.client.ExchangeFilterFunction;\nimport org.springframework.web.reactive.function.client.ExchangeFunction;\nimport reactor.core.publisher.Mono;\n\npublic class WebClientTracingFilter implements ExchangeFilterFunction {\n\n    @NonNull\n    @Override\n    public Mono<ClientResponse> filter(@NonNull ClientRequest clientRequest, @NonNull ExchangeFunction exchangeFunction) {\n        return exchangeFunction.exchange(clientRequest);\n    }\n}\n"
  },
  {
    "path": "plugins/springweb/src/main/resources/application.yaml",
    "content": "feign:\n  client:\n    config:\n      default:\n        connectTimeout: 5000\n        readTimeout: 5000\n        loggerLevel: basic\n"
  },
  {
    "path": "plugins/springweb/src/test/java/com/megaease/easeagent/plugin/springweb/interceptor/HeadersFieldFinderTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.springweb.interceptor;\n\nimport feign.Request;\nimport org.junit.Test;\n\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.HashMap;\n\nimport static org.junit.Assert.*;\n\npublic class HeadersFieldFinderTest {\n\n    @Test\n    public void getHashMapHeaders() {\n        Request request = RequestUtils.buildFeignClient();\n        HashMap<String, Collection<String>> hashMap = HeadersFieldFinder.getHashMapHeaders(request);\n        assertTrue(hashMap.isEmpty());\n        String key = \"testKey\";\n        String value = \"testValue\";\n        hashMap.put(key, Collections.singletonList(value));\n        assertEquals(value, request.headers().get(key).iterator().next());\n\n        hashMap = HeadersFieldFinder.getHashMapHeaders(request);\n        assertEquals(1, hashMap.size());\n\n        Request request2 = RequestUtils.buildFeignClient();\n        HashMap<String, Collection<String>> hashMap2 = HeadersFieldFinder.getHashMapHeaders(request2);\n        assertTrue(hashMap2.isEmpty());\n        hashMap2.put(key, Collections.singletonList(value));\n        assertEquals(value, request2.headers().get(key).iterator().next());\n\n    }\n}\n"
  },
  {
    "path": "plugins/springweb/src/test/java/com/megaease/easeagent/plugin/springweb/interceptor/RequestUtils.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.springweb.interceptor;\n\nimport feign.Request;\nimport feign.RequestTemplate;\nimport feign.Response;\n\nimport java.util.Arrays;\nimport java.util.Collections;\n\npublic class RequestUtils {\n    public static final String URL = \"http://127.0.0.1:8080\";\n\n    public static Request buildFeignClient() {\n        RequestTemplate requestTemplate = new RequestTemplate();\n        Request request = Request.create(\n            Request.HttpMethod.GET,\n            URL,\n            requestTemplate.headers(),\n            Request.Body.create(requestTemplate.body()),\n            requestTemplate\n        );\n        return request;\n    }\n\n    public static Response.Builder responseBuilder(Request request) {\n        Response.Builder builder = Response.builder();\n        builder.status(200);\n        builder.request(request);\n        builder.headers(Collections.singletonMap(TestConst.RESPONSE_TAG_NAME, Arrays.asList(TestConst.RESPONSE_TAG_VALUE)));\n        builder.body(\"test\".getBytes());\n        return builder;\n    }\n}\n"
  },
  {
    "path": "plugins/springweb/src/test/java/com/megaease/easeagent/plugin/springweb/interceptor/TestConst.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.springweb.interceptor;\n\npublic class TestConst {\n    public static final String FORWARDED_NAME = \"X-Forwarded-For\";\n    public static final String FORWARDED_VALUE = \"testForwarded\";\n    public static final String RESPONSE_TAG_NAME = \"X-EG-Test\";\n    public static final String RESPONSE_TAG_VALUE = \"X-EG-Test-Value\";\n}\n"
  },
  {
    "path": "plugins/springweb/src/test/java/com/megaease/easeagent/plugin/springweb/interceptor/forwarded/FeignClientForwardedInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.springweb.interceptor.forwarded;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.springweb.interceptor.RequestUtils;\nimport com.megaease.easeagent.plugin.springweb.interceptor.TestConst;\nimport feign.Request;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport java.util.Collection;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class FeignClientForwardedInterceptorTest {\n\n    @Test\n    public void before() {\n        FeignClientForwardedInterceptor feignClientForwardedInterceptor = new FeignClientForwardedInterceptor();\n        Request request = RequestUtils.buildFeignClient();\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{request}).build();\n        Context context = EaseAgent.getContext();\n        feignClientForwardedInterceptor.before(methodInfo, context);\n        assertNull(header(request, TestConst.FORWARDED_NAME));\n        context.put(TestConst.FORWARDED_NAME, TestConst.FORWARDED_VALUE);\n        try {\n            feignClientForwardedInterceptor.before(methodInfo, context);\n            assertNotNull(header(request, TestConst.FORWARDED_NAME));\n            assertEquals(TestConst.FORWARDED_VALUE, header(request, TestConst.FORWARDED_NAME));\n        } finally {\n            context.remove(TestConst.FORWARDED_NAME);\n        }\n\n    }\n\n    public String header(Request request, String name) {\n        Collection<String> collection = request.headers().get(name);\n        if (collection == null || collection.isEmpty()) {\n            return null;\n        }\n        return collection.iterator().next();\n    }\n\n    @Test\n    public void getType() {\n        FeignClientForwardedInterceptor feignClientForwardedInterceptor = new FeignClientForwardedInterceptor();\n        assertEquals(ConfigConst.PluginID.FORWARDED, feignClientForwardedInterceptor.getType());\n\n    }\n}\n"
  },
  {
    "path": "plugins/springweb/src/test/java/com/megaease/easeagent/plugin/springweb/interceptor/forwarded/RestTemplateForwardedInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.springweb.interceptor.forwarded;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.springweb.interceptor.TestConst;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.http.HttpMethod;\nimport org.springframework.http.client.ClientHttpRequest;\nimport org.springframework.http.client.ClientHttpRequestFactory;\nimport org.springframework.http.client.SimpleClientHttpRequestFactory;\n\nimport java.io.IOException;\nimport java.net.URI;\nimport java.net.URISyntaxException;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class RestTemplateForwardedInterceptorTest {\n\n    @Test\n    public void before() throws URISyntaxException, IOException {\n        ClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();\n        ClientHttpRequest request = requestFactory.createRequest(new URI(\"http://127.0.0.1:8080/test\"), HttpMethod.GET);\n        RestTemplateForwardedInterceptor restTemplateForwardedInterceptor = new RestTemplateForwardedInterceptor();\n\n        MethodInfo methodInfo = MethodInfo.builder().invoker(request).build();\n        Context context = EaseAgent.getContext();\n        restTemplateForwardedInterceptor.before(methodInfo, context);\n        assertNull(request.getHeaders().getFirst(TestConst.FORWARDED_NAME));\n        context.put(TestConst.FORWARDED_NAME, TestConst.FORWARDED_VALUE);\n        try {\n            restTemplateForwardedInterceptor.before(methodInfo, context);\n            assertNotNull(request.getHeaders().get(TestConst.FORWARDED_NAME));\n            assertEquals(TestConst.FORWARDED_VALUE, request.getHeaders().getFirst(TestConst.FORWARDED_NAME));\n        } finally {\n            context.remove(TestConst.FORWARDED_NAME);\n        }\n\n\n    }\n\n    @Test\n    public void getType() {\n        RestTemplateForwardedInterceptor restTemplateForwardedInterceptor = new RestTemplateForwardedInterceptor();\n        assertEquals(ConfigConst.PluginID.FORWARDED, restTemplateForwardedInterceptor.getType());\n\n    }\n}\n"
  },
  {
    "path": "plugins/springweb/src/test/java/com/megaease/easeagent/plugin/springweb/interceptor/forwarded/WebClientFilterForwardedInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.springweb.interceptor.forwarded;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.mock.plugin.api.utils.ConfigTestUtils;\nimport com.megaease.easeagent.mock.plugin.api.utils.InterceptorTestUtils;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.api.config.Const;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.springweb.ForwardedPlugin;\nimport com.megaease.easeagent.plugin.springweb.interceptor.TestConst;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.web.reactive.function.client.*;\nimport reactor.core.publisher.Mono;\n\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.atomic.AtomicReference;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class WebClientFilterForwardedInterceptorTest {\n\n    @Test\n    public void init() {\n        WebClientFilterForwardedInterceptor interceptor = new WebClientFilterForwardedInterceptor();\n        InterceptorTestUtils.initByUnique(interceptor, new ForwardedPlugin());\n        assertNotNull(WebClientFilterForwardedInterceptor.AUTO_CONFIG);\n    }\n\n    @Test\n    public void before() {\n        WebClientFilterForwardedInterceptor interceptor = new WebClientFilterForwardedInterceptor();\n        WebClient.Builder builder = WebClient.builder();\n        MethodInfo methodInfo = MethodInfo.builder().invoker(builder).build();\n        interceptor.before(methodInfo, null);\n        AtomicReference<ExchangeFilterFunction> filter = new AtomicReference<>();\n        builder.filters(exchangeFilterFunctions -> {\n            for (ExchangeFilterFunction exchangeFilterFunction : exchangeFilterFunctions) {\n                filter.set(exchangeFilterFunction);\n            }\n        });\n        assertNotNull(filter.get());\n        assertTrue(filter.get() instanceof WebClientFilterForwardedInterceptor.WebClientForwardedFilter);\n    }\n\n    @Test\n    public void testWebClientForwardedFilter() throws URISyntaxException {\n        WebClientFilterForwardedInterceptor interceptor = new WebClientFilterForwardedInterceptor();\n        InterceptorTestUtils.initByUnique(interceptor, new ForwardedPlugin());\n        WebClientFilterForwardedInterceptor.WebClientForwardedFilter webClientForwardedFilter = interceptor.new WebClientForwardedFilter();\n        URI uri = new URI(\"http://127.0.0.1:8080\");\n        ClientRequest clientRequest = MockClientRequest.build(uri);\n        try (ConfigTestUtils.Reset ignored = ConfigTestUtils.changeBoolean(WebClientFilterForwardedInterceptor.AUTO_CONFIG, Const.ENABLED_CONFIG, false)) {\n            assertFalse(WebClientFilterForwardedInterceptor.AUTO_CONFIG.enabled());\n            MockExchangeFunction mockExchangeFunction = new MockExchangeFunction();\n            webClientForwardedFilter.filter(clientRequest, mockExchangeFunction);\n            assertTrue(mockExchangeFunction.ran.get());\n            assertSame(clientRequest, mockExchangeFunction.clientRequest);\n        }\n        assertTrue(WebClientFilterForwardedInterceptor.AUTO_CONFIG.enabled());\n        MockExchangeFunction mockExchangeFunction = new MockExchangeFunction();\n        webClientForwardedFilter.filter(clientRequest, mockExchangeFunction);\n        assertTrue(mockExchangeFunction.ran.get());\n        assertSame(clientRequest, mockExchangeFunction.clientRequest);\n\n        EaseAgent.getContext().put(TestConst.FORWARDED_NAME, TestConst.FORWARDED_VALUE);\n        mockExchangeFunction = new MockExchangeFunction();\n        webClientForwardedFilter.filter(clientRequest, mockExchangeFunction);\n        assertTrue(mockExchangeFunction.ran.get());\n        assertNotSame(clientRequest, mockExchangeFunction.clientRequest);\n        assertEquals(TestConst.FORWARDED_VALUE, mockExchangeFunction.clientRequest.headers().getFirst(TestConst.FORWARDED_NAME));\n    }\n\n    class MockExchangeFunction implements ExchangeFunction {\n        protected final AtomicBoolean ran = new AtomicBoolean(false);\n        protected ClientRequest clientRequest;\n\n        @Override\n        public Mono<ClientResponse> exchange(ClientRequest clientRequest) {\n            ran.set(true);\n            this.clientRequest = clientRequest;\n            return null;\n        }\n    }\n\n    @Test\n    public void testRequest() throws URISyntaxException {\n        URI uri = new URI(\"http://127.0.0.1:8080\");\n        ClientRequest clientRequest = MockClientRequest.build(uri);\n        WebClientFilterForwardedInterceptor.Request request = new WebClientFilterForwardedInterceptor.Request(clientRequest);\n        assertSame(clientRequest, request.get());\n        String key = \"testKey\";\n        String value = \"testValue\";\n        request.setHeader(key, value);\n        ClientRequest newClientRequest = request.get();\n        assertNotSame(clientRequest, newClientRequest);\n        assertEquals(value, newClientRequest.headers().getFirst(key));\n    }\n\n    @Test\n    public void getType() {\n        WebClientFilterForwardedInterceptor interceptor = new WebClientFilterForwardedInterceptor();\n        assertEquals(ConfigConst.PluginID.FORWARDED, interceptor.getType());\n    }\n}\n"
  },
  {
    "path": "plugins/springweb/src/test/java/com/megaease/easeagent/plugin/springweb/interceptor/initialize/WebClientBuildInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.springweb.interceptor.initialize;\n\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.springweb.interceptor.forwarded.WebClientFilterForwardedInterceptor;\nimport com.megaease.plugin.easeagent.springweb.interceptor.tracing.WebClientTracingFilter;\nimport org.junit.Test;\nimport org.springframework.web.reactive.function.client.ExchangeFilterFunction;\nimport org.springframework.web.reactive.function.client.WebClient;\n\nimport java.util.concurrent.atomic.AtomicReference;\n\nimport static org.junit.Assert.*;\n\npublic class WebClientBuildInterceptorTest {\n\n    @Test\n    public void before() {\n        WebClientBuildInterceptor interceptor = new WebClientBuildInterceptor();\n        WebClient.Builder builder = WebClient.builder();\n        MethodInfo methodInfo = MethodInfo.builder().invoker(builder).build();\n        interceptor.before(methodInfo, null);\n        AtomicReference<ExchangeFilterFunction> filter = new AtomicReference<>();\n        builder.filters(exchangeFilterFunctions -> {\n            for (ExchangeFilterFunction exchangeFilterFunction : exchangeFilterFunctions) {\n                filter.set(exchangeFilterFunction);\n            }\n        });\n        assertNotNull(filter.get());\n        assertTrue(filter.get() instanceof WebClientTracingFilter);\n    }\n\n    @Test\n    public void order() {\n        WebClientBuildInterceptor interceptor = new WebClientBuildInterceptor();\n        assertEquals(Order.TRACING_INIT.getOrder(), interceptor.order());\n    }\n\n    @Test\n    public void getType() {\n        WebClientBuildInterceptor interceptor = new WebClientBuildInterceptor();\n        assertEquals(ConfigConst.PluginID.TRACING, interceptor.getType());\n    }\n}\n"
  },
  {
    "path": "plugins/springweb/src/test/java/com/megaease/easeagent/plugin/springweb/interceptor/tracing/ClientHttpRequestInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.springweb.interceptor.tracing;\n\nimport com.megaease.easeagent.mock.plugin.api.MockEaseAgent;\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.trace.Scope;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport com.megaease.easeagent.plugin.springweb.interceptor.TestConst;\nimport com.megaease.easeagent.plugin.tools.trace.HttpRequest;\nimport com.megaease.easeagent.plugin.tools.trace.HttpResponse;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.http.HttpMethod;\nimport org.springframework.http.client.*;\n\nimport java.io.IOException;\nimport java.net.URI;\nimport java.net.URISyntaxException;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class ClientHttpRequestInterceptorTest {\n\n    @Test\n    public void testRestTemplate() throws URISyntaxException, IOException {\n        String url = \"http://127.0.0.1:8080/test\";\n\n        ClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();\n        ClientHttpRequest request = requestFactory.createRequest(new URI(url), HttpMethod.GET);\n        ClientHttpResponse clientHttpResponse = SimpleClientHttpResponseFactory.createMockResponse(url);\n        MethodInfo.MethodInfoBuilder methodInfoBuilder = MethodInfo.builder();\n        MethodInfo methodInfo = methodInfoBuilder.invoker(request).build();\n        Context context = EaseAgent.getContext();\n        ClientHttpRequestInterceptor clientHttpRequestInterceptor = new ClientHttpRequestInterceptor();\n        MockEaseAgent.cleanLastSpan();\n\n        clientHttpRequestInterceptor.before(methodInfo, context);\n        methodInfo = methodInfoBuilder.retValue(clientHttpResponse).build();\n        clientHttpRequestInterceptor.after(methodInfo, context);\n\n        ReportSpan mockSpan = MockEaseAgent.getLastSpan();\n        assertNotNull(mockSpan);\n        assertEquals(Span.Kind.CLIENT.name(), mockSpan.kind());\n        assertEquals(TestConst.RESPONSE_TAG_VALUE, mockSpan.tag(TestConst.RESPONSE_TAG_NAME));\n        assertNull(mockSpan.parentId());\n\n        request = requestFactory.createRequest(new URI(url), HttpMethod.GET);\n        methodInfo = methodInfoBuilder.invoker(request).build();\n        clientHttpRequestInterceptor.before(methodInfo, context);\n        methodInfo = methodInfoBuilder.retValue(clientHttpResponse).build();\n        RuntimeException runtimeException = new RuntimeException(\"test error\");\n        methodInfo.throwable(runtimeException);\n        clientHttpRequestInterceptor.after(methodInfo, context);\n\n        mockSpan = MockEaseAgent.getLastSpan();\n        assertNotNull(mockSpan);\n        assertEquals(Span.Kind.CLIENT.name(), mockSpan.kind());\n        assertEquals(TestConst.RESPONSE_TAG_VALUE, mockSpan.tag(TestConst.RESPONSE_TAG_NAME));\n        assertNull(mockSpan.parentId());\n\n\n        request = requestFactory.createRequest(new URI(url), HttpMethod.GET);\n        methodInfo = methodInfoBuilder.invoker(request).build();\n        Span span = context.nextSpan();\n        try (Scope ignored = span.maybeScope()) {\n            clientHttpRequestInterceptor.before(methodInfo, context);\n            methodInfo = methodInfoBuilder.retValue(clientHttpResponse).build();\n            clientHttpRequestInterceptor.after(methodInfo, context);\n            mockSpan = MockEaseAgent.getLastSpan();\n            assertEquals(span.traceIdString(), mockSpan.traceId());\n            assertEquals(span.spanIdString(), mockSpan.parentId());\n            assertNotNull(mockSpan.id());\n        }\n        span.finish();\n    }\n\n    @Test\n    public void getRequest() throws URISyntaxException, IOException {\n        ClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();\n        ClientHttpRequest request = requestFactory.createRequest(new URI(\"http://127.0.0.1:8080/test\"), HttpMethod.GET);\n        MethodInfo methodInfo = MethodInfo.builder().invoker(request).build();\n        Context context = EaseAgent.getContext();\n\n        ClientHttpRequestInterceptor clientHttpRequestInterceptor = new ClientHttpRequestInterceptor();\n        HttpRequest httpRequest = clientHttpRequestInterceptor.getRequest(methodInfo, context);\n        assertEquals(com.megaease.easeagent.plugin.api.trace.Span.Kind.CLIENT, httpRequest.kind());\n        assertEquals(\"GET\", httpRequest.method());\n        assertEquals(\"/test\", httpRequest.path());\n    }\n\n    @Test\n    public void getResponse() throws IOException, URISyntaxException {\n        String url = \"http://127.0.0.1:8080/test\";\n        ClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();\n        ClientHttpRequest request = requestFactory.createRequest(new URI(url), HttpMethod.GET);\n        ClientHttpResponse clientHttpResponse = SimpleClientHttpResponseFactory.createMockResponse(url);\n        MethodInfo.MethodInfoBuilder methodInfoBuilder = MethodInfo.builder().invoker(request).retValue(clientHttpResponse);\n        MethodInfo methodInfo = methodInfoBuilder.build();\n\n        ClientHttpRequestInterceptor clientHttpRequestInterceptor = new ClientHttpRequestInterceptor();\n        HttpResponse httpResponse = clientHttpRequestInterceptor.getResponse(methodInfo, EaseAgent.getContext());\n        assertEquals(\"GET\", httpResponse.method());\n        assertNull(httpResponse.route());\n        assertNull(httpResponse.maybeError());\n        assertEquals(200, httpResponse.statusCode());\n\n        RuntimeException runtimeException = new RuntimeException(\"test error\");\n        methodInfoBuilder.throwable(runtimeException);\n        httpResponse = clientHttpRequestInterceptor.getResponse(methodInfoBuilder.build(), EaseAgent.getContext());\n        assertEquals(runtimeException, httpResponse.maybeError());\n\n\n    }\n}\n"
  },
  {
    "path": "plugins/springweb/src/test/java/com/megaease/easeagent/plugin/springweb/interceptor/tracing/FeignClientTracingInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.springweb.interceptor.tracing;\n\nimport com.megaease.easeagent.mock.plugin.api.MockEaseAgent;\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.trace.Scope;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport com.megaease.easeagent.plugin.springweb.interceptor.RequestUtils;\nimport com.megaease.easeagent.plugin.springweb.interceptor.TestConst;\nimport com.megaease.easeagent.plugin.tools.trace.HttpRequest;\nimport com.megaease.easeagent.plugin.tools.trace.HttpResponse;\nimport feign.Request;\nimport feign.Response;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static org.junit.Assert.*;\n\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class FeignClientTracingInterceptorTest {\n\n    @Test\n    public void testFeignTracing() {\n        Request request = RequestUtils.buildFeignClient();\n        MethodInfo.MethodInfoBuilder methodInfoBuilder = MethodInfo.builder();\n        MethodInfo methodInfo = methodInfoBuilder.args(new Object[]{request}).build();\n\n        Context context = EaseAgent.getContext();\n        FeignClientTracingInterceptor feignClientTracingInterceptor = new FeignClientTracingInterceptor();\n        MockEaseAgent.cleanLastSpan();\n\n        feignClientTracingInterceptor.before(methodInfo, context);\n        methodInfo = methodInfoBuilder.retValue(RequestUtils.responseBuilder(request).build()).build();\n        feignClientTracingInterceptor.after(methodInfo, context);\n\n        ReportSpan mockSpan = MockEaseAgent.getLastSpan();\n        assertNotNull(mockSpan);\n        assertEquals(Span.Kind.CLIENT.name(), mockSpan.kind());\n        assertEquals(TestConst.RESPONSE_TAG_VALUE, mockSpan.tag(TestConst.RESPONSE_TAG_NAME));\n        assertNull(mockSpan.parentId());\n\n        request = RequestUtils.buildFeignClient();\n        methodInfo = methodInfoBuilder.args(new Object[]{request}).build();\n        feignClientTracingInterceptor.before(methodInfo, context);\n        methodInfo.retValue(RequestUtils.responseBuilder(request).build());\n        RuntimeException runtimeException = new RuntimeException(\"test error\");\n        methodInfo.throwable(runtimeException);\n        feignClientTracingInterceptor.after(methodInfo, context);\n\n        mockSpan = MockEaseAgent.getLastSpan();\n        assertNotNull(mockSpan);\n        assertEquals(Span.Kind.CLIENT.name(), mockSpan.kind());\n        assertEquals(TestConst.RESPONSE_TAG_VALUE, mockSpan.tag(TestConst.RESPONSE_TAG_NAME));\n        assertNull(mockSpan.parentId());\n\n\n        request = RequestUtils.buildFeignClient();\n        methodInfo = methodInfoBuilder.args(new Object[]{request}).build();\n        Span span = context.nextSpan();\n        try (Scope ignored = span.maybeScope()) {\n            feignClientTracingInterceptor.before(methodInfo, context);\n            methodInfo.retValue(RequestUtils.responseBuilder(request).build());\n            feignClientTracingInterceptor.after(methodInfo, context);\n            mockSpan = MockEaseAgent.getLastSpan();\n            assertEquals(span.traceIdString(), mockSpan.traceId());\n            assertEquals(span.spanIdString(), mockSpan.parentId());\n            assertNotNull(mockSpan.id());\n        }\n        span.finish();\n    }\n\n    @Test\n    public void getRequest() {\n        Request request = RequestUtils.buildFeignClient();\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{request}).build();\n        Context context = EaseAgent.getContext();\n        FeignClientTracingInterceptor feignClientTracingInterceptor = new FeignClientTracingInterceptor();\n        HttpRequest httpRequest = feignClientTracingInterceptor.getRequest(methodInfo, context);\n        assertEquals(com.megaease.easeagent.plugin.api.trace.Span.Kind.CLIENT, httpRequest.kind());\n        assertEquals(\"GET\", httpRequest.method());\n        assertEquals(RequestUtils.URL, httpRequest.path());\n\n    }\n\n    @Test\n    public void getResponse() {\n        Request request = RequestUtils.buildFeignClient();\n        Response response = RequestUtils.responseBuilder(request).build();\n\n        MethodInfo.MethodInfoBuilder methodInfoBuilder = MethodInfo.builder().args(new Object[]{request}).retValue(response);\n        MethodInfo methodInfo = methodInfoBuilder.build();\n\n        FeignClientTracingInterceptor feignClientTracingInterceptor = new FeignClientTracingInterceptor();\n        HttpResponse httpResponse = feignClientTracingInterceptor.getResponse(methodInfo, EaseAgent.getContext());\n        assertEquals(\"GET\", httpResponse.method());\n        assertNull(httpResponse.route());\n        assertNull(httpResponse.maybeError());\n        assertEquals(200, httpResponse.statusCode());\n\n        RuntimeException runtimeException = new RuntimeException(\"test error\");\n        methodInfoBuilder.throwable(runtimeException);\n        httpResponse = feignClientTracingInterceptor.getResponse(methodInfoBuilder.build(), EaseAgent.getContext());\n        assertEquals(runtimeException, httpResponse.maybeError());\n    }\n}\n"
  },
  {
    "path": "plugins/springweb/src/test/java/com/megaease/easeagent/plugin/springweb/interceptor/tracing/WebClientFilterTracingInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.springweb.interceptor.tracing;\n\nimport com.megaease.easeagent.mock.plugin.api.MockEaseAgent;\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.mock.plugin.api.utils.SpanTestUtils;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.RequestContext;\nimport com.megaease.easeagent.plugin.api.trace.Scope;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport com.megaease.easeagent.plugin.springweb.reactor.AgentMono;\nimport com.megaease.easeagent.plugin.springweb.reactor.MockMono;\nimport com.megaease.easeagent.plugin.tools.trace.HttpRequest;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.web.reactive.function.client.ClientRequest;\nimport org.springframework.web.reactive.function.client.ClientResponse;\nimport org.springframework.web.reactive.function.client.MockClientRequest;\nimport org.springframework.web.reactive.function.client.MockDefaultClientResponse;\n\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.util.Collection;\nimport java.util.Objects;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class WebClientFilterTracingInterceptorTest {\n    String url = \"http://127.0.0.1:8080/test\";\n\n    @Test\n    public void getProgressKey() {\n        WebClientFilterTracingInterceptor interceptor = new WebClientFilterTracingInterceptor();\n        assertSame(AgentFieldReflectAccessor.getStaticFieldValue(WebClientFilterTracingInterceptor.class, \"PROGRESS_CONTEXT\"), interceptor.getProgressKey());\n    }\n\n    @Test\n    public void doBefore() throws URISyntaxException {\n        WebClientFilterTracingInterceptor interceptor = new WebClientFilterTracingInterceptor();\n        Context context = EaseAgent.getContext();\n        URI uri = new URI(url);\n        ClientRequest clientRequest = MockClientRequest.build(uri);\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{clientRequest}).build();\n        interceptor.doBefore(methodInfo, context);\n        RequestContext requestContext = context.get(interceptor.getProgressKey());\n        assertNotNull(requestContext);\n        requestContext.scope().close();\n        requestContext.span().finish();\n        assertNull(requestContext.span().parentIdString());\n        ReportSpan mockSpan = MockEaseAgent.getLastSpan();\n        SpanTestUtils.sameId(requestContext.span(), mockSpan);\n\n        ClientRequest request = (ClientRequest) methodInfo.getArgs()[0];\n        Collection<String> headers = request.headers().toSingleValueMap().values();\n        assertTrue(headers.contains(mockSpan.traceId()));\n        assertTrue(headers.contains(mockSpan.id()));\n\n        Span span = context.nextSpan().start();\n        try (Scope ignored = span.maybeScope()) {\n            clientRequest = MockClientRequest.build(uri);\n            methodInfo = MethodInfo.builder().args(new Object[]{clientRequest}).build();\n            interceptor.doBefore(methodInfo, context);\n            requestContext = context.get(interceptor.getProgressKey());\n            requestContext.scope().close();\n            requestContext.span().finish();\n            mockSpan = MockEaseAgent.getLastSpan();\n            assertEquals(span.traceIdString(), mockSpan.traceId());\n            assertEquals(span.spanIdString(), mockSpan.parentId());\n        }\n        span.finish();\n    }\n\n    @Test\n    public void doAfter() throws URISyntaxException {\n        WebClientFilterTracingInterceptor interceptor = new WebClientFilterTracingInterceptor();\n        Context context = EaseAgent.getContext();\n        URI uri = new URI(url);\n        ClientRequest clientRequest = MockClientRequest.build(uri);\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{clientRequest}).retValue(new MockMono()).build();\n        interceptor.doBefore(methodInfo, context);\n        assertTrue(context.currentTracing().hasCurrentSpan());\n        MockEaseAgent.cleanLastSpan();\n        interceptor.doAfter(methodInfo, context);\n        assertFalse(context.currentTracing().hasCurrentSpan());\n        assertNull(MockEaseAgent.getLastSpan());\n        assertTrue(methodInfo.getRetValue() instanceof AgentMono);\n        RequestContext pCtx =  context.get(interceptor.getProgressKey());\n        pCtx.span().abandon();\n\n        String errorInfo = \"test error\";\n        clientRequest = MockClientRequest.build(uri);\n        methodInfo = MethodInfo.builder().args(new Object[]{clientRequest}).retValue(new MockMono()).throwable(new RuntimeException(errorInfo)).build();\n        interceptor.doBefore(methodInfo, context);\n        assertTrue(context.currentTracing().hasCurrentSpan());\n        interceptor.doAfter(methodInfo, context);\n        assertFalse(context.currentTracing().hasCurrentSpan());\n\n        assertTrue(methodInfo.getRetValue() instanceof AgentMono);\n        ReportSpan mockSpan = MockEaseAgent.getLastSpan();\n        assertNotNull(mockSpan);\n        assertTrue(mockSpan.hasError());\n        assertEquals(errorInfo, mockSpan.errorInfo());\n    }\n\n    @Test\n    public void getRequest() throws URISyntaxException {\n        URI uri = new URI(url);\n        ClientRequest clientRequest = MockClientRequest.build(uri);\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{clientRequest}).build();\n        WebClientFilterTracingInterceptor interceptor = new WebClientFilterTracingInterceptor();\n        HttpRequest httpRequest = interceptor.getRequest(methodInfo);\n        check(httpRequest);\n        String key = \"testKey\";\n        String value = \"testValue\";\n        httpRequest.setHeader(key, value);\n        assertNull(httpRequest.header(key));\n        assertEquals(value, Objects.requireNonNull(((ClientRequest) methodInfo.getArgs()[0]).headers().get(key)).get(0));\n    }\n\n    private void check(HttpRequest httpRequest) {\n        assertEquals(\"GET\", httpRequest.method());\n        assertEquals(\"/test\", httpRequest.path());\n        assertNull(httpRequest.route());\n        assertEquals(url, httpRequest.getRemoteAddr());\n        assertEquals(0, httpRequest.getRemotePort());\n        assertEquals(Span.Kind.CLIENT, httpRequest.kind());\n        assertFalse(httpRequest.cacheScope());\n    }\n\n    @Test\n    public void testWebClientRequest() throws URISyntaxException {\n        URI uri = new URI(url);\n        ClientRequest clientRequest = MockClientRequest.build(uri);\n        ClientRequest.Builder builder = ClientRequest.from(clientRequest);\n        WebClientFilterTracingInterceptor.WebClientRequest webClientRequest = new WebClientFilterTracingInterceptor.WebClientRequest(clientRequest, builder);\n        check(webClientRequest);\n        String key = \"testKey\";\n        String value = \"testValue\";\n        webClientRequest.setHeader(key, value);\n        assertNull(webClientRequest.header(key));\n        assertEquals(value, Objects.requireNonNull(builder.build().headers().get(key)).get(0));\n    }\n\n    @Test\n    public void testWebClientResponse() {\n        String key = \"testKey\";\n        String value = \"testValue\";\n        WebClientFilterTracingInterceptor.WebClientResponse webClientResponse = new WebClientFilterTracingInterceptor.WebClientResponse(null, null);\n        assertNull(webClientResponse.header(key));\n        assertEquals(0, webClientResponse.statusCode());\n        assertNull(webClientResponse.maybeError());\n        assertNull(webClientResponse.method());\n        assertNull(webClientResponse.route());\n\n        ClientResponse clientResponse = MockDefaultClientResponse.builder().addHeader(key, value).build();\n        webClientResponse = new WebClientFilterTracingInterceptor.WebClientResponse(null, clientResponse);\n        assertEquals(value, webClientResponse.header(key));\n        assertEquals(200, webClientResponse.statusCode());\n        assertNull(webClientResponse.maybeError());\n        assertNull(webClientResponse.method());\n        assertNull(webClientResponse.route());\n\n        Throwable throwable = new RuntimeException(\"test error\");\n        webClientResponse = new WebClientFilterTracingInterceptor.WebClientResponse(throwable, clientResponse);\n        assertSame(throwable, webClientResponse.maybeError());\n\n\n    }\n}\n"
  },
  {
    "path": "plugins/springweb/src/test/java/com/megaease/easeagent/plugin/springweb/reactor/AgentCoreSubscriberTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.springweb.reactor;\n\nimport com.megaease.easeagent.mock.plugin.api.MockEaseAgent;\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.RequestContext;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport com.megaease.easeagent.plugin.springweb.interceptor.tracing.WebClientFilterTracingInterceptor;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.reactivestreams.Subscription;\nimport org.springframework.web.reactive.function.client.ClientRequest;\nimport org.springframework.web.reactive.function.client.ClientResponse;\nimport org.springframework.web.reactive.function.client.MockClientRequest;\nimport org.springframework.web.reactive.function.client.MockDefaultClientResponse;\n\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.util.concurrent.atomic.AtomicReference;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class AgentCoreSubscriberTest {\n    String url = \"http://127.0.0.1:8080/test\";\n\n    private AgentCoreSubscriber createOne(MockCoreSubscriber mockCoreSubscriber) throws URISyntaxException {\n        WebClientFilterTracingInterceptor interceptor = new WebClientFilterTracingInterceptor();\n        Context context = EaseAgent.getContext();\n        URI uri = new URI(url);\n        ClientRequest clientRequest = MockClientRequest.build(uri);\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{clientRequest}).retValue(new MockMono()).build();\n        interceptor.doBefore(methodInfo, context);\n        RequestContext requestContext = context.get(interceptor.getProgressKey());\n\n        AgentCoreSubscriber agentCoreSubscriber = new AgentCoreSubscriber(mockCoreSubscriber, methodInfo, requestContext);\n\n        requestContext.span().start().finish();\n        requestContext.scope().close();\n\n        return agentCoreSubscriber;\n    }\n\n    @Test\n    public void currentContext() throws URISyntaxException {\n        MockCoreSubscriber mockCoreSubscriber = new MockCoreSubscriber();\n        AgentCoreSubscriber agentCoreSubscriber = createOne(mockCoreSubscriber);\n        assertTrue(agentCoreSubscriber.currentContext().isEmpty());\n    }\n\n    @Test\n    public void onSubscribe() throws URISyntaxException {\n        MockCoreSubscriber mockCoreSubscriber = new MockCoreSubscriber();\n        AgentCoreSubscriber agentCoreSubscriber = createOne(mockCoreSubscriber);\n        agentCoreSubscriber.onSubscribe(new Subscription() {\n            @Override\n            public void request(long l) {\n\n            }\n\n            @Override\n            public void cancel() {\n\n            }\n        });\n        assertTrue(mockCoreSubscriber.onSubscribe.get());\n    }\n\n\n    @Test\n    public void onNext() throws URISyntaxException {\n        MockCoreSubscriber mockCoreSubscriber = new MockCoreSubscriber();\n        AgentCoreSubscriber agentCoreSubscriber = createOne(mockCoreSubscriber);\n        ClientResponse clientResponse = MockDefaultClientResponse.builder().build();\n        agentCoreSubscriber.onNext(clientResponse);\n        assertTrue(mockCoreSubscriber.onNext.get());\n        AtomicReference<ClientResponse> result = AgentFieldReflectAccessor.getFieldValue(agentCoreSubscriber, \"result\");\n        assertSame(clientResponse, result.get());\n\n        ClientResponse clientResponse2 = MockDefaultClientResponse.builder().build();\n        agentCoreSubscriber.onNext(clientResponse2);\n        assertSame(clientResponse, result.get());\n    }\n\n    @Test\n    public void onError() throws URISyntaxException {\n        MockCoreSubscriber mockCoreSubscriber = new MockCoreSubscriber();\n        String errorInfo = \"test error\";\n        WebClientFilterTracingInterceptor interceptor = new WebClientFilterTracingInterceptor();\n        Context context = EaseAgent.getContext();\n\n        URI uri = new URI(url);\n        ClientRequest clientRequest = MockClientRequest.build(uri);\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{clientRequest}).retValue(new MockMono()).build();\n        interceptor.doBefore(methodInfo, context);\n        RequestContext requestContext = context.get(interceptor.getProgressKey());\n        interceptor.doAfter(methodInfo, context);\n\n        AgentCoreSubscriber agentCoreSubscriber = new AgentCoreSubscriber(mockCoreSubscriber, methodInfo, requestContext);\n        agentCoreSubscriber.onError(new RuntimeException(errorInfo));\n        assertFalse(methodInfo.isSuccess());\n        ReportSpan mockSpan = MockEaseAgent.getLastSpan();\n        assertTrue(mockSpan.hasError());\n        assertEquals(errorInfo, mockSpan.errorInfo());\n        assertTrue(mockCoreSubscriber.onError.get());\n\n        MockEaseAgent.cleanLastSpan();\n\n        methodInfo = MethodInfo.builder().args(new Object[]{clientRequest}).retValue(new MockMono()).build();\n        interceptor.doBefore(methodInfo, context);\n        requestContext = context.get(interceptor.getProgressKey());\n        interceptor.doAfter(methodInfo, context);\n        agentCoreSubscriber = new AgentCoreSubscriber(mockCoreSubscriber, methodInfo, requestContext);\n        ClientResponse clientResponse = MockDefaultClientResponse.builder(500).build();\n        agentCoreSubscriber.onNext(clientResponse);\n        agentCoreSubscriber.onError(new RuntimeException(errorInfo));\n        assertFalse(methodInfo.isSuccess());\n        mockSpan = MockEaseAgent.getLastSpan();\n        assertTrue(mockSpan.hasError());\n        assertEquals(errorInfo, mockSpan.errorInfo());\n        assertEquals(\"500\", mockSpan.tag(\"http.status_code\"));\n    }\n\n    @Test\n    public void onComplete() throws URISyntaxException {\n        MockCoreSubscriber mockCoreSubscriber = new MockCoreSubscriber();\n        WebClientFilterTracingInterceptor interceptor = new WebClientFilterTracingInterceptor();\n        Context context = EaseAgent.getContext();\n\n        URI uri = new URI(url);\n        ClientRequest clientRequest = MockClientRequest.build(uri);\n\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{clientRequest}).retValue(new MockMono()).build();\n        interceptor.doBefore(methodInfo, context);\n        RequestContext requestContext = context.get(interceptor.getProgressKey());\n        interceptor.doAfter(methodInfo, context);\n        AgentCoreSubscriber agentCoreSubscriber = new AgentCoreSubscriber(mockCoreSubscriber, methodInfo, requestContext);\n        agentCoreSubscriber.onComplete();\n        assertTrue(mockCoreSubscriber.onComplete.get());\n        ReportSpan mockSpan = MockEaseAgent.getLastSpan();\n        assertFalse(mockSpan.hasError());\n\n        MockEaseAgent.cleanLastSpan();\n        methodInfo = MethodInfo.builder().args(new Object[]{clientRequest}).retValue(new MockMono()).build();\n        interceptor.doBefore(methodInfo, context);\n        requestContext = context.get(interceptor.getProgressKey());\n        interceptor.doAfter(methodInfo, context);\n        agentCoreSubscriber = new AgentCoreSubscriber(mockCoreSubscriber, methodInfo, requestContext);\n        ClientResponse clientResponse = MockDefaultClientResponse.builder(203).build();\n        agentCoreSubscriber.onNext(clientResponse);\n        agentCoreSubscriber.onComplete();\n        assertTrue(mockCoreSubscriber.onComplete.get());\n        mockSpan = MockEaseAgent.getLastSpan();\n        assertFalse(mockSpan.hasError());\n\n    }\n}\n"
  },
  {
    "path": "plugins/springweb/src/test/java/com/megaease/easeagent/plugin/springweb/reactor/AgentMonoTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.springweb.reactor;\n\nimport org.junit.Test;\nimport org.reactivestreams.Subscription;\nimport org.springframework.web.reactive.function.client.ClientResponse;\nimport reactor.core.CoreSubscriber;\nimport reactor.core.publisher.Mono;\n\nimport java.util.concurrent.atomic.AtomicBoolean;\n\nimport static org.junit.Assert.*;\n\npublic class AgentMonoTest {\n\n    @Test\n    public void subscribe() {\n        AtomicBoolean ran = new AtomicBoolean(false);\n        AgentMono agentMono = new AgentMono(new Mono<ClientResponse>() {\n            @Override\n            public void subscribe(CoreSubscriber<? super ClientResponse> coreSubscriber) {\n                ran.set(true);\n                assertTrue(coreSubscriber instanceof AgentCoreSubscriber);\n            }\n        }, null, null);\n        agentMono.subscribe(new MockCoreSubscriber());\n\n        assertTrue(ran.get());\n    }\n}\n"
  },
  {
    "path": "plugins/springweb/src/test/java/com/megaease/easeagent/plugin/springweb/reactor/MockCoreSubscriber.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.springweb.reactor;\n\nimport org.reactivestreams.Subscription;\nimport org.springframework.web.reactive.function.client.ClientResponse;\nimport reactor.core.CoreSubscriber;\n\nimport java.util.concurrent.atomic.AtomicBoolean;\n\npublic class MockCoreSubscriber implements CoreSubscriber<ClientResponse> {\n    protected final AtomicBoolean onSubscribe = new AtomicBoolean(false);\n    protected final AtomicBoolean onNext = new AtomicBoolean(false);\n    protected final AtomicBoolean onError = new AtomicBoolean(false);\n    protected final AtomicBoolean onComplete = new AtomicBoolean(false);\n\n\n    @Override\n    public void onSubscribe(Subscription subscription) {\n        onSubscribe.set(true);\n    }\n\n    @Override\n    public void onNext(ClientResponse clientResponse) {\n        onNext.set(true);\n    }\n\n    @Override\n    public void onError(Throwable throwable) {\n        onError.set(true);\n    }\n\n    @Override\n    public void onComplete() {\n        onComplete.set(true);\n    }\n}\n"
  },
  {
    "path": "plugins/springweb/src/test/java/com/megaease/easeagent/plugin/springweb/reactor/MockMono.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.springweb.reactor;\n\nimport org.springframework.web.reactive.function.client.ClientResponse;\nimport reactor.core.CoreSubscriber;\nimport reactor.core.publisher.Mono;\n\npublic class MockMono extends Mono<ClientResponse> {\n    @Override\n    public void subscribe(CoreSubscriber<? super ClientResponse> coreSubscriber) {\n\n    }\n}\n"
  },
  {
    "path": "plugins/springweb/src/test/java/org/springframework/http/client/SimpleClientHttpResponseFactory.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.http.client;\n\nimport com.megaease.easeagent.plugin.springweb.interceptor.TestConst;\n\nimport java.io.IOException;\nimport java.net.HttpURLConnection;\nimport java.net.URL;\n\npublic class SimpleClientHttpResponseFactory {\n    public static ClientHttpResponse createMockResponse(String urlStr) throws IOException {\n        URL url = new URL(urlStr);\n        HttpURLConnection httpURLConnection = new HttpURLConnection(url) {\n            @Override\n            public void disconnect() {\n\n            }\n\n            @Override\n            public boolean usingProxy() {\n                return false;\n            }\n\n            @Override\n            public void connect() throws IOException {\n\n            }\n\n            @Override\n            public int getResponseCode() throws IOException {\n                return 200;\n            }\n\n            @Override\n            public String getHeaderFieldKey(int n) {\n                if (n == 0) {\n                    return TestConst.RESPONSE_TAG_NAME;\n                }\n                return super.getHeaderFieldKey(n);\n            }\n\n            @Override\n            public String getHeaderField(int n) {\n                if (n == 0) {\n                    return TestConst.RESPONSE_TAG_VALUE;\n                }\n                return super.getHeaderField(n);\n            }\n        };\n        return new SimpleClientHttpResponse(httpURLConnection);\n    }\n}\n"
  },
  {
    "path": "plugins/springweb/src/test/java/org/springframework/web/reactive/function/client/MockClientRequest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.web.reactive.function.client;\n\nimport org.springframework.http.HttpMethod;\n\nimport java.net.URI;\n\npublic class MockClientRequest {\n    public static ClientRequest build(URI url) {\n        return new DefaultClientRequestBuilder(HttpMethod.GET, url).build();\n    }\n}\n"
  },
  {
    "path": "plugins/springweb/src/test/java/org/springframework/web/reactive/function/client/MockDefaultClientResponse.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.web.reactive.function.client;\n\nimport org.springframework.mock.http.client.reactive.MockClientHttpResponse;\n\npublic class MockDefaultClientResponse {\n    public static Builder builder() {\n        return new Builder(200);\n    }\n\n    public static Builder builder(int status) {\n        return new Builder(status);\n    }\n\n    public static class Builder {\n        MockClientHttpResponse mockClientHttpResponse;\n\n        public Builder(int status) {\n            mockClientHttpResponse = new MockClientHttpResponse(status);\n        }\n\n        public Builder addHeader(String name, String value) {\n            mockClientHttpResponse.getHeaders().add(name, value);\n            return this;\n        }\n\n        public DefaultClientResponse build() {\n            return new DefaultClientResponse(mockClientHttpResponse, null, \"\", \"\", null);\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/springweb/src/test/resources/mock_agent.properties",
    "content": "plugin.integrability.global.forwarded.enabled=true\neaseagent.progress.forwarded.headers=X-Forwarded-For\nobservability.tracings.tag.response.headers.eg.0=X-EG-Test\n"
  },
  {
    "path": "plugins/tomcat-jdk17/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright (c) 2021, MegaEase\n  ~ All rights reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~   http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>plugins</artifactId>\n        <groupId>com.megaease.easeagent</groupId>\n        <version>2.3.0</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>tomcat-jdk17</artifactId>\n\n\n    <dependencies>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>plugin-api</artifactId>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.apache.tomcat.embed</groupId>\n            <artifactId>tomcat-embed-core</artifactId>\n            <version>10.1.42</version>\n            <exclusions>\n                <exclusion>\n                    <groupId>org.apache.tomcat</groupId>\n                    <artifactId>tomcat-annotations-api</artifactId>\n                </exclusion>\n            </exclusions>\n            <scope>provided</scope>\n        </dependency>\n\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>plugin-api-mock</artifactId>\n            <version>${project.version}</version>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-web</artifactId>\n            <version>3.5.3</version>\n            <exclusions>\n                <exclusion>\n                    <groupId>org.springframework.boot</groupId>\n                    <artifactId>spring-boot-starter-logging</artifactId>\n                </exclusion>\n            </exclusions>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-test</artifactId>\n            <version>3.5.3</version>\n            <exclusions>\n                <exclusion>\n                    <groupId>org.springframework.boot</groupId>\n                    <artifactId>spring-boot-starter-logging</artifactId>\n                </exclusion>\n            </exclusions>\n            <scope>test</scope>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-compiler-plugin</artifactId>\n                <version>3.11.0</version>\n                <configuration>\n                    <source>17</source>\n                    <target>17</target>\n                </configuration>\n            </plugin>\n        </plugins>\n    </build>\n\n</project>\n"
  },
  {
    "path": "plugins/tomcat-jdk17/src/main/java/com/megaease/easeagent/plugin/tomcat/AccessPlugin.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.tomcat;\n\nimport com.megaease.easeagent.plugin.AgentPlugin;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.enums.Order;\n\npublic class AccessPlugin implements AgentPlugin {\n    @Override\n    public String getNamespace() {\n        return ConfigConst.Namespace.ACCESS;\n    }\n\n    @Override\n    public String getDomain() {\n        return ConfigConst.OBSERVABILITY;\n    }\n\n    @Override\n    public int order() {\n        return Order.HIGH.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/tomcat-jdk17/src/main/java/com/megaease/easeagent/plugin/tomcat/ForwardedPlugin.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.tomcat;\n\nimport com.megaease.easeagent.plugin.AgentPlugin;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.enums.Order;\n\npublic class ForwardedPlugin implements AgentPlugin {\n    @Override\n    public String getNamespace() {\n        return ConfigConst.Namespace.FORWARDED;\n    }\n\n    @Override\n    public String getDomain() {\n        return ConfigConst.INTEGRABILITY;\n    }\n\n    @Override\n    public int order() {\n        return Order.FORWARDED.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/tomcat-jdk17/src/main/java/com/megaease/easeagent/plugin/tomcat/TomcatPlugin.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.tomcat;\n\nimport com.megaease.easeagent.plugin.AgentPlugin;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.enums.Order;\n\nimport static com.megaease.easeagent.plugin.api.config.ConfigConst.Namespace.TOMCAT;\n\npublic class TomcatPlugin implements AgentPlugin {\n    @Override\n    public String getNamespace() {\n        return TOMCAT;\n    }\n\n    @Override\n    public String getDomain() {\n        return ConfigConst.OBSERVABILITY;\n    }\n\n    @Override\n    public int order() {\n        return Order.HIGH.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/tomcat-jdk17/src/main/java/com/megaease/easeagent/plugin/tomcat/advice/FilterChainPoints.java",
    "content": "package com.megaease.easeagent.plugin.tomcat.advice;\n\nimport com.megaease.easeagent.plugin.CodeVersion;\nimport com.megaease.easeagent.plugin.Points;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.matcher.ClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IClassMatcher;\nimport com.megaease.easeagent.plugin.matcher.IMethodMatcher;\nimport com.megaease.easeagent.plugin.matcher.MethodMatcher;\n\nimport java.util.Set;\n\npublic class FilterChainPoints implements Points {\n    private static final String FILTER_NAME = \"jakarta.servlet.FilterChain\";\n    private static final String HTTP_SERVLET_NAME = \"jakarta.servlet.http.HttpServlet\";\n    static final String SERVLET_REQUEST = \"jakarta.servlet.ServletRequest\";\n    static final String SERVLET_RESPONSE = \"jakarta.servlet.ServletResponse\";\n\n    private final static CodeVersion VERSIONS = CodeVersion.builder()\n        .key(ConfigConst.CodeVersion.KEY_JDK)\n        .add(ConfigConst.CodeVersion.VERSION_JDK17).build();\n\n    @Override\n    public CodeVersion codeVersions() {\n        return VERSIONS;\n    }\n\n    @Override\n    public IClassMatcher getClassMatcher() {\n        return ClassMatcher.builder()\n            .hasInterface(FILTER_NAME)\n            .build().or(ClassMatcher.builder()\n                .hasSuperClass(HTTP_SERVLET_NAME)\n                .build());\n    }\n\n    @Override\n    public Set<IMethodMatcher> getMethodMatcher() {\n        return MethodMatcher.multiBuilder()\n            .match(MethodMatcher.builder().named(\"doFilter\")\n                .arg(0, SERVLET_REQUEST)\n                .arg(1, SERVLET_RESPONSE)\n                .or()\n                .named(\"service\")\n                .arg(0, SERVLET_REQUEST)\n                .arg(1, SERVLET_RESPONSE)\n                .qualifier(\"default\")\n                .build())\n            .build();\n    }\n}\n"
  },
  {
    "path": "plugins/tomcat-jdk17/src/main/java/com/megaease/easeagent/plugin/tomcat/interceptor/BaseServletInterceptor.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.tomcat.interceptor;\n\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.tomcat.utils.InternalAsyncListener;\nimport com.megaease.easeagent.plugin.tomcat.utils.ServletUtils;\nimport com.megaease.easeagent.plugin.interceptor.NonReentrantInterceptor;\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.servlet.http.HttpServletResponse;\n\npublic abstract class BaseServletInterceptor implements NonReentrantInterceptor {\n\n    @Override\n    public void doBefore(MethodInfo methodInfo, Context context) {\n        HttpServletRequest httpServletRequest = (HttpServletRequest) methodInfo.getArgs()[0];\n        ServletUtils.startTime(httpServletRequest);\n    }\n\n    @Override\n    public void doAfter(MethodInfo methodInfo, Context context) {\n        HttpServletRequest httpServletRequest = (HttpServletRequest) methodInfo.getArgs()[0];\n        final long start = ServletUtils.startTime(httpServletRequest);\n        if (ServletUtils.markProcessed(httpServletRequest, getAfterMark())) {\n            return;\n        }\n        String httpRoute = ServletUtils.getHttpRouteAttributeFromRequest(httpServletRequest);\n        final String key = httpServletRequest.getMethod() + \" \" + httpRoute;\n        HttpServletResponse httpServletResponse = (HttpServletResponse) methodInfo.getArgs()[1];\n        if (methodInfo.getThrowable() != null) {\n            internalAfter(methodInfo.getThrowable(), key, httpServletRequest, httpServletResponse, start);\n        } else if (httpServletRequest.isAsyncStarted()) {\n            httpServletRequest.getAsyncContext().addListener(new InternalAsyncListener(\n                    asyncEvent -> {\n                        HttpServletResponse suppliedResponse = (HttpServletResponse) asyncEvent.getSuppliedResponse();\n                        internalAfter(asyncEvent.getThrowable(), key, httpServletRequest, suppliedResponse, start);\n                    }\n\n                )\n            );\n        } else {\n            internalAfter(null, key, httpServletRequest, httpServletResponse, start);\n        }\n    }\n\n    protected abstract String getAfterMark();\n\n    abstract void internalAfter(Throwable throwable, String key, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, long start);\n}\n"
  },
  {
    "path": "plugins/tomcat-jdk17/src/main/java/com/megaease/easeagent/plugin/tomcat/interceptor/FilterChainForwardedInterceptor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.tomcat.interceptor;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Cleaner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.interceptor.NonReentrantInterceptor;\nimport com.megaease.easeagent.plugin.tomcat.ForwardedPlugin;\nimport com.megaease.easeagent.plugin.tomcat.advice.FilterChainPoints;\nimport com.megaease.easeagent.plugin.tools.trace.HttpRequest;\nimport jakarta.servlet.http.HttpServletRequest;\n\n@AdviceTo(value = FilterChainPoints.class, qualifier = \"default\", plugin = ForwardedPlugin.class)\npublic class FilterChainForwardedInterceptor implements NonReentrantInterceptor {\n    private static final Object FORWARDED_KEY = new Object();\n\n    @Override\n    public void doBefore(MethodInfo methodInfo, Context context) {\n        HttpServletRequest httpServletRequest = (HttpServletRequest) methodInfo.getArgs()[0];\n        HttpRequest httpRequest = new HttpServerRequest(httpServletRequest);\n        Cleaner cleaner = context.importForwardedHeaders(httpRequest);\n        context.put(FORWARDED_KEY, cleaner);\n    }\n\n    @Override\n    public void doAfter(MethodInfo methodInfo, Context context) {\n        Cleaner cleaner = context.remove(FORWARDED_KEY);\n        if (cleaner != null) {\n            cleaner.close();\n        }\n    }\n\n    @Override\n    public String getType() {\n        return ConfigConst.PluginID.FORWARDED;\n    }\n\n    @Override\n    public int order() {\n        return Order.FORWARDED.getOrder();\n    }\n}\n\n"
  },
  {
    "path": "plugins/tomcat-jdk17/src/main/java/com/megaease/easeagent/plugin/tomcat/interceptor/FilterChainMetricInterceptor.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.tomcat.interceptor;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.metric.ServiceMetricRegistry;\nimport com.megaease.easeagent.plugin.api.metric.name.Tags;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.tomcat.TomcatPlugin;\nimport com.megaease.easeagent.plugin.tomcat.advice.FilterChainPoints;\nimport com.megaease.easeagent.plugin.tools.metrics.ServerMetric;\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.servlet.http.HttpServletResponse;\n@AdviceTo(value = FilterChainPoints.class, qualifier = \"default\", plugin = TomcatPlugin.class)\npublic class FilterChainMetricInterceptor extends BaseServletInterceptor {\n    private static final String AFTER_MARK = FilterChainMetricInterceptor.class.getName() + \"$AfterMark\";\n    private static volatile ServerMetric SERVER_METRIC = null;\n\n    @Override\n    public void init(IPluginConfig config, String className, String methodName, String methodDescriptor) {\n        SERVER_METRIC = ServiceMetricRegistry.getOrCreate(config, new Tags(\"application\", \"http-request\", \"url\"), ServerMetric.SERVICE_METRIC_SUPPLIER);\n    }\n\n    @Override\n    protected String getAfterMark() {\n        return AFTER_MARK;\n    }\n\n    @Override\n    public void internalAfter(Throwable throwable, String key, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, long start) {\n        long end = System.currentTimeMillis();\n        SERVER_METRIC.collectMetric(key, httpServletResponse.getStatus(), throwable, start, end);\n    }\n\n    @Override\n    public String getType() {\n        return Order.METRIC.getName();\n    }\n\n    @Override\n    public int order() {\n        return Order.METRIC.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/tomcat-jdk17/src/main/java/com/megaease/easeagent/plugin/tomcat/interceptor/FilterChainTraceInterceptor.java",
    "content": "package com.megaease.easeagent.plugin.tomcat.interceptor;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.RequestContext;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.interceptor.NonReentrantInterceptor;\nimport com.megaease.easeagent.plugin.tomcat.TomcatPlugin;\nimport com.megaease.easeagent.plugin.tomcat.advice.FilterChainPoints;\nimport com.megaease.easeagent.plugin.tomcat.utils.ServletUtils;\nimport com.megaease.easeagent.plugin.tools.trace.HttpRequest;\nimport com.megaease.easeagent.plugin.tools.trace.HttpResponse;\nimport com.megaease.easeagent.plugin.tools.trace.HttpUtils;\nimport com.megaease.easeagent.plugin.tools.trace.TraceConst;\nimport jakarta.servlet.*;\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.servlet.http.HttpServletResponse;\n\nimport java.util.concurrent.atomic.AtomicBoolean;\n\n@AdviceTo(value = FilterChainPoints.class, plugin = TomcatPlugin.class)\npublic class FilterChainTraceInterceptor implements NonReentrantInterceptor {\n    private static final String AFTER_MARK = FilterChainTraceInterceptor.class.getName() + \"$AfterMark\";\n    private static final String ERROR_KEY = \"error\";\n\n    @Override\n    public void doBefore(MethodInfo methodInfo, Context context) {\n        HttpServletRequest httpServletRequest = (HttpServletRequest) methodInfo.getArgs()[0];\n        RequestContext requestContext = (RequestContext) httpServletRequest.getAttribute(ServletUtils.PROGRESS_CONTEXT);\n        if (requestContext != null) {\n            return;\n        }\n        HttpRequest httpRequest = new HttpServerRequest(httpServletRequest);\n        requestContext = context.serverReceive(httpRequest);\n        httpServletRequest.setAttribute(ServletUtils.PROGRESS_CONTEXT, requestContext);\n        HttpUtils.handleReceive(requestContext.span().start(), httpRequest);\n    }\n\n    @Override\n    public void doAfter(MethodInfo methodInfo, Context context) {\n        HttpServletRequest httpServletRequest = (HttpServletRequest) methodInfo.getArgs()[0];\n        if (ServletUtils.markProcessed(httpServletRequest, AFTER_MARK)) {\n            return;\n        }\n        HttpServletResponse httpServletResponse = (HttpServletResponse) methodInfo.getArgs()[1];\n        RequestContext requestContext = (RequestContext) httpServletRequest.getAttribute(ServletUtils.PROGRESS_CONTEXT);\n        try {\n            Span span = requestContext.span();\n            if (!httpServletRequest.isAsyncStarted()) {\n                span.tag(TraceConst.HTTP_TAG_ROUTE, ServletUtils.getHttpRouteAttributeFromRequest(httpServletRequest));\n                HttpUtils.finish(span, new Response(methodInfo.getThrowable(), httpServletRequest, httpServletResponse));\n            } else if (methodInfo.getThrowable() != null) {\n                span.error(methodInfo.getThrowable());\n                span.finish();\n            } else {\n                httpServletRequest.getAsyncContext().addListener(new TracingAsyncListener(requestContext), httpServletRequest, httpServletResponse);\n            }\n        } finally {\n            requestContext.scope().close();\n        }\n    }\n\n    @Override\n    public int order() {\n        return Order.TRACING.getOrder();\n    }\n\n\n    public static class Response implements HttpResponse {\n        private final Throwable caught;\n        private final HttpServletRequest httpServletRequest;\n        private final HttpServletResponse httpServletResponse;\n\n        public Response(Throwable caught, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {\n            this.caught = caught;\n            this.httpServletRequest = httpServletRequest;\n            this.httpServletResponse = httpServletResponse;\n        }\n\n        @Override\n        public String method() {\n            return httpServletRequest.getMethod();\n        }\n\n        @Override\n        public String route() {\n            Object maybeRoute = httpServletRequest.getAttribute(TraceConst.HTTP_ATTRIBUTE_ROUTE);\n            return maybeRoute instanceof String ? (String) maybeRoute : null;\n        }\n\n        @Override\n        public int statusCode() {\n            if (httpServletResponse == null) {\n                return 0;\n            }\n            int result = httpServletResponse.getStatus();\n            if (caught != null && result == 200) {\n                if (caught instanceof UnavailableException) {\n                    return ((UnavailableException) caught).isPermanent() ? 404 : 503;\n                } else {\n                    return 500;\n                }\n            } else {\n                return result;\n            }\n        }\n\n        @Override\n        public Throwable maybeError() {\n            if (caught != null) {\n                return caught;\n            }\n            Object maybeError = httpServletRequest.getAttribute(ERROR_KEY);\n            if (maybeError instanceof Throwable) {\n                return (Throwable) maybeError;\n            } else {\n                maybeError = httpServletRequest.getAttribute(\"javax.servlet.error.exception\");\n                return maybeError instanceof Throwable ? (Throwable) maybeError : null;\n            }\n        }\n\n        @Override\n        public String header(String name) {\n            return httpServletResponse.getHeader(name);\n        }\n    }\n\n    public static final class TracingAsyncListener implements AsyncListener {\n        final RequestContext requestContext;\n        final AtomicBoolean sendHandled = new AtomicBoolean();\n\n        TracingAsyncListener(RequestContext requestContext) {\n            this.requestContext = requestContext;\n        }\n\n        public void onComplete(AsyncEvent e) {\n            HttpServletRequest req = (HttpServletRequest) e.getSuppliedRequest();\n            if (sendHandled.compareAndSet(false, true)) {\n                HttpServletResponse res = (HttpServletResponse) e.getSuppliedResponse();\n                Response response = new Response(e.getThrowable(), req, res);\n                HttpUtils.save(requestContext.span(), response);\n                requestContext.finish(response);\n            }\n\n        }\n\n        public void onTimeout(AsyncEvent e) {\n            onError(e);\n        }\n\n        public void onError(AsyncEvent e) {\n            ServletRequest request = e.getSuppliedRequest();\n            if (request.getAttribute(ERROR_KEY) == null) {\n                request.setAttribute(ERROR_KEY, e.getThrowable());\n            }\n        }\n\n        public void onStartAsync(AsyncEvent e) {\n            AsyncContext eventAsyncContext = e.getAsyncContext();\n            if (eventAsyncContext != null) {\n                eventAsyncContext.addListener(this, e.getSuppliedRequest(), e.getSuppliedResponse());\n            }\n        }\n\n        public String toString() {\n            return \"TracingAsyncListener{\" + this.requestContext + \"}\";\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/tomcat-jdk17/src/main/java/com/megaease/easeagent/plugin/tomcat/interceptor/HttpServerRequest.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.tomcat.interceptor;\n\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.tools.trace.HttpRequest;\nimport com.megaease.easeagent.plugin.tools.trace.TraceConst;\n\nimport jakarta.servlet.http.HttpServletRequest;\n\npublic class HttpServerRequest implements HttpRequest {\n    protected final HttpServletRequest delegate;\n\n    public HttpServerRequest(HttpServletRequest httpServletRequest) {\n        this.delegate = httpServletRequest;\n    }\n\n    @Override\n    public Span.Kind kind() {\n        return Span.Kind.SERVER;\n    }\n\n    @Override\n    public String method() {\n        return delegate.getMethod();\n    }\n\n    @Override\n    public String path() {\n        return delegate.getRequestURI();\n    }\n\n    @Override\n    public String route() {\n        Object maybeRoute = this.delegate.getAttribute(TraceConst.HTTP_ATTRIBUTE_ROUTE);\n        return maybeRoute instanceof String ? (String) maybeRoute : null;\n    }\n\n    @Override\n    public String getRemoteAddr() {\n        return this.delegate.getRemoteAddr();\n    }\n\n    @Override\n    public int getRemotePort() {\n        return this.delegate.getRemotePort();\n    }\n\n    @Override\n    public String header(String name) {\n        return this.delegate.getHeader(name);\n    }\n\n    @Override\n    public boolean cacheScope() {\n        return false;\n    }\n\n    @Override\n    public void setHeader(String name, String value) {\n//            this.delegate.setAttribute(name, value);\n    }\n}\n\n"
  },
  {
    "path": "plugins/tomcat-jdk17/src/main/java/com/megaease/easeagent/plugin/tomcat/interceptor/TomcatAccessLogServerInfo.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.tomcat.interceptor;\n\nimport com.megaease.easeagent.plugin.tomcat.utils.ServletUtils;\nimport com.megaease.easeagent.plugin.tools.metrics.AccessLogServerInfo;\nimport com.megaease.easeagent.plugin.utils.common.StringUtils;\n\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.servlet.http.HttpServletResponse;\nimport java.util.Enumeration;\nimport java.util.HashMap;\nimport java.util.Map;\n\npublic class TomcatAccessLogServerInfo implements AccessLogServerInfo {\n\n    private HttpServletRequest request;\n    private HttpServletResponse response;\n\n    public void load(HttpServletRequest request, HttpServletResponse response) {\n        this.request = request;\n        this.response = response;\n    }\n\n    @Override\n    public String getMethod() {\n        return request.getMethod();\n    }\n\n    @Override\n    public String getHeader(String key) {\n        return request.getHeader(key);\n    }\n\n    @Override\n    public String getRemoteAddr() {\n        return request.getRemoteAddr();\n    }\n\n    @Override\n    public String getRequestURI() {\n        return request.getRequestURI();\n    }\n\n    @Override\n    public int getResponseBufferSize() {\n        return response.getBufferSize();\n    }\n\n    @Override\n    public String getMatchURL() {\n        String matchURL = ServletUtils.matchUrlBySpringWeb(request);\n        if (StringUtils.isEmpty(matchURL)) {\n            return \"\";\n        }\n        return request.getMethod() + \" \" + matchURL;\n    }\n\n    @Override\n    public Map<String, String> findHeaders() {\n        Map<String, String> headers = new HashMap<>();\n        final Enumeration<String> headerNames = request.getHeaderNames();\n        while (headerNames.hasMoreElements()) {\n            final String key = headerNames.nextElement();\n            headers.put(key, request.getHeader(key));\n        }\n        return headers;\n    }\n\n    @Override\n    public Map<String, String> findQueries() {\n        return ServletUtils.getQueries4SingleValue(request);\n    }\n\n    @Override\n    public String getStatusCode() {\n        return String.valueOf(response.getStatus());\n    }\n\n}\n"
  },
  {
    "path": "plugins/tomcat-jdk17/src/main/java/com/megaease/easeagent/plugin/tomcat/interceptor/TomcatHttpLogInterceptor.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.tomcat.interceptor;\n\nimport com.megaease.easeagent.plugin.annotation.AdviceTo;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.RequestContext;\nimport com.megaease.easeagent.plugin.api.logging.AccessLogInfo;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.tomcat.AccessPlugin;\nimport com.megaease.easeagent.plugin.tomcat.advice.FilterChainPoints;\nimport com.megaease.easeagent.plugin.tomcat.utils.ServletUtils;\nimport com.megaease.easeagent.plugin.tools.metrics.AccessLogServerInfo;\nimport com.megaease.easeagent.plugin.tools.metrics.HttpLog;\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.servlet.http.HttpServletResponse;\n\n@AdviceTo(value = FilterChainPoints.class, plugin = AccessPlugin.class)\npublic class TomcatHttpLogInterceptor extends BaseServletInterceptor {\n    private static final String BEFORE_MARK = TomcatHttpLogInterceptor.class.getName() + \"$BeforeMark\";\n    private static final String AFTER_MARK = TomcatHttpLogInterceptor.class.getName() + \"$AfterMark\";\n    private final HttpLog httpLog = new HttpLog();\n\n    public AccessLogServerInfo serverInfo(HttpServletRequest request, HttpServletResponse response) {\n        TomcatAccessLogServerInfo serverInfo = (TomcatAccessLogServerInfo) request.getAttribute(TomcatAccessLogServerInfo.class.getName());\n        if (serverInfo == null) {\n            serverInfo = new TomcatAccessLogServerInfo();\n            request.setAttribute(TomcatAccessLogServerInfo.class.getName(), serverInfo);\n        }\n        serverInfo.load(request, response);\n        return serverInfo;\n    }\n\n    private Span getSpan(HttpServletRequest httpServletRequest, Context context) {\n        RequestContext requestContext = (RequestContext) httpServletRequest.getAttribute(ServletUtils.PROGRESS_CONTEXT);\n        if (requestContext != null) {\n            return requestContext.span();\n        }\n        return context.currentTracing().currentSpan();\n    }\n\n    private String getSystem() {\n        return EaseAgent.getConfig(\"system\");\n    }\n\n    private String getServiceName() {\n        return EaseAgent.getConfig(\"name\");\n    }\n\n\n    @Override\n    public void doBefore(MethodInfo methodInfo, Context context) {\n        HttpServletRequest httpServletRequest = (HttpServletRequest) methodInfo.getArgs()[0];\n        if (ServletUtils.markProcessed(httpServletRequest, BEFORE_MARK)) {\n            return;\n        }\n        HttpServletResponse httpServletResponse = (HttpServletResponse) methodInfo.getArgs()[1];\n        Long beginTime = ServletUtils.startTime(httpServletRequest);\n        Span span = getSpan(httpServletRequest, context);\n        AccessLogServerInfo serverInfo = this.serverInfo(httpServletRequest, httpServletResponse);\n        AccessLogInfo accessLog = this.httpLog.prepare(getSystem(), getServiceName(), beginTime, span, serverInfo);\n        httpServletRequest.setAttribute(AccessLogInfo.class.getName(), accessLog);\n    }\n\n    @Override\n    protected String getAfterMark() {\n        return AFTER_MARK;\n    }\n\n    @Override\n    void internalAfter(Throwable throwable, String key, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, long start) {\n        Long beginTime = ServletUtils.startTime(httpServletRequest);\n        AccessLogInfo accessLog = (AccessLogInfo) httpServletRequest.getAttribute(AccessLogInfo.class.getName());\n        AccessLogServerInfo serverInfo = this.serverInfo(httpServletRequest, httpServletResponse);\n        this.httpLog.finish(accessLog, throwable == null, beginTime, serverInfo);\n        EaseAgent.agentReport.report(accessLog);\n    }\n\n    @Override\n    public String getType() {\n        return Order.LOG.getName();\n    }\n\n    @Override\n    public int order() {\n        return Order.LOG.getOrder();\n    }\n}\n"
  },
  {
    "path": "plugins/tomcat-jdk17/src/main/java/com/megaease/easeagent/plugin/tomcat/utils/InternalAsyncListener.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.tomcat.utils;\n\nimport jakarta.servlet.AsyncContext;\nimport jakarta.servlet.AsyncEvent;\nimport jakarta.servlet.AsyncListener;\n\nimport java.util.function.Consumer;\n\npublic class InternalAsyncListener implements AsyncListener {\n\n    private final Consumer<AsyncEvent> consumer;\n\n    public InternalAsyncListener(Consumer<AsyncEvent> consumer) {\n        this.consumer = consumer;\n    }\n\n    @Override\n    public void onComplete(AsyncEvent event) {\n        this.consumer.accept(event);\n    }\n\n    @Override\n    public void onTimeout(AsyncEvent event) {\n    }\n\n    @Override\n    public void onError(AsyncEvent event) {\n    }\n\n    @Override\n    public void onStartAsync(AsyncEvent event) {\n        AsyncContext eventAsyncContext = event.getAsyncContext();\n        if (eventAsyncContext != null) {\n            eventAsyncContext.addListener(this, event.getSuppliedRequest(), event.getSuppliedResponse());\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/tomcat-jdk17/src/main/java/com/megaease/easeagent/plugin/tomcat/utils/ServletUtils.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.tomcat.utils;\n\nimport com.megaease.easeagent.plugin.api.logging.Logger;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.tomcat.interceptor.FilterChainTraceInterceptor;\nimport com.megaease.easeagent.plugin.utils.ClassUtils;\nimport jakarta.servlet.http.HttpServletRequest;\nimport lombok.SneakyThrows;\n\nimport java.net.URLDecoder;\nimport java.util.HashMap;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Map;\n\npublic class ServletUtils {\n    public static final Logger LOGGER = EaseAgent.getLogger(ServletUtils.class);\n    public static final String START_TIME = ServletUtils.class.getName() + \"$StartTime\";\n    public static final String PROGRESS_CONTEXT = FilterChainTraceInterceptor.class.getName() + \".RequestContext\";\n    public static final String HANDLER_MAPPING_CLASS = \"org.springframework.web.servlet.HandlerMapping\";\n    public static final String BEST_MATCHING_PATTERN_ATTRIBUTE_FIELD_NAME = \"BEST_MATCHING_PATTERN_ATTRIBUTE\";\n    public static final String BEST_MATCHING_PATTERN_ATTRIBUTE;\n\n    static {\n        String pattern;\n        Object field = ClassUtils.getStaticField(HANDLER_MAPPING_CLASS, BEST_MATCHING_PATTERN_ATTRIBUTE_FIELD_NAME);\n        if (field == null) {\n            if (LOGGER.isDebugEnabled()) {\n                LOGGER.debug(\"class<{}>.{} not found \", HANDLER_MAPPING_CLASS, BEST_MATCHING_PATTERN_ATTRIBUTE_FIELD_NAME);\n            }\n            pattern = \"org.springframework.web.servlet.HandlerMapping.bestMatchingPattern\";\n        } else if (field instanceof String) {\n            pattern = (String) field;\n        } else {\n            if (LOGGER.isDebugEnabled()) {\n                LOGGER.debug(\"class<{}>.{} is not String\", HANDLER_MAPPING_CLASS, BEST_MATCHING_PATTERN_ATTRIBUTE_FIELD_NAME);\n            }\n            pattern = \"org.springframework.web.servlet.HandlerMapping.bestMatchingPattern\";\n        }\n        BEST_MATCHING_PATTERN_ATTRIBUTE = pattern;\n    }\n\n    public static String matchUrlBySpringWeb(HttpServletRequest request) {\n        return (String) request.getAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE);\n    }\n\n    public static String getHttpRouteAttributeFromRequest(HttpServletRequest request) {\n        Object httpRoute = request.getAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE);\n        return httpRoute != null ? httpRoute.toString() : \"\";\n    }\n\n    public static boolean markProcessed(HttpServletRequest request, String mark) {\n        if (request.getAttribute(mark) != null) {\n            return true;\n        }\n        request.setAttribute(mark, \"m\");\n        return false;\n    }\n\n    public static long startTime(HttpServletRequest httpServletRequest) {\n        Object startObj = httpServletRequest.getAttribute(START_TIME);\n        Long start;\n        if (startObj == null) {\n            start = System.currentTimeMillis();\n            httpServletRequest.setAttribute(START_TIME, start);\n        } else {\n            start = (Long) startObj;\n        }\n        return start;\n    }\n\n\n    @SneakyThrows\n    public static Map<String, List<String>> getQueries(HttpServletRequest httpServletRequest) {\n        Map<String, List<String>> map = new HashMap<>();\n        String queryString = httpServletRequest.getQueryString();\n        if (queryString == null || queryString.isEmpty()) {\n            return map;\n        }\n        String[] pairs = queryString.split(\"&\");\n        for (String pair : pairs) {\n            int idx = pair.indexOf(\"=\");\n            String key = idx > 0 ? URLDecoder.decode(pair.substring(0, idx), \"UTF-8\") : pair;\n            if (!map.containsKey(key)) {\n                map.put(key, new LinkedList<>());\n            }\n            String value = idx > 0 && pair.length() > idx + 1 ? URLDecoder.decode(pair.substring(idx + 1), \"UTF-8\") : null;\n            map.get(key).add(value);\n        }\n        return map;\n    }\n\n    public static Map<String, String> getQueries4SingleValue(HttpServletRequest httpServletRequest) {\n        Map<String, List<String>> map = getQueries(httpServletRequest);\n        Map<String, String> singleValueMap = new HashMap<>();\n        map.forEach((key, values) -> {\n            if (values != null && values.size() > 0) {\n                singleValueMap.put(key, values.get(0));\n            }\n        });\n        return singleValueMap;\n    }\n}\n"
  },
  {
    "path": "plugins/tomcat-jdk17/src/test/java/com/megaease/easeagent/plugin/tomcat/interceptor/BaseServletInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.tomcat.interceptor;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.tomcat.utils.ServletUtils;\nimport jakarta.servlet.AsyncContext;\nimport jakarta.servlet.AsyncEvent;\nimport jakarta.servlet.AsyncListener;\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.servlet.http.HttpServletResponse;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.mock.web.MockAsyncContext;\nimport org.springframework.mock.web.MockHttpServletRequest;\n\nimport java.io.IOException;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.function.Consumer;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class BaseServletInterceptorTest {\n\n    @Test\n    public void doBefore() {\n        HttpServletRequest httpServletRequest = TestServletUtils.buildMockRequest();\n        HttpServletResponse response = TestServletUtils.buildMockResponse();\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{httpServletRequest, response}).build();\n\n        MockBaseServletInterceptor mockBaseServletInterceptor = new MockBaseServletInterceptor();\n        mockBaseServletInterceptor.doBefore(methodInfo, EaseAgent.getContext());\n        assertNotNull(httpServletRequest.getAttribute(ServletUtils.START_TIME));\n    }\n\n    @Test\n    public void doAfter() {\n        internalAfter(null);\n    }\n\n    @Test\n    public void getAfterMark() {\n        MockBaseServletInterceptor mockBaseServletInterceptor = new MockBaseServletInterceptor();\n        assertEquals(MockBaseServletInterceptor.BEFORE_MARK, mockBaseServletInterceptor.getAfterMark());\n    }\n\n    @Test\n    public void internalAfter() {\n        internalAfter(new RuntimeException(\"test error\"));\n    }\n\n    private void internalAfter(Throwable error) {\n        HttpServletRequest httpServletRequest = TestServletUtils.buildMockRequest();\n        HttpServletResponse response = TestServletUtils.buildMockResponse();\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{httpServletRequest, response}).throwable(error).build();\n\n        MockBaseServletInterceptor mockBaseServletInterceptor = new MockBaseServletInterceptor();\n        mockBaseServletInterceptor.throwable = error;\n        mockBaseServletInterceptor.key = TestConst.METHOD + \" \" + TestConst.ROUTE;\n        mockBaseServletInterceptor.httpServletRequest = httpServletRequest;\n        mockBaseServletInterceptor.httpServletResponse = response;\n        mockBaseServletInterceptor.doBefore(methodInfo, EaseAgent.getContext());\n        mockBaseServletInterceptor.start = (long) httpServletRequest.getAttribute(ServletUtils.START_TIME);\n        mockBaseServletInterceptor.doAfter(methodInfo, EaseAgent.getContext());\n        assertTrue(mockBaseServletInterceptor.isRan.get());\n    }\n\n\n    @Test\n    public void testAsync() throws InterruptedException {\n        runAsyncOne(AsyncContext::complete, null);\n\n        String errorInfo = \"timeout eror\";\n        RuntimeException error = new RuntimeException(errorInfo);\n        runAsyncOne(asyncContext -> {\n            AsyncEvent asyncEvent = new AsyncEvent(asyncContext, asyncContext.getRequest(), asyncContext.getResponse(), error);\n            MockAsyncContext mockAsyncContext = (MockAsyncContext) asyncContext;\n            for (AsyncListener asyncListener : mockAsyncContext.getListeners()) {\n                try {\n                    asyncListener.onComplete(asyncEvent);\n                } catch (IOException e) {\n                    throw new RuntimeException(\"error\");\n                }\n            }\n        }, error);\n    }\n\n    private void runAsyncOne(Consumer<AsyncContext> asyncContextConsumer, Throwable error) throws InterruptedException {\n        MockHttpServletRequest httpServletRequest = TestServletUtils.buildMockRequest();\n        HttpServletResponse response = TestServletUtils.buildMockResponse();\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{httpServletRequest, response}).build();\n        MockBaseServletInterceptor mockBaseServletInterceptor = new MockBaseServletInterceptor();\n        mockBaseServletInterceptor.key = TestConst.METHOD + \" \" + TestConst.ROUTE;\n        mockBaseServletInterceptor.httpServletRequest = httpServletRequest;\n        mockBaseServletInterceptor.httpServletResponse = response;\n        mockBaseServletInterceptor.throwable = error;\n\n        mockBaseServletInterceptor.doBefore(methodInfo, EaseAgent.getContext());\n        mockBaseServletInterceptor.start = (long) httpServletRequest.getAttribute(ServletUtils.START_TIME);\n        httpServletRequest.setAsyncSupported(true);\n        final AsyncContext asyncContext = httpServletRequest.startAsync(httpServletRequest, response);\n        mockBaseServletInterceptor.doAfter(methodInfo, EaseAgent.getContext());\n\n        Thread thread = new Thread(() -> asyncContextConsumer.accept(asyncContext));\n        thread.start();\n        thread.join();\n        assertTrue(mockBaseServletInterceptor.isRan.get());\n    }\n\n    private static class MockBaseServletInterceptor extends BaseServletInterceptor {\n        private static final String BEFORE_MARK = MockBaseServletInterceptor.class.getName() + \"$BeforeMark\";\n        private AtomicBoolean isRan = new AtomicBoolean(false);\n        private Throwable throwable;\n        private String key;\n        private HttpServletRequest httpServletRequest;\n        private HttpServletResponse httpServletResponse;\n        private long start;\n\n        @Override\n        public int order() {\n            return Order.HIGH.getOrder();\n        }\n\n\n        @Override\n        protected String getAfterMark() {\n            return BEFORE_MARK;\n        }\n\n        @Override\n        void internalAfter(Throwable throwable, String key, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, long start) {\n            isRan.set(true);\n            if (this.throwable == null) {\n                assertNull(throwable);\n            } else {\n                assertSame(this.throwable, throwable);\n            }\n            assertEquals(this.key, key);\n            assertSame(this.httpServletRequest, httpServletRequest);\n            assertSame(this.httpServletResponse, httpServletResponse);\n            assertEquals(this.start, start);\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/tomcat-jdk17/src/test/java/com/megaease/easeagent/plugin/tomcat/interceptor/FilterChainForwardedInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.tomcat.interceptor;\n\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport jakarta.servlet.http.HttpServletRequest;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNull;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class FilterChainForwardedInterceptorTest {\n\n    public void testForwarded() {\n        HttpServletRequest httpServletRequest = TestServletUtils.buildMockRequest();\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{httpServletRequest}).build();\n        FilterChainForwardedInterceptor filterChainForwardedInterceptor = new FilterChainForwardedInterceptor();\n        filterChainForwardedInterceptor.doBefore(methodInfo, EaseAgent.getContext());\n        assertEquals(TestConst.FORWARDED_VALUE, EaseAgent.getContext().get(TestConst.FORWARDED_NAME));\n        filterChainForwardedInterceptor.doAfter(methodInfo, EaseAgent.getContext());\n        assertNull(EaseAgent.getContext().get(TestConst.FORWARDED_NAME));\n    }\n\n    @Test\n    public void doBefore() {\n        testForwarded();\n    }\n\n    @Test\n    public void doAfter() {\n        testForwarded();\n    }\n\n    @Test\n    public void getType() {\n        FilterChainForwardedInterceptor filterChainForwardedInterceptor = new FilterChainForwardedInterceptor();\n        assertEquals(ConfigConst.PluginID.FORWARDED, filterChainForwardedInterceptor.getType());\n    }\n}\n"
  },
  {
    "path": "plugins/tomcat-jdk17/src/test/java/com/megaease/easeagent/plugin/tomcat/interceptor/FilterChainMetricInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.tomcat.interceptor;\n\nimport com.megaease.easeagent.mock.plugin.api.MockEaseAgent;\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.mock.plugin.api.utils.TagVerifier;\nimport com.megaease.easeagent.mock.report.impl.LastJsonReporter;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.metric.ServiceMetric;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.tomcat.TomcatPlugin;\nimport jakarta.servlet.http.HttpServletResponse;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.mock.web.MockHttpServletRequest;\n\nimport java.util.Map;\nimport java.util.Objects;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotNull;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class FilterChainMetricInterceptorTest {\n\n    @Test\n    public void init() {\n        FilterChainMetricInterceptor doFilterMetricInterceptor = new FilterChainMetricInterceptor();\n        TomcatPlugin httpServletPlugin = new TomcatPlugin();\n        IPluginConfig iPluginConfig = EaseAgent.getConfig(httpServletPlugin.getDomain(), httpServletPlugin.getNamespace(), doFilterMetricInterceptor.getType());\n        doFilterMetricInterceptor.init(iPluginConfig, \"\", \"\", \"\");\n        assertNotNull(AgentFieldReflectAccessor.getFieldValue(doFilterMetricInterceptor, \"SERVER_METRIC\"));\n\n    }\n\n    @Test\n    public void getAfterMark() {\n        FilterChainMetricInterceptor doFilterMetricInterceptor = new FilterChainMetricInterceptor();\n        assertNotNull(doFilterMetricInterceptor.getAfterMark());\n\n    }\n\n    public Map<String, Object> getMetric(LastJsonReporter lastJsonReporter) throws InterruptedException {\n        return lastJsonReporter.flushAndOnlyOne();\n    }\n\n    @Test\n    public void internalAfter() throws InterruptedException {\n        MockHttpServletRequest httpServletRequest = TestServletUtils.buildMockRequest();\n        HttpServletResponse response = TestServletUtils.buildMockResponse();\n\n        FilterChainMetricInterceptor doFilterMetricInterceptor = new FilterChainMetricInterceptor();\n        TomcatPlugin httpServletPlugin = new TomcatPlugin();\n        IPluginConfig iPluginConfig = EaseAgent.getConfig(httpServletPlugin.getDomain(), httpServletPlugin.getNamespace(), doFilterMetricInterceptor.getType());\n        int interval = iPluginConfig.getInt(\"interval\");\n        assertEquals(1, interval);\n        doFilterMetricInterceptor.init(iPluginConfig, \"\", \"\", \"\");\n\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{httpServletRequest, response}).build();\n        doFilterMetricInterceptor.doBefore(methodInfo, EaseAgent.getContext());\n        TagVerifier tagVerifier = new TagVerifier()\n            .add(\"category\", \"application\")\n            .add(\"type\", \"http-request\")\n            .add(\"url\", TestConst.METHOD + \" \" + TestConst.ROUTE);\n        LastJsonReporter lastJsonReporter = MockEaseAgent.lastMetricJsonReporter(tagVerifier::verifyAnd);\n\n        doFilterMetricInterceptor.doAfter(methodInfo, EaseAgent.getContext());\n        Map<String, Object> metric = getMetric(lastJsonReporter);\n\n        assertEquals(1, (int) metric.get(\"cnt\"));\n        assertEquals(0, (int) metric.get(\"errcnt\"));\n\n        MockEaseAgent.cleanMetric(Objects.requireNonNull(AgentFieldReflectAccessor.<ServiceMetric>getFieldValue(doFilterMetricInterceptor, \"SERVER_METRIC\")));\n        lastJsonReporter.clean();\n        httpServletRequest = TestServletUtils.buildMockRequest();\n        response = TestServletUtils.buildMockResponse();\n        methodInfo = MethodInfo.builder().args(new Object[]{httpServletRequest, response}).throwable(new RuntimeException(\"test error\")).build();\n        doFilterMetricInterceptor.doBefore(methodInfo, EaseAgent.getContext());\n        doFilterMetricInterceptor.doAfter(methodInfo, EaseAgent.getContext());\n\n        metric = getMetric(lastJsonReporter);\n        assertEquals(1, (int) metric.get(\"cnt\"));\n        assertEquals(1, (int) metric.get(\"errcnt\"));\n    }\n\n    @Test\n    public void getType() {\n        FilterChainMetricInterceptor doFilterMetricInterceptor = new FilterChainMetricInterceptor();\n        assertEquals(Order.METRIC.getName(), doFilterMetricInterceptor.getType());\n\n    }\n}\n"
  },
  {
    "path": "plugins/tomcat-jdk17/src/test/java/com/megaease/easeagent/plugin/tomcat/interceptor/FilterChainTraceInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.tomcat.interceptor;\n\nimport com.megaease.easeagent.mock.plugin.api.MockEaseAgent;\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.plugin.api.Context;\nimport com.megaease.easeagent.plugin.api.context.RequestContext;\nimport com.megaease.easeagent.plugin.api.trace.Request;\nimport com.megaease.easeagent.plugin.api.trace.Setter;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.tomcat.utils.ServletUtils;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport com.megaease.easeagent.plugin.tools.trace.TraceConst;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.mock.web.MockAsyncContext;\nimport org.springframework.mock.web.MockHttpServletRequest;\n\nimport jakarta.servlet.*;\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.servlet.http.HttpServletResponse;\n\nimport java.io.IOException;\nimport java.util.function.Consumer;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class FilterChainTraceInterceptorTest {\n\n    private void testTrace() {\n        HttpServletRequest httpServletRequest = TestServletUtils.buildMockRequest();\n        HttpServletResponse response = TestServletUtils.buildMockResponse();\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{httpServletRequest, response}).build();\n\n        FilterChainTraceInterceptor doFilterTraceInterceptor = new FilterChainTraceInterceptor();\n        doFilterTraceInterceptor.doBefore(methodInfo, EaseAgent.getContext());\n        Object o = httpServletRequest.getAttribute(ServletUtils.PROGRESS_CONTEXT);\n        assertNotNull(o);\n        assertTrue(o instanceof RequestContext);\n        doFilterTraceInterceptor.doBefore(methodInfo, EaseAgent.getContext());\n        Object o2 = httpServletRequest.getAttribute(ServletUtils.PROGRESS_CONTEXT);\n        assertNotNull(o2);\n        assertSame(o, o2);\n        doFilterTraceInterceptor.doAfter(methodInfo, EaseAgent.getContext());\n        ReportSpan mockSpan = MockEaseAgent.getLastSpan();\n        assertNotNull(mockSpan);\n        assertNull(mockSpan.parentId());\n        checkServerSpan(mockSpan);\n\n        MockEaseAgent.cleanLastSpan();\n        doFilterTraceInterceptor.doAfter(methodInfo, EaseAgent.getContext());\n        assertNull(MockEaseAgent.getLastSpan());\n    }\n\n\n    private void checkServerSpan(ReportSpan mockSpan) {\n        assertEquals(Span.Kind.SERVER.name(), mockSpan.kind());\n        assertEquals(TestConst.ROUTE, mockSpan.tag(TraceConst.HTTP_ATTRIBUTE_ROUTE));\n        assertEquals(TestConst.METHOD, mockSpan.tag(\"http.method\"));\n        assertEquals(TestConst.URL, mockSpan.tag(\"http.path\"));\n        assertEquals(TestConst.REMOTE_ADDR, mockSpan.remoteEndpoint().ipv4());\n        assertEquals(TestConst.REMOTE_PORT, mockSpan.remoteEndpoint().port());\n    }\n\n    @Test\n    public void testErrorTracing() {\n        HttpServletRequest httpServletRequest = TestServletUtils.buildMockRequest();\n        HttpServletResponse response = TestServletUtils.buildMockResponse();\n        String errorInfo = \"test error\";\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{httpServletRequest, response}).throwable(new RuntimeException(errorInfo)).build();\n        FilterChainTraceInterceptor doFilterTraceInterceptor = new FilterChainTraceInterceptor();\n        doFilterTraceInterceptor.doBefore(methodInfo, EaseAgent.getContext());\n        doFilterTraceInterceptor.doAfter(methodInfo, EaseAgent.getContext());\n        ReportSpan mockSpan = MockEaseAgent.getLastSpan();\n        assertNotNull(mockSpan);\n        assertEquals(Span.Kind.SERVER.name(), mockSpan.kind());\n        assertNull(mockSpan.parentId());\n        checkServerSpan(mockSpan);\n        assertEquals(errorInfo, mockSpan.tag(\"error\"));\n    }\n\n    @Test\n    public void testHasPassHeader() {\n        MockHttpServletRequest httpServletRequest = TestServletUtils.buildMockRequest();\n        Context context = EaseAgent.getContext();\n        RequestContext requestContext = context.clientRequest(new MockClientRequest(httpServletRequest::addHeader));\n        requestContext.scope().close();\n        assertFalse(context.currentTracing().hasCurrentSpan());\n        requestContext.span().abandon();\n\n        HttpServletResponse response = TestServletUtils.buildMockResponse();\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{httpServletRequest, response}).build();\n        FilterChainTraceInterceptor doFilterTraceInterceptor = new FilterChainTraceInterceptor();\n        doFilterTraceInterceptor.doBefore(methodInfo, EaseAgent.getContext());\n        doFilterTraceInterceptor.doAfter(methodInfo, EaseAgent.getContext());\n        ReportSpan mockSpan = MockEaseAgent.getLastSpan();\n        assertNotNull(mockSpan);\n        assertEquals(requestContext.span().traceIdString(), mockSpan.traceId());\n        assertEquals(requestContext.span().spanIdString(), mockSpan.id());\n        assertEquals(requestContext.span().parentIdString(), mockSpan.parentId());\n        checkServerSpan(mockSpan);\n    }\n\n    @Test\n    public void testAsync() throws InterruptedException {\n        ReportSpan mockSpan = runAsyncOne(AsyncContext::complete);\n        assertNotNull(mockSpan);\n        assertEquals(Span.Kind.SERVER.name(), mockSpan.kind());\n        assertNull(mockSpan.parentId());\n        checkServerSpan(mockSpan);\n\n        String errorInfo = \"timeout eror\";\n        mockSpan = runAsyncOne(asyncContext -> {\n            AsyncEvent asyncEvent = new AsyncEvent(asyncContext, asyncContext.getRequest(), asyncContext.getResponse(), new RuntimeException(errorInfo));\n            MockAsyncContext mockAsyncContext = (MockAsyncContext) asyncContext;\n            for (AsyncListener asyncListener : mockAsyncContext.getListeners()) {\n                try {\n                    asyncListener.onTimeout(asyncEvent);\n                } catch (IOException e) {\n                    throw new RuntimeException(\"error\");\n                }\n            }\n            asyncContext.complete();\n        });\n        assertNotNull(mockSpan);\n        assertEquals(Span.Kind.SERVER.name(), mockSpan.kind());\n        assertNull(mockSpan.parentId());\n        checkServerSpan(mockSpan);\n        assertEquals(errorInfo, mockSpan.tag(\"error\"));\n    }\n\n    public ReportSpan runAsyncOne(Consumer<AsyncContext> asyncContextConsumer) throws InterruptedException {\n        MockHttpServletRequest httpServletRequest = TestServletUtils.buildMockRequest();\n        HttpServletResponse response = TestServletUtils.buildMockResponse();\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{httpServletRequest, response}).build();\n        FilterChainTraceInterceptor doFilterTraceInterceptor = new FilterChainTraceInterceptor();\n        doFilterTraceInterceptor.doBefore(methodInfo, EaseAgent.getContext());\n        httpServletRequest.setAsyncSupported(true);\n        final AsyncContext asyncContext = httpServletRequest.startAsync(httpServletRequest, response);\n        doFilterTraceInterceptor.doAfter(methodInfo, EaseAgent.getContext());\n\n        MockEaseAgent.cleanLastSpan();\n        Thread thread = new Thread(() -> asyncContextConsumer.accept(asyncContext));\n        thread.start();\n        thread.join();\n        return MockEaseAgent.getLastSpan();\n    }\n\n    @Test\n    public void doBefore() {\n        testTrace();\n    }\n\n    @Test\n    public void doAfter() {\n        testTrace();\n    }\n\n    public class MockClientRequest implements Request {\n        private final Setter setter;\n\n        public MockClientRequest(Setter setter) {\n            this.setter = setter;\n        }\n\n        @Override\n        public Span.Kind kind() {\n            return Span.Kind.CLIENT;\n        }\n\n        @Override\n        public String header(String name) {\n            return null;\n        }\n\n        @Override\n        public String name() {\n            return \"test\";\n        }\n\n        @Override\n        public boolean cacheScope() {\n            return false;\n        }\n\n        @Override\n        public void setHeader(String name, String value) {\n            setter.setHeader(name, value);\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/tomcat-jdk17/src/test/java/com/megaease/easeagent/plugin/tomcat/interceptor/HttpServerRequestTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.tomcat.interceptor;\n\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.tomcat.interceptor.HttpServerRequest;\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\npublic class HttpServerRequestTest {\n    public HttpServerRequest build() {\n        return new HttpServerRequest(TestServletUtils.buildMockRequest());\n    }\n\n    @Test\n    public void kind() {\n        assertEquals(Span.Kind.SERVER, build().kind());\n    }\n\n    @Test\n    public void method() {\n        assertEquals(TestConst.METHOD, build().method());\n    }\n\n    @Test\n    public void path() {\n        assertEquals(TestConst.URL, build().path());\n    }\n\n    @Test\n    public void route() {\n        assertEquals(TestConst.ROUTE, build().route());\n    }\n\n    @Test\n    public void getRemoteAddr() {\n        assertEquals(TestConst.REMOTE_ADDR, build().getRemoteAddr());\n    }\n\n    @Test\n    public void getRemotePort() {\n        assertEquals(TestConst.REMOTE_PORT, build().getRemotePort());\n    }\n\n    @Test\n    public void header() {\n        assertEquals(TestConst.FORWARDED_VALUE, build().header(TestConst.FORWARDED_NAME));\n    }\n\n    @Test\n    public void cacheScope() {\n        assertEquals(false, build().cacheScope());\n    }\n}\n"
  },
  {
    "path": "plugins/tomcat-jdk17/src/test/java/com/megaease/easeagent/plugin/tomcat/interceptor/ServletAccessLogInfoServerInfoTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.tomcat.interceptor;\n\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\nimport jakarta.servlet.http.HttpServletResponse;\nimport org.junit.Test;\nimport org.springframework.mock.web.MockHttpServletRequest;\n\nimport java.util.Map;\n\nimport static org.junit.Assert.*;\n\npublic class ServletAccessLogInfoServerInfoTest {\n\n    private TomcatAccessLogServerInfo loadMock() {\n        TomcatAccessLogServerInfo servletAccessLogServerInfo = new TomcatAccessLogServerInfo();\n        MockHttpServletRequest httpServletRequest = TestServletUtils.buildMockRequest();\n        HttpServletResponse response = TestServletUtils.buildMockResponse();\n        servletAccessLogServerInfo.load(httpServletRequest, response);\n        return servletAccessLogServerInfo;\n    }\n\n    @Test\n    public void load() {\n        TomcatAccessLogServerInfo serverInfo = loadMock();\n        assertNotNull(AgentFieldReflectAccessor.getFieldValue(serverInfo, \"request\"));\n        assertNotNull(AgentFieldReflectAccessor.getFieldValue(serverInfo, \"response\"));\n    }\n\n    @Test\n    public void getMethod() {\n        assertEquals(TestConst.METHOD, loadMock().getMethod());\n    }\n\n    @Test\n    public void getHeader() {\n        assertEquals(TestConst.FORWARDED_VALUE, loadMock().getHeader(TestConst.FORWARDED_NAME));\n    }\n\n    @Test\n    public void getRemoteAddr() {\n        assertEquals(TestConst.REMOTE_ADDR, loadMock().getRemoteAddr());\n    }\n\n    @Test\n    public void getRequestURI() {\n        assertEquals(TestConst.URL, loadMock().getRequestURI());\n    }\n\n    @Test\n    public void getResponseBufferSize() {\n        assertEquals(TestConst.RESPONSE_BUFFER_SIZE, loadMock().getResponseBufferSize());\n    }\n\n    @Test\n    public void getMatchURL() {\n        assertEquals(TestConst.METHOD + \" \" + TestConst.ROUTE, loadMock().getMatchURL());\n    }\n\n    @Test\n    public void findHeaders() {\n        assertEquals(TestConst.FORWARDED_VALUE, loadMock().findHeaders().get(TestConst.FORWARDED_NAME));\n    }\n\n    @Test\n    public void findQueries() {\n        Map<String, String> queries = loadMock().findQueries();\n        assertEquals(\"10\", queries.get(\"q1\"));\n        assertEquals(\"testq\", queries.get(\"q2\"));\n    }\n\n    @Test\n    public void getStatusCode() {\n        assertEquals(\"200\", loadMock().getStatusCode());\n    }\n}\n"
  },
  {
    "path": "plugins/tomcat-jdk17/src/test/java/com/megaease/easeagent/plugin/tomcat/interceptor/TestConst.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.tomcat.interceptor;\n\npublic class TestConst {\n    public static final String FORWARDED_NAME = \"X-Forwarded-For\";\n    public static final String FORWARDED_VALUE = \"testForwarded\";\n    public static final String RESPONSE_TAG_NAME = \"X-EG-Test\";\n    public static final String RESPONSE_TAG_VALUE = \"X-EG-Test-Value\";\n    public static final String METHOD = \"GET\";\n    public static final String URL = \"http://192.168.5.1:8080/test\";\n    public static final String QUERY_STRING = \"q1=10&q2=testq\";\n    public static final String ROUTE = \"/test\";\n    public static final String REMOTE_ADDR = \"192.168.5.1\";\n    public static final int REMOTE_PORT = 8080;\n    public static final int RESPONSE_BUFFER_SIZE = 1024;\n}\n"
  },
  {
    "path": "plugins/tomcat-jdk17/src/test/java/com/megaease/easeagent/plugin/tomcat/interceptor/TestServletUtils.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.tomcat.interceptor;\n\nimport com.megaease.easeagent.plugin.tomcat.utils.ServletUtils;\nimport com.megaease.easeagent.plugin.tools.trace.TraceConst;\nimport jakarta.servlet.http.HttpServletResponse;\nimport org.springframework.mock.web.MockHttpServletRequest;\nimport org.springframework.mock.web.MockHttpServletResponse;\n\npublic class TestServletUtils {\n    public static MockHttpServletRequest buildMockRequest() {\n        MockHttpServletRequest httpServletRequest = new MockHttpServletRequest(TestConst.METHOD, TestConst.URL);\n        httpServletRequest.setQueryString(TestConst.QUERY_STRING);\n        httpServletRequest.addHeader(TestConst.FORWARDED_NAME, TestConst.FORWARDED_VALUE);\n        httpServletRequest.setAttribute(TraceConst.HTTP_ATTRIBUTE_ROUTE, TestConst.ROUTE);\n        httpServletRequest.setAttribute(ServletUtils.BEST_MATCHING_PATTERN_ATTRIBUTE, TestConst.ROUTE);\n        httpServletRequest.setRemoteAddr(TestConst.REMOTE_ADDR);\n        httpServletRequest.setRemotePort(TestConst.REMOTE_PORT);\n        return httpServletRequest;\n    }\n\n    public static HttpServletResponse buildMockResponse() {\n        MockHttpServletResponse response = new MockHttpServletResponse();\n        response.setBufferSize(TestConst.RESPONSE_BUFFER_SIZE);\n        response.setStatus(200);\n        return response;\n    }\n}\n"
  },
  {
    "path": "plugins/tomcat-jdk17/src/test/java/com/megaease/easeagent/plugin/tomcat/interceptor/TomcatHttpLogInterceptorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.plugin.tomcat.interceptor;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.megaease.easeagent.mock.plugin.api.MockEaseAgent;\nimport com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner;\nimport com.megaease.easeagent.mock.report.MockReport;\nimport com.megaease.easeagent.mock.report.impl.LastJsonReporter;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.api.logging.AccessLogInfo;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.enums.Order;\nimport com.megaease.easeagent.plugin.interceptor.MethodInfo;\nimport com.megaease.easeagent.plugin.tomcat.AccessPlugin;\nimport com.megaease.easeagent.plugin.tomcat.utils.ServletUtils;\nimport com.megaease.easeagent.plugin.tools.metrics.AccessLogServerInfo;\nimport com.megaease.easeagent.plugin.utils.common.HostAddress;\nimport com.megaease.easeagent.plugin.utils.common.JsonUtil;\nimport jakarta.servlet.http.HttpServletResponse;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.mock.web.MockHttpServletRequest;\n\nimport static org.junit.Assert.*;\n\n@RunWith(EaseAgentJunit4ClassRunner.class)\npublic class TomcatHttpLogInterceptorTest {\n\n    @Test\n    public void serverInfo() {\n        MockHttpServletRequest httpServletRequest = TestServletUtils.buildMockRequest();\n        HttpServletResponse response = TestServletUtils.buildMockResponse();\n        TomcatHttpLogInterceptor tomcatHttpLogInterceptor = new TomcatHttpLogInterceptor();\n        AccessLogServerInfo accessLogServerInfo = tomcatHttpLogInterceptor.serverInfo(httpServletRequest, response);\n        assertSame(accessLogServerInfo, tomcatHttpLogInterceptor.serverInfo(httpServletRequest, response));\n    }\n\n    @Test\n    public void doBefore() throws JsonProcessingException {\n        internalAfter();\n    }\n\n    public void verify(AccessLogInfo accessLog, long startTime) {\n        assertEquals(EaseAgent.getConfig(\"system\"), accessLog.getSystem());\n        assertEquals(EaseAgent.getConfig(\"name\"), accessLog.getService());\n        assertEquals(HostAddress.localhost(), accessLog.getHostName());\n        assertEquals(HostAddress.getHostIpv4(), accessLog.getHostIpv4());\n        assertEquals(TestConst.METHOD + \" \" + TestConst.URL, accessLog.getUrl());\n        assertEquals(TestConst.METHOD, accessLog.getMethod());\n        assertEquals(TestConst.FORWARDED_VALUE, accessLog.getHeaders().get(TestConst.FORWARDED_NAME));\n        assertEquals(startTime, accessLog.getBeginTime());\n        assertEquals(\"10\", accessLog.getQueries().get(\"q1\"));\n        assertEquals(\"testq\", accessLog.getQueries().get(\"q2\"));\n        assertEquals(TestConst.FORWARDED_VALUE, accessLog.getClientIP());\n        assertTrue(accessLog.getBeginCpuTime() > 0);\n    }\n\n    @Test\n    public void getAfterMark() {\n        TomcatHttpLogInterceptor tomcatHttpLogInterceptor = new TomcatHttpLogInterceptor();\n        assertNotNull(tomcatHttpLogInterceptor.getAfterMark());\n    }\n\n    private AccessLogInfo getRequestInfo(LastJsonReporter lastJsonReporter) {\n        String result = JsonUtil.toJson(lastJsonReporter.getLastOnlyOne());\n        assertNotNull(result);\n        return JsonUtil.toObject(result, AccessLogInfo.TYPE_REFERENCE);\n    }\n\n    @Test\n    public void internalAfter() throws JsonProcessingException {\n        EaseAgent.agentReport = MockReport.getAgentReport();\n        MockHttpServletRequest httpServletRequest = TestServletUtils.buildMockRequest();\n        HttpServletResponse response = TestServletUtils.buildMockResponse();\n        TomcatHttpLogInterceptor tomcatHttpLogInterceptor = new TomcatHttpLogInterceptor();\n        AccessPlugin accessPlugin = new AccessPlugin();\n        IPluginConfig iPluginConfig = EaseAgent.getConfig(accessPlugin.getDomain(), accessPlugin.getNamespace(), tomcatHttpLogInterceptor.getType());\n        tomcatHttpLogInterceptor.init(iPluginConfig, \"\", \"\", \"\");\n\n        MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{httpServletRequest, response}).build();\n        tomcatHttpLogInterceptor.doBefore(methodInfo, EaseAgent.getContext());\n        Object requestInfoO = httpServletRequest.getAttribute(AccessLogInfo.class.getName());\n        assertNotNull(requestInfoO);\n        assertTrue(requestInfoO instanceof AccessLogInfo);\n        AccessLogInfo accessLog = (AccessLogInfo) requestInfoO;\n        long start = (long) httpServletRequest.getAttribute(ServletUtils.START_TIME);\n        verify(accessLog, start);\n        LastJsonReporter lastJsonReporter = MockEaseAgent.lastMetricJsonReporter(stringObjectMap -> {\n            Object type = stringObjectMap.get(\"type\");\n            return type instanceof String && \"access-log\".equals(type);\n        });\n        tomcatHttpLogInterceptor.doAfter(methodInfo, EaseAgent.getContext());\n        // AccessLogInfo info = getRequestInfo(lastJsonReporter);\n        AccessLogInfo info = MockEaseAgent.getLastLog();\n        verify(info, start);\n        assertEquals(\"200\", info.getStatusCode());\n\n        lastJsonReporter.clean();\n        httpServletRequest = TestServletUtils.buildMockRequest();\n        response = TestServletUtils.buildMockResponse();\n        methodInfo = MethodInfo.builder().args(new Object[]{httpServletRequest, response}).throwable(new RuntimeException(\"test error\")).build();\n        tomcatHttpLogInterceptor.doBefore(methodInfo, EaseAgent.getContext());\n        tomcatHttpLogInterceptor.doAfter(methodInfo, EaseAgent.getContext());\n        // info = getRequestInfo(lastJsonReporter);\n        info = MockEaseAgent.getLastLog();\n        start = (long) httpServletRequest.getAttribute(ServletUtils.START_TIME);\n        verify(info, start);\n        assertEquals(\"500\", info.getStatusCode());\n    }\n\n    @Test\n    public void getType() {\n        TomcatHttpLogInterceptor tomcatHttpLogInterceptor = new TomcatHttpLogInterceptor();\n        assertEquals(Order.LOG.getName(), tomcatHttpLogInterceptor.getType());\n    }\n}\n"
  },
  {
    "path": "plugins/tomcat-jdk17/src/test/resources/mock_agent.properties",
    "content": "name=test-httpservlet-service\nsystem=test-httpservlet-system\neaseagent.progress.forwarded.headers=X-Forwarded-For\nobservability.tracings.tag.response.headers.eg.0=X-EG-Test\n# plugin.observability.tomcat.metric.enabled=true\nplugin.observability.tomcat.metric.interval=1\n"
  },
  {
    "path": "pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  Copyright (c) 2017, MegaEase\n  All rights reserved.\n\n  Licensed under the Apache License, Version 2.0 (the \"License\");\n  you may not use this file except in compliance with the License.\n  You may obtain a copy of the License at\n\n      http://www.apache.org/licenses/LICENSE-2.0\n\n  Unless required by applicable law or agreed to in writing, software\n  distributed under the License is distributed on an \"AS IS\" BASIS,\n  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  See the License for the specific language governing permissions and\n  limitations under the License.\n-->\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <groupId>com.megaease.easeagent</groupId>\n    <artifactId>easeagent</artifactId>\n    <packaging>pom</packaging>\n    <version>2.3.0</version>\n    <modules>\n        <module>plugin-api</module>\n        <module>log4j2</module>\n        <module>core</module>\n        <module>metrics</module>\n        <module>zipkin</module>\n        <module>loader</module>\n        <module>config</module>\n        <module>report</module>\n        <module>httpserver</module>\n        <module>plugins</module>\n        <module>build</module>\n        <module>context</module>\n        <module>mock</module>\n    </modules>\n\n    <properties>\n        <encoding.file>UTF-8</encoding.file>\n        <version.java>8</version.java>\n        <version.junit>4.13.1</version.junit>\n        <!--\n        <version.mockito>1.10.19</version.mockito>\n        -->\n        <version.mockito>4.2.0</version.mockito>\n        <version.guava>32.0.0-jre</version.guava>\n        <version.slf4j>1.7.21</version.slf4j>\n        <version.byte-buddy>1.11.21</version.byte-buddy>\n        <version.auto-service>1.0.1</version.auto-service>\n        <version.javapoet>1.13.0</version.javapoet>\n        <version.jsr305>3.0.2</version.jsr305>\n        <version.lombok>1.18.30</version.lombok>\n        <version.config>1.2.1</version.config>\n        <version.jackson>2.12.7</version.jackson>\n        <version.servlet>3.1.0</version.servlet>\n        <version.brave>5.18.1</version.brave>\n        <version.otel>1.12.0</version.otel>\n        <version.otel-alpha>1.12.0-alpha</version.otel-alpha>\n        <version.zipkin-sender-kafka11>2.8.0</version.zipkin-sender-kafka11>\n        <version.zipkin-reporter2>2.15.0</version.zipkin-reporter2>\n        <version.moco>1.1.0</version.moco>\n        <version.okhttp3>4.9.0</version.okhttp3>\n        <version.disruptor>3.4.2</version.disruptor>\n        <version.commons-lang3>3.11</version.commons-lang3>\n        <version.commons-codec>1.15</version.commons-codec>\n        <version.maven-shade-plugin>3.2.4</version.maven-shade-plugin>\n        <version.log4j>2.17.1</version.log4j>\n        <version.kafka>2.8.2</version.kafka>\n        <version.lettuce>5.3.6.RELEASE</version.lettuce>\n        <version.metrics>4.2.23</version.metrics>\n        <version.jedis>3.5.2</version.jedis>\n        <version.amqp-client>5.11.0</version.amqp-client>\n        <version.prometheus>0.16.0</version.prometheus>\n        <version.sparkjava>2.9.2</version.sparkjava>\n        <version.httpclient>4.5.13</version.httpclient>\n        <version.yaml>1.32</version.yaml>\n    </properties>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.google.code.findbugs</groupId>\n            <artifactId>jsr305</artifactId>\n            <scope>compile</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.projectlombok</groupId>\n            <artifactId>lombok</artifactId>\n            <version>${version.lombok}</version>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>junit</groupId>\n            <artifactId>junit</artifactId>\n            <version>${version.junit}</version>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.mockito</groupId>\n            <artifactId>mockito-core</artifactId>\n            <version>${version.mockito}</version>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.mockito</groupId>\n            <artifactId>mockito-inline</artifactId>\n            <version>${version.mockito}</version>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.awaitility</groupId>\n            <artifactId>awaitility</artifactId>\n            <version>4.1.1</version>\n            <scope>test</scope>\n        </dependency>\n    </dependencies>\n\n    <dependencyManagement>\n        <dependencies>\n            <dependency>\n                <groupId>com.megaease.easeagent</groupId>\n                <artifactId>plugin-api</artifactId>\n                <version>${project.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>com.megaease.easeagent</groupId>\n                <artifactId>plugins</artifactId>\n                <version>${project.version}</version>\n            </dependency>\n\n            <dependency>\n                <groupId>com.megaease.easeagent</groupId>\n                <artifactId>log4j2-api</artifactId>\n                <version>${project.version}</version>\n            </dependency>\n\n            <dependency>\n                <groupId>com.squareup</groupId>\n                <artifactId>javapoet</artifactId>\n                <version>${version.javapoet}</version>\n            </dependency>\n\n            <dependency>\n                <groupId>com.google.auto.service</groupId>\n                <artifactId>auto-service</artifactId>\n                <version>${version.auto-service}</version>\n            </dependency>\n\n            <dependency>\n                <groupId>com.google.auto.service</groupId>\n                <artifactId>auto-service-annotations</artifactId>\n                <version>${version.auto-service}</version>\n            </dependency>\n\n            <dependency>\n                <groupId>com.google.code.findbugs</groupId>\n                <artifactId>jsr305</artifactId>\n                <version>${version.jsr305}</version>\n                <scope>compile</scope>\n            </dependency>\n            <dependency>\n                <groupId>junit</groupId>\n                <artifactId>junit</artifactId>\n                <version>${version.junit}</version>\n            </dependency>\n            <dependency>\n                <groupId>io.projectreactor</groupId>\n                <artifactId>reactor-bom</artifactId>\n                <version>2020.0.4</version>\n                <type>pom</type>\n                <scope>import</scope>\n            </dependency>\n            <dependency>\n                <groupId>net.bytebuddy</groupId>\n                <artifactId>byte-buddy</artifactId>\n                <version>${version.byte-buddy}</version>\n            </dependency>\n            <dependency>\n                <groupId>net.bytebuddy</groupId>\n                <artifactId>byte-buddy-agent</artifactId>\n                <version>${version.byte-buddy}</version>\n            </dependency>\n            <dependency>\n                <groupId>com.google.guava</groupId>\n                <artifactId>guava</artifactId>\n                <version>${version.guava}</version>\n            </dependency>\n            <dependency>\n                <groupId>org.slf4j</groupId>\n                <artifactId>slf4j-api</artifactId>\n                <version>${version.slf4j}</version>\n            </dependency>\n            <dependency>\n                <groupId>javax.servlet</groupId>\n                <artifactId>javax.servlet-api</artifactId>\n                <version>${version.servlet}</version>\n            </dependency>\n\n            <dependency>\n                <groupId>io.opentelemetry</groupId>\n                <artifactId>opentelemetry-sdk</artifactId>\n                <version>${version.otel}</version>\n            </dependency>\n\n            <dependency>\n                <groupId>io.opentelemetry</groupId>\n                <artifactId>opentelemetry-sdk-common</artifactId>\n                <version>${version.otel}</version>\n            </dependency>\n\n            <dependency>\n                <groupId>io.opentelemetry</groupId>\n                <artifactId>opentelemetry-semconv</artifactId>\n                <version>${version.otel-alpha}</version>\n            </dependency>\n\n            <dependency>\n                <groupId>io.opentelemetry</groupId>\n                <artifactId>opentelemetry-sdk-logs</artifactId>\n                <version>${version.otel-alpha}</version>\n            </dependency>\n\n            <dependency>\n                <groupId>io.zipkin.brave</groupId>\n                <artifactId>brave</artifactId>\n                <version>${version.brave}</version>\n            </dependency>\n            <dependency>\n                <groupId>io.zipkin.brave</groupId>\n                <artifactId>brave-instrumentation-http</artifactId>\n                <version>${version.brave}</version>\n            </dependency>\n            <dependency>\n                <groupId>io.zipkin.brave</groupId>\n                <artifactId>brave-context-log4j2</artifactId>\n                <version>${version.brave}</version>\n            </dependency>\n            <dependency>\n                <groupId>io.zipkin.brave</groupId>\n                <artifactId>brave-context-slf4j</artifactId>\n                <version>${version.brave}</version>\n            </dependency>\n            <dependency>\n                <groupId>io.zipkin.reporter2</groupId>\n                <artifactId>zipkin-sender-kafka11</artifactId>\n                <version>${version.zipkin-sender-kafka11}</version>\n                <exclusions>\n                    <exclusion>\n                        <artifactId>*</artifactId>\n                        <groupId>*</groupId>\n                    </exclusion>\n                </exclusions>\n            </dependency>\n            <dependency>\n                <groupId>io.zipkin.reporter2</groupId>\n                <artifactId>zipkin-sender-urlconnection</artifactId>\n                <version>${version.zipkin-reporter2}</version>\n            </dependency>\n            <dependency>\n                <groupId>com.github.dreamhead</groupId>\n                <artifactId>moco-core</artifactId>\n                <version>${version.moco}</version>\n            </dependency>\n            <dependency>\n                <groupId>org.apache.logging.log4j</groupId>\n                <artifactId>log4j-slf4j-impl</artifactId>\n                <version>${version.log4j}</version>\n            </dependency>\n            <dependency>\n                <groupId>org.apache.logging.log4j</groupId>\n                <artifactId>log4j-core</artifactId>\n                <version>${version.log4j}</version>\n            </dependency>\n            <dependency>\n                <groupId>com.lmax</groupId>\n                <artifactId>disruptor</artifactId>\n                <version>${version.disruptor}</version>\n            </dependency>\n            <dependency>\n                <groupId>org.apache.commons</groupId>\n                <artifactId>commons-lang3</artifactId>\n                <version>${version.commons-lang3}</version>\n            </dependency>\n\n            <dependency>\n                <groupId>commons-codec</groupId>\n                <artifactId>commons-codec</artifactId>\n                <version>${version.commons-codec}</version>\n            </dependency>\n\n            <dependency>\n                <groupId>org.apache.kafka</groupId>\n                <artifactId>kafka-log4j-appender</artifactId>\n                <version>${version.kafka}</version>\n            </dependency>\n            <dependency>\n                <groupId>org.apache.kafka</groupId>\n                <artifactId>kafka-clients</artifactId>\n                <version>${version.kafka}</version>\n            </dependency>\n            <dependency>\n                <groupId>com.fasterxml.jackson.core</groupId>\n                <artifactId>jackson-core</artifactId>\n                <version>${version.jackson}</version>\n            </dependency>\n            <dependency>\n                <groupId>com.fasterxml.jackson.core</groupId>\n                <artifactId>jackson-databind</artifactId>\n                <version>${version.jackson}</version>\n            </dependency>\n            <dependency>\n                <groupId>com.squareup.okhttp3</groupId>\n                <artifactId>okhttp</artifactId>\n                <version>${version.okhttp3}</version>\n            </dependency>\n            <dependency>\n                <groupId>com.squareup.okhttp3</groupId>\n                <artifactId>okhttp-tls</artifactId>\n                <version>${version.okhttp3}</version>\n            </dependency>\n            <dependency>\n                <groupId>io.lettuce</groupId>\n                <artifactId>lettuce-core</artifactId>\n                <version>${version.lettuce}</version>\n            </dependency>\n            <dependency>\n                <groupId>io.dropwizard.metrics</groupId>\n                <artifactId>metrics-core</artifactId>\n                <version>${version.metrics}</version>\n            </dependency>\n            <dependency>\n                <groupId>redis.clients</groupId>\n                <artifactId>jedis</artifactId>\n                <version>${version.jedis}</version>\n            </dependency>\n            <dependency>\n                <groupId>io.zipkin.brave</groupId>\n                <artifactId>brave-instrumentation-kafka-clients</artifactId>\n                <version>${version.brave}</version>\n            </dependency>\n            <dependency>\n                <groupId>io.zipkin.brave</groupId>\n                <artifactId>brave-instrumentation-messaging</artifactId>\n                <version>${version.brave}</version>\n            </dependency>\n            <dependency>\n                <groupId>io.zipkin.brave</groupId>\n                <artifactId>brave-instrumentation-rpc</artifactId>\n                <version>${version.brave}</version>\n            </dependency>\n            <dependency>\n                <groupId>io.prometheus</groupId>\n                <artifactId>simpleclient_dropwizard</artifactId>\n                <version>${version.prometheus}</version>\n            </dependency>\n            <dependency>\n                <groupId>io.prometheus</groupId>\n                <artifactId>simpleclient_httpserver</artifactId>\n                <version>${version.prometheus}</version>\n            </dependency>\n            <dependency>\n                <groupId>io.prometheus</groupId>\n                <artifactId>simpleclient_servlet</artifactId>\n                <version>${version.prometheus}</version>\n            </dependency>\n            <dependency>\n                <groupId>io.prometheus</groupId>\n                <artifactId>simpleclient_common</artifactId>\n                <version>${version.prometheus}</version>\n            </dependency>\n\n            <dependency>\n                <groupId>org.apache.httpcomponents</groupId>\n                <artifactId>httpclient</artifactId>\n                <version>${version.httpclient}</version>\n            </dependency>\n            <dependency>\n                <groupId>org.apache.httpcomponents.client5</groupId>\n                <artifactId>httpclient5</artifactId>\n                <version>5.1</version>\n            </dependency>\n\n            <dependency>\n                <groupId>org.yaml</groupId>\n                <artifactId>snakeyaml</artifactId>\n                <version>${version.yaml}</version>\n            </dependency>\n        </dependencies>\n    </dependencyManagement>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-compiler-plugin</artifactId>\n                <version>3.6.1</version>\n                <configuration>\n                    <source>${version.java}</source>\n                    <target>${version.java}</target>\n                    <encoding>${encoding.file}</encoding>\n                    <compilerArgs>\n                        <arg>-Xlint:unchecked</arg>\n                    </compilerArgs>\n                </configuration>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-resources-plugin</artifactId>\n                <version>3.0.1</version>\n                <configuration>\n                    <encoding>${encoding.file}</encoding>\n                </configuration>\n            </plugin>\n            <plugin>\n                <groupId>org.projectlombok</groupId>\n                <artifactId>lombok-maven-plugin</artifactId>\n                <version>1.18.20.0</version>\n                <configuration>\n                    <encoding>${encoding.file}</encoding>\n                </configuration>\n                <executions>\n                    <execution>\n                        <phase>generate-sources</phase>\n                        <goals>\n                            <goal>delombok</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n            <plugin>\n                <groupId>org.codehaus.mojo</groupId>\n                <artifactId>cobertura-maven-plugin</artifactId>\n                <version>2.7</version>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-release-plugin</artifactId>\n                <version>2.5.3</version>\n            </plugin>\n\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-surefire-plugin</artifactId>\n                <version>2.18.1</version>\n                <configuration>\n                    <systemPropertyVariables>\n                        <EASEAGENT-SLF4J2-USE-CURRENT>true</EASEAGENT-SLF4J2-USE-CURRENT>\n                    </systemPropertyVariables>\n                </configuration>\n            </plugin>\n        </plugins>\n    </build>\n\n</project>\n"
  },
  {
    "path": "release",
    "content": "#! /bin/bash\n# After `mvn clean package -am -pl build`, this script can help you to release `easeagent-dep.jar` to github.\n\nRELEASE_ID=41698268\nLIST_RELEASE_URL=\"https://api.github.com/repos/megaease/easeagent/releases\"\nRELEASE_URL=\"https://api.github.com/repos/megaease/easeagent/releases/${RELEASE_ID}\"\nUPLOAD_URL=\"https://uploads.github.com/repos/megaease/easeagent/releases/${RELEASE_ID}/assets?name=easeagent.jar\"\nTOKEN=\"\"\nAUTH_HEAD=\"Authorization: token $TOKEN\"\nLIB_PATH=\"build/target/easeagent-dep.jar\"\nASSET_URL=`curl -X GET -H \"$AUTH_HEAD\" \"$RELEASE_URL\" | jq --raw-output '.assets[0].url'`\n\necho \"Delete last asset is $ASSET_URL\"\n\ncurl -X DELETE -H \"$AUTH_HEAD\" \"$ASSET_URL\"\n\nMD5=`md5 -q $LIB_PATH`\n\necho \"Update easeagent hash: $MD5\"\n\ncurl -X PATCH -H \"$AUTH_HEAD\" -H \"Content-Type: application/json\" -d \"{\\\"body\\\":\\\"MD5(easeagent.jar) = \\`$MD5\\`\\\"}\" \"$RELEASE_URL\" > /dev/null\n\necho \"Upload easeagent.jar\"\n\ncurl -X POST -H \"$AUTH_HEAD\" -H \"Content-Type: application/java-archive\" --progress-bar --data-binary @$LIB_PATH \"$UPLOAD_URL\""
  },
  {
    "path": "report/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright (c) 2021, MegaEase\n  ~ All rights reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~   http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>easeagent</artifactId>\n        <groupId>com.megaease.easeagent</groupId>\n        <version>2.3.0</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>report</artifactId>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>config</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>plugin-api</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n\n        <dependency>\n            <groupId>org.slf4j</groupId>\n            <artifactId>slf4j-api</artifactId>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.apache.logging.log4j</groupId>\n            <artifactId>log4j-core</artifactId>\n            <scope>provided</scope>\n        </dependency>\n\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>log4j2-api</artifactId>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>log4j2-mock</artifactId>\n            <version>${project.version}</version>\n            <scope>test</scope>\n        </dependency>\n\n        <dependency>\n            <groupId>org.apache.commons</groupId>\n            <artifactId>commons-lang3</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>com.google.guava</groupId>\n            <artifactId>guava</artifactId>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.apache.kafka</groupId>\n            <artifactId>kafka-clients</artifactId>\n            <exclusions>\n                <exclusion>\n                    <groupId>org.slf4j</groupId>\n                    <artifactId>slf4j-log4j12</artifactId>\n                </exclusion>\n                <exclusion>\n                    <groupId>log4j</groupId>\n                    <artifactId>log4j</artifactId>\n                </exclusion>\n            </exclusions>\n            <!--            <optional>true</optional>-->\n        </dependency>\n\n        <dependency>\n            <groupId>io.zipkin.reporter2</groupId>\n            <artifactId>zipkin-sender-kafka11</artifactId>\n            <!--            <optional>true</optional>-->\n        </dependency>\n\n        <dependency>\n            <groupId>io.zipkin.brave</groupId>\n            <artifactId>brave</artifactId>\n            <!--            <optional>true</optional>-->\n        </dependency>\n        <dependency>\n            <groupId>com.google.auto.service</groupId>\n            <artifactId>auto-service</artifactId>\n            <scope>provided</scope>\n        </dependency>\n\n        <dependency>\n            <groupId>com.squareup.okhttp3</groupId>\n            <artifactId>okhttp</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>com.squareup.okhttp3</groupId>\n            <artifactId>okhttp-tls</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>com.fasterxml.jackson.core</groupId>\n            <artifactId>jackson-databind</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>io.opentelemetry</groupId>\n            <artifactId>opentelemetry-sdk-logs</artifactId>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-shade-plugin</artifactId>\n                <version>${version.maven-shade-plugin}</version>\n                <configuration>\n                    <minimizeJar>true</minimizeJar>\n                    <relocations>\n                        <relocation>\n                            <pattern>org.apache.kafka</pattern>\n                            <shadedPattern>com.megaease.easeagent.org.apache.kafka</shadedPattern>\n                        </relocation>\n                        <relocation>\n                            <pattern>okhttp3</pattern>\n                            <shadedPattern>easeagent.okhttp3</shadedPattern>\n                        </relocation>\n                    </relocations>\n                </configuration>\n                <executions>\n                    <execution>\n                        <phase>package</phase>\n                        <goals>\n                            <goal>shade</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n        </plugins>\n    </build>\n</project>\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/AgentReportAware.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.report;\n\nimport com.megaease.easeagent.plugin.report.AgentReport;\n\npublic interface AgentReportAware {\n    void setAgentReport(AgentReport report);\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/DefaultAgentReport.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.report;\n\nimport com.megaease.easeagent.config.Configs;\nimport com.megaease.easeagent.config.report.ReportConfigAdapter;\nimport com.megaease.easeagent.plugin.api.config.ChangeItem;\nimport com.megaease.easeagent.plugin.api.config.Config;\nimport com.megaease.easeagent.plugin.api.config.ConfigChangeListener;\nimport com.megaease.easeagent.plugin.api.logging.AccessLogInfo;\nimport com.megaease.easeagent.plugin.api.otlp.common.AgentLogData;\nimport com.megaease.easeagent.plugin.report.AgentReport;\nimport com.megaease.easeagent.plugin.report.metric.MetricReporterFactory;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport com.megaease.easeagent.report.async.log.AccessLogReporter;\nimport com.megaease.easeagent.report.async.log.ApplicationLogReporter;\nimport com.megaease.easeagent.report.metric.MetricReporterFactoryImpl;\nimport com.megaease.easeagent.report.plugin.ReporterLoader;\nimport com.megaease.easeagent.report.trace.TraceReport;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.util.List;\nimport java.util.Map;\n\n@Slf4j\npublic class DefaultAgentReport implements AgentReport, ConfigChangeListener {\n    private final TraceReport traceReport;\n    private final MetricReporterFactory metricReporterFactory;\n    private final AccessLogReporter accessLogReporter;\n    private final ApplicationLogReporter appLogReporter;\n    private final Config config;\n    private final Config reportConfig;\n\n    DefaultAgentReport(Config config) {\n        this.config = config;\n        this.reportConfig = new Configs(ReportConfigAdapter.extractReporterConfig(config));\n        this.traceReport = new TraceReport(this.reportConfig);\n        this.accessLogReporter = new AccessLogReporter(this.reportConfig);\n        this.appLogReporter = new ApplicationLogReporter(this.reportConfig);\n        this.metricReporterFactory = MetricReporterFactoryImpl.create(this.reportConfig);\n        this.config.addChangeListener(this);\n    }\n\n    public static AgentReport create(Configs config) {\n        ReporterLoader.load();\n        return new DefaultAgentReport(config);\n    }\n\n    @Override\n    public void report(ReportSpan span) {\n        this.traceReport.report(span);\n    }\n\n    @Override\n    public void report(AccessLogInfo log) {\n        this.accessLogReporter.report(log);\n    }\n\n    @Override\n    public void report(AgentLogData log) {\n        this.appLogReporter.report(log);\n    }\n\n    @Override\n    public MetricReporterFactory metricReporter() {\n        return this.metricReporterFactory;\n    }\n\n    @Override\n    public void onChange(List<ChangeItem> list) {\n        Map<String, String> changes = ReportConfigAdapter.extractReporterConfig(this.config);\n        this.reportConfig.updateConfigs(changes);\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/GlobalExtractor.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.report;\n\nimport com.megaease.easeagent.config.AutoRefreshConfigItem;\nimport com.megaease.easeagent.plugin.api.config.Config;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.report.encoder.span.GlobalExtrasSupplier;\n\npublic class GlobalExtractor implements GlobalExtrasSupplier {\n    static volatile GlobalExtractor instance;\n    final AutoRefreshConfigItem<String> serviceName;\n    final AutoRefreshConfigItem<String> systemName;\n\n    public static GlobalExtractor getInstance(Config configs) {\n        if (instance == null) {\n            synchronized (GlobalExtractor.class) {\n                if (instance != null) {\n                    return instance;\n                }\n                instance = new GlobalExtractor(configs);\n            }\n        }\n        return instance;\n    }\n\n    private GlobalExtractor(Config configs) {\n        serviceName = new AutoRefreshConfigItem<>(configs, ConfigConst.SERVICE_NAME, Config::getString);\n        systemName = new AutoRefreshConfigItem<>(configs, ConfigConst.SYSTEM_NAME, Config::getString);\n    }\n\n    @Override\n    public String service() {\n        return serviceName.getValue();\n    }\n\n    @Override\n    public String system() {\n        return systemName.getValue();\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/OutputProperties.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.report;\n\nimport com.megaease.easeagent.config.Configs;\nimport com.megaease.easeagent.plugin.api.config.Config;\n\nimport java.util.Map;\n\nimport static com.megaease.easeagent.config.ConfigUtils.bindProp;\nimport static com.megaease.easeagent.config.ConfigUtils.isChanged;\nimport static com.megaease.easeagent.config.report.ReportConfigConst.*;\nimport static com.megaease.easeagent.plugin.api.config.ConfigConst.Observability.*;\n\npublic interface OutputProperties {\n    String getServers();\n\n    String getTimeout();\n\n    Boolean isEnabled();\n\n    String getSecurityProtocol();\n\n    String getSSLKeyStoreType();\n\n    String getKeyStoreKey();\n\n    String getKeyStoreCertChain();\n\n    String getTrustCertificate();\n\n    String getTrustCertificateType();\n\n    String getEndpointAlgorithm();\n\n    boolean updateConfig(Map<String, String> changed);\n\n    static OutputProperties newDefault(Config configs) {\n        return new Default(configs);\n    }\n\n    class Default implements OutputProperties {\n        private volatile String endpointAlgorithm = \"\";\n        private volatile String trustCertificateType = \"\";\n        private volatile String trustCertificate = \"\";\n        private volatile String servers = \"\";\n        private volatile String timeout = \"\";\n        private volatile boolean enabled;\n        private volatile String protocol = \"\";\n        private volatile String sslKeyStoreType = \"\";\n        private volatile String sslKey = \"\";\n        private volatile String certificate = \"\";\n\n\n        public Default(Config configs) {\n            extractProp(configs);\n        }\n\n        @Override\n        public boolean updateConfig(Map<String, String> changed) {\n            Configs configs = new Configs(changed);\n            int changeItems = 0;\n            changeItems += isChanged(OUTPUT_SERVERS, changed, this.servers);\n            changeItems += isChanged(OUTPUT_TIMEOUT, changed, this.timeout);\n            changeItems += isChanged(OUTPUT_ENABLED, changed, String.valueOf(this.enabled));\n            changeItems += isChanged(OUTPUT_SECURITY_PROTOCOL, changed, this.protocol);\n            changeItems += isChanged(OUTPUT_SSL_KEYSTORE_TYPE, changed, this.sslKeyStoreType);\n            changeItems += isChanged(OUTPUT_KEY, changed, this.sslKey);\n            changeItems += isChanged(OUTPUT_CERT, changed, this.certificate);\n            changeItems += isChanged(OUTPUT_TRUST_CERT, changed, this.trustCertificate);\n            changeItems += isChanged(OUTPUT_TRUST_CERT_TYPE, changed, this.trustCertificateType);\n            changeItems += isChanged(OUTPUT_ENDPOINT_IDENTIFICATION_ALGORITHM, changed, this.endpointAlgorithm);\n\n            // if there are v2 configuration items, override with v2 config.\n            changeItems += isChanged(BOOTSTRAP_SERVERS, changed, this.servers);\n            changeItems += isChanged(OUTPUT_SERVERS_TIMEOUT, changed, this.timeout);\n            changeItems += isChanged(OUTPUT_SERVERS_ENABLE, changed, String.valueOf(this.enabled));\n            changeItems += isChanged(OUTPUT_SECURITY_PROTOCOL_V2, changed, this.protocol);\n            changeItems += isChanged(OUTPUT_SSL_KEYSTORE_TYPE_V2, changed, this.sslKeyStoreType);\n            changeItems += isChanged(OUTPUT_KEY_V2, changed, this.sslKey);\n            changeItems += isChanged(OUTPUT_CERT_V2, changed, this.certificate);\n            changeItems += isChanged(OUTPUT_TRUST_CERT_V2, changed, this.trustCertificate);\n            changeItems += isChanged(OUTPUT_TRUST_CERT_TYPE_V2, changed, this.trustCertificateType);\n            changeItems += isChanged(OUTPUT_ENDPOINT_IDENTIFICATION_ALGORITHM_V2, changed, this.endpointAlgorithm);\n            if (changeItems == 0) {\n                return false;\n            }\n            extractProp(configs);\n            return true;\n        }\n\n        private void extractProp(Config configs) {\n            bindProp(OUTPUT_SERVERS, configs, Config::getString, v -> this.servers = v);\n            bindProp(OUTPUT_TIMEOUT, configs, Config::getString, v -> this.timeout = v);\n            bindProp(OUTPUT_ENABLED, configs, Config::getBoolean, v -> this.enabled = v);\n            bindProp(OUTPUT_SECURITY_PROTOCOL, configs, Config::getString, v -> this.protocol = v);\n            bindProp(OUTPUT_SSL_KEYSTORE_TYPE, configs, Config::getString, v -> this.sslKeyStoreType = v);\n            bindProp(OUTPUT_KEY, configs, Config::getString, v -> this.sslKey = v);\n            bindProp(OUTPUT_CERT, configs, Config::getString, v -> this.certificate = v);\n            bindProp(OUTPUT_TRUST_CERT, configs, Config::getString, v -> this.trustCertificate = v);\n            bindProp(OUTPUT_TRUST_CERT_TYPE, configs, Config::getString, v -> this.trustCertificateType = v);\n            bindProp(OUTPUT_ENDPOINT_IDENTIFICATION_ALGORITHM, configs, Config::getString, v -> this.endpointAlgorithm = v);\n\n            // if there are v2 configuration items, override with v2 config.\n            bindProp(BOOTSTRAP_SERVERS, configs, Config::getString, v -> this.servers = v);\n            bindProp(OUTPUT_SERVERS_TIMEOUT, configs, Config::getString, v -> this.timeout = v);\n            bindProp(OUTPUT_SERVERS_ENABLE, configs, Config::getBoolean, v -> this.enabled = v);\n            bindProp(OUTPUT_SECURITY_PROTOCOL_V2, configs, Config::getString, v -> this.protocol = v);\n            bindProp(OUTPUT_SSL_KEYSTORE_TYPE_V2, configs, Config::getString, v -> this.sslKeyStoreType = v);\n            bindProp(OUTPUT_KEY_V2, configs, Config::getString, v -> this.sslKey = v);\n            bindProp(OUTPUT_CERT_V2, configs, Config::getString, v -> this.certificate = v);\n            bindProp(OUTPUT_TRUST_CERT_V2, configs, Config::getString, v -> this.trustCertificate = v);\n            bindProp(OUTPUT_TRUST_CERT_TYPE_V2, configs, Config::getString, v -> this.trustCertificateType = v);\n            bindProp(OUTPUT_ENDPOINT_IDENTIFICATION_ALGORITHM_V2, configs, Config::getString, v -> this.endpointAlgorithm = v);\n        }\n\n        @Override\n        public String getServers() {\n            return this.servers;\n        }\n\n        @Override\n        public String getTimeout() {\n            return this.timeout;\n        }\n\n        @Override\n        public Boolean isEnabled() {\n            return this.enabled;\n        }\n\n        @Override\n        public String getSecurityProtocol() {\n            return this.protocol;\n        }\n\n        @Override\n        public String getSSLKeyStoreType() {\n            return this.sslKeyStoreType;\n        }\n\n        @Override\n        public String getKeyStoreKey() {\n            return this.sslKey;\n        }\n\n        @Override\n        public String getKeyStoreCertChain() {\n            return this.certificate;\n        }\n\n        @Override\n        public String getTrustCertificate() {\n            return this.trustCertificate;\n        }\n\n        @Override\n        public String getTrustCertificateType() {\n            return this.trustCertificateType;\n        }\n\n        @Override\n        public String getEndpointAlgorithm() {\n            return this.endpointAlgorithm;\n        }\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/ReportConfigChange.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.report;\n\nimport java.util.Map;\n\npublic interface ReportConfigChange {\n    void updateConfigs(Map<String, String> outputProperties);\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/async/AsyncProps.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.report.async;\n\npublic interface AsyncProps {\n    int getReportThread();\n\n    int getQueuedMaxItems();\n\n    long getMessageTimeout();\n\n    int getQueuedMaxSize();\n\n    int getMessageMaxBytes();\n\n    static int onePercentOfMemory() {\n        long result = (long) (Runtime.getRuntime().totalMemory() * 0.01);\n        // don't overflow in the rare case 1% of memory is larger than 2 GiB!\n        return (int) Math.max(Math.min(Integer.MAX_VALUE, result), Integer.MIN_VALUE);\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/async/AsyncReporter.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.report.async;\n\nimport com.megaease.easeagent.plugin.api.config.ConfigChangeListener;\nimport com.megaease.easeagent.report.sender.SenderWithEncoder;\n\nimport java.util.List;\nimport java.util.concurrent.ThreadFactory;\n\npublic interface AsyncReporter<S> extends ConfigChangeListener {\n    void setFlushThreads(List<Thread> flushThreads);\n\n    void setSender(SenderWithEncoder sender);\n\n    SenderWithEncoder getSender();\n\n    void setPending(int queuedMaxSpans, int queuedMaxBytes);\n\n    void setMessageTimeoutNanos(long messageTimeoutNanos);\n\n    /**\n     * Returns true if the was encoded and accepted onto the queue.\n     */\n    void report(S next);\n\n    void flush();\n\n    boolean check();\n\n    void close();\n\n    void setThreadFactory(ThreadFactory threadFactory);\n\n    void startFlushThread();\n\n    void closeFlushThread();\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/async/AsyncReporterMetrics.java",
    "content": "package com.megaease.easeagent.report.async;\n\n/*\n * Copyright 2016-2019 The OpenZipkin Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except\n * in compliance with the License. You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under the License\n * is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express\n * or implied. See the License for the specific language governing permissions and limitations under\n * the License.\n */\n\n/**\n *  borrow from zipkin2.reporter.ReporterMetrics\n */\n@SuppressWarnings(\"unused\")\npublic interface AsyncReporterMetrics {\n\n    /**\n     * Increments count of message attempts, which contain 1 or more items(spans/logs). Ex POST requests or Kafka\n     * messages sent.\n     */\n    void incrementMessages();\n\n    /**\n     * Increments count of messages that could not be sent. Ex host unavailable, or peer disconnect.\n     */\n    void incrementMessagesDropped(Throwable cause);\n\n    /**\n     * Increments the count of items(spans/logs) reported. When {@link AsyncReporter} is used, reported items will\n     * usually be a larger number than messages.\n     */\n    void incrementItems(int quantity);\n\n    /**\n     * Increments the number of encoded item(span/log) bytes reported.\n     */\n    void incrementSpanBytes(int quantity);\n\n    /**\n     * Increments the number of bytes containing encoded spans in a message.\n     *\n     * <p>This is a function of item(span/access-log) bytes per message and overhead\n     */\n    void incrementMessageBytes(int quantity);\n\n    /**\n     * Increments the count of spans dropped for any reason. For example, failure queueing or\n     * sending.\n     */\n    void incrementItemsDropped(int quantity);\n\n    /** Updates the count of items(spans/logs) pending, following a flush activity. */\n    void updateQueuedItems(int update);\n\n    /** Updates the count of encoded items(spans/logs) bytes pending, following a flush activity. */\n    void updateQueuedBytes(int update);\n\n    AsyncReporterMetrics NOOP_METRICS = new AsyncReporterMetrics() {\n        @Override public void incrementMessages() {\n            // noop\n        }\n\n        @Override public void incrementMessagesDropped(Throwable cause) {\n            // noop\n        }\n\n        @Override public void incrementItems(int quantity) {\n            // noop\n        }\n\n        @Override public void incrementSpanBytes(int quantity) {\n            // noop\n        }\n\n        @Override public void incrementMessageBytes(int quantity) {\n            // noop\n        }\n\n        @Override public void incrementItemsDropped(int quantity) {\n            // noop\n        }\n\n        @Override public void updateQueuedItems(int update) {\n            // noop\n        }\n\n        @Override public void updateQueuedBytes(int update) {\n            // noop\n        }\n\n        @Override public String toString() {\n            return \"NoOpReporterMetrics\";\n        }\n    };\n}\n\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/async/DefaultAsyncReporter.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.report.async;\n\nimport com.megaease.easeagent.plugin.api.config.ChangeItem;\nimport com.megaease.easeagent.plugin.report.EncodedData;\nimport com.megaease.easeagent.plugin.report.Encoder;\nimport com.megaease.easeagent.report.async.zipkin.AgentBufferNextMessage;\nimport com.megaease.easeagent.report.async.zipkin.AgentByteBoundedQueue;\nimport com.megaease.easeagent.report.encoder.PackedMessage;\nimport com.megaease.easeagent.report.encoder.PackedMessage.DefaultPackedMessage;\nimport com.megaease.easeagent.report.encoder.span.GlobalExtrasSupplier;\nimport com.megaease.easeagent.report.sender.SenderWithEncoder;\nimport lombok.SneakyThrows;\nimport zipkin2.Call;\n\nimport java.io.IOException;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.*;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\n\nimport static java.lang.String.format;\nimport static java.util.logging.Level.FINE;\nimport static java.util.logging.Level.WARNING;\n\n@SuppressWarnings(\"unused\")\npublic class DefaultAsyncReporter<S> implements AsyncReporter<S> {\n    static final String NAME_PREFIX = \"DefaultAsyncReporter\";\n    static final Logger logger = Logger.getLogger(DefaultAsyncReporter.class.getName());\n    final AsyncReporterMetrics metrics;\n\n    final AtomicBoolean closed = new AtomicBoolean(false);\n\n    AgentByteBoundedQueue<S> pending;\n    final CountDownLatch close;\n\n    final int messageMaxBytes;\n    final long closeTimeoutNanos;\n    long messageTimeoutNanos;\n    ThreadFactory threadFactory;\n\n    SenderWithEncoder sender;\n    Encoder<S> encoder;\n\n    AsyncProps asyncProperties;\n\n    /*\n     * Tracks if we should log the first instance of an exception in flush().\n     */\n    private boolean shouldWarnException = true;\n\n    List<Thread> flushThreads;\n\n    DefaultAsyncReporter(Builder builder, AsyncProps asyncProperties) {\n        this.asyncProperties = asyncProperties;\n\n        this.pending = new AgentByteBoundedQueue<>(builder.queuedMaxItems, builder.queuedMaxBytes);\n        this.messageMaxBytes = builder.messageMaxBytes;\n        this.messageTimeoutNanos = builder.messageTimeoutNanos;\n        this.closeTimeoutNanos = builder.closeTimeoutNanos;\n        this.close = new CountDownLatch(builder.messageTimeoutNanos > 0 ? 1 : 0);\n\n        this.metrics = builder.metrics;\n        this.sender = builder.sender;\n        this.encoder = builder.sender.getEncoder();\n    }\n\n    public static <S> AsyncReporter<S> builderAsyncReporter(SenderWithEncoder sender,\n                                                                           AsyncProps asyncProperties) {\n        return new Builder(sender, asyncProperties).build();\n    }\n\n    @Override\n    public void setFlushThreads(List<Thread> flushThreads) {\n        this.flushThreads = flushThreads;\n    }\n\n    //modify sender\n    public SenderWithEncoder getSender() {\n        return this.sender;\n    }\n\n    @Override\n    public void setSender(SenderWithEncoder sender) {\n        this.sender = sender;\n        this.encoder = sender.getEncoder();\n    }\n\n    public void setAsyncProperties(AsyncProps asyncProperties) {\n        this.asyncProperties = asyncProperties;\n    }\n\n    @Override\n    public void setPending(int queuedMaxSpans, int queuedMaxBytes) {\n        AgentByteBoundedQueue<S> copyPending = this.pending;\n        this.pending = new AgentByteBoundedQueue<>(queuedMaxSpans, queuedMaxBytes);\n        consumerData(copyPending);\n    }\n\n    private void consumerData(final AgentByteBoundedQueue<S> copyPending) {\n        Thread flushThread = this.threadFactory.newThread((() -> {\n            final AgentBufferNextMessage<S> bufferNextMessage = AgentBufferNextMessage\n                .create(encoder, messageMaxBytes, 0);\n            while (copyPending.getCount() > 0) {\n                flush(bufferNextMessage, copyPending);\n            }\n        }));\n        flushThread.setName(\"TempAsyncReporter{\" + this.sender + \"}\");\n        flushThread.setDaemon(true);\n        flushThread.start();\n    }\n\n    public void setMessageTimeoutNanos(long messageTimeoutNanos) {\n        this.messageTimeoutNanos = messageTimeoutNanos;\n    }\n\n    /**\n     * Returns true if the was encoded and accepted onto the queue.\n     */\n    @SneakyThrows\n    public void report(S next) {\n        if (!this.sender.isAvailable()) {\n            return;\n        }\n\n        metrics.incrementItems(1);\n        int nextSizeInBytes = encoder.sizeInBytes(next);\n        int messageSizeOfNextSpan = encoder.packageSizeInBytes(Collections.singletonList(nextSizeInBytes));\n        metrics.incrementSpanBytes(nextSizeInBytes);\n        if (closed.get() ||\n            // don't enqueue something larger than we can drain\n            messageSizeOfNextSpan > messageMaxBytes ||\n            !pending.offer(next, nextSizeInBytes)) {\n            metrics.incrementItemsDropped(1);\n        }\n    }\n\n    public final void flush() {\n        if (!this.sender.isAvailable()) {\n            return;\n        }\n\n        flush(AgentBufferNextMessage.create(encoder, messageMaxBytes, 0), pending);\n    }\n\n\n    void flush(AgentBufferNextMessage<S> bundler, AgentByteBoundedQueue<S> pending) {\n        if (closed.get()) {\n            throw new IllegalStateException(\"closed\");\n        }\n\n        pending.drainTo(bundler, bundler.remainingNanos());\n\n        // record after flushing reduces the amount of gauge events vs on doing this on report\n        metrics.updateQueuedItems(pending.getCount());\n        metrics.updateQueuedBytes(pending.getSizeInBytes());\n\n        // loop around if we are running, and the bundle isn't full\n        // if we are closed, try to send what's pending\n        if (!bundler.isReady() && !closed.get()) {\n            return;\n        }\n\n        // Signal that we are about to send a message of a known size in bytes\n        metrics.incrementMessages();\n        metrics.incrementMessageBytes(bundler.sizeInBytes());\n\n        // Create the next message. Since we are outside the lock shared with writers, we can encode\n        PackedMessage message = new DefaultPackedMessage(bundler.count(), encoder);\n        bundler.drain((next, nextSizeInBytes) -> {\n            if (message.calculateAppendSize(nextSizeInBytes) <= messageMaxBytes) {\n                message.addMessage(encoder.encode(next));\n                return true;\n            } else {\n                return false;\n            }\n        });\n\n        List<EncodedData> nextMessage = message.getMessages();\n        try {\n            sender.send(nextMessage).execute();\n        } catch (IOException | RuntimeException t) {\n            // In failure case, we increment messages and spans dropped.\n            int count = nextMessage.size();\n            Call.propagateIfFatal(t);\n            metrics.incrementMessagesDropped(t);\n            metrics.incrementItemsDropped(count);\n\n            Level logLevel = FINE;\n\n            if (shouldWarnException) {\n                logger.log(WARNING, \"Spans were dropped due to exceptions. \"\n                    + \"All subsequent errors will be logged at FINE level.\");\n                logLevel = WARNING;\n                shouldWarnException = false;\n            }\n\n            if (logger.isLoggable(logLevel)) {\n                logger.log(logLevel,\n                    format(\"Dropped %s spans due to %s(%s)\", count, t.getClass().getSimpleName(),\n                        t.getMessage() == null ? \"\" : t.getMessage()), t);\n            }\n\n            // Raise in case the sender was closed out-of-band.\n            if (t instanceof IllegalStateException) {\n                throw (IllegalStateException) t;\n            }\n        }\n    }\n\n    @Override\n    public boolean check() {\n        return sender.isAvailable();\n    }\n\n    @Override\n    public void close() {\n        if (!closed.compareAndSet(false, true)) {\n            return; // already closed\n        }\n\n        try {\n            // wait for in-flight data to send\n            if (!close.await(closeTimeoutNanos, TimeUnit.NANOSECONDS)) {\n                logger.warning(\"Timed out waiting for in-flight spans to send\");\n            }\n        } catch (InterruptedException e) {\n            logger.warning(\"Interrupted waiting for in-flight spans to send\");\n            Thread.currentThread().interrupt();\n        }\n        int count = pending.clear();\n        if (count > 0) {\n            metrics.incrementItemsDropped(count);\n            logger.log(WARNING, \"Dropped {0} spans due to AsyncReporter.close()\", count);\n        }\n    }\n\n    @Override\n    public String toString() {\n        return NAME_PREFIX + \"{\" + sender + \"}\";\n    }\n\n    @Override\n    public void setThreadFactory(ThreadFactory threadFactory) {\n        this.threadFactory = threadFactory;\n    }\n\n    @Override\n    public void startFlushThread() {\n        if (this.messageTimeoutNanos > 0) {\n            List<Thread> threads = new CopyOnWriteArrayList<>();\n            for (int i = 0; i < asyncProperties.getReportThread(); i++) { // Multiple consumer consumption\n                final AgentBufferNextMessage<S> consumer =\n                    AgentBufferNextMessage.create(encoder, this.messageMaxBytes, this.messageTimeoutNanos);\n                Thread flushThread = this.threadFactory.newThread(new Flusher<>(this, consumer));\n                flushThread.setName(NAME_PREFIX + \"{\" + this.sender + \"}\");\n                flushThread.setDaemon(true);\n                flushThread.start();\n            }\n            this.setFlushThreads(threads);\n        }\n    }\n\n    // 关掉flushThread\n    public void closeFlushThread() {\n        for (Thread thread : this.flushThreads) {\n            thread.interrupt();\n        }\n    }\n\n    @Override\n    public void onChange(List<ChangeItem> list) {\n        // skip\n    }\n\n    private Map<String, String> filterChanges(List<ChangeItem> list) {\n        Map<String, String> cfg = new HashMap<>();\n        list.forEach(one -> cfg.put(one.getFullName(), one.getNewValue()));\n        return cfg;\n    }\n\n    @SuppressWarnings(\"unused\")\n    public static final class Builder {\n        final SenderWithEncoder sender;\n        ThreadFactory threadFactory = Executors.defaultThreadFactory();\n        AsyncReporterMetrics metrics = AsyncReporterMetrics.NOOP_METRICS;\n        int messageMaxBytes;\n        long messageTimeoutNanos;\n        long closeTimeoutNanos = TimeUnit.SECONDS.toNanos(1);\n        int queuedMaxItems;\n        int queuedMaxBytes;\n        AsyncProps props;\n        GlobalExtrasSupplier globalExtrasSupplier;\n\n        static int onePercentOfMemory() {\n            long result = (long) (Runtime.getRuntime().totalMemory() * 0.01);\n            // don't overflow in the rare case 1% of memory is larger than 2 GiB!\n            return (int) Math.max(Math.min(Integer.MAX_VALUE, result), Integer.MIN_VALUE);\n        }\n\n        Builder(SenderWithEncoder sender, AsyncProps asyncProperties) {\n            if (sender == null) {\n                throw new NullPointerException(\"sender == null\");\n            }\n            this.props = asyncProperties;\n            this.sender = sender;\n            this.messageMaxBytes = asyncProperties.getMessageMaxBytes();\n            this.queuedMaxItems = asyncProperties.getQueuedMaxItems();\n            this.messageTimeoutNanos = TimeUnit.MILLISECONDS.toNanos(asyncProperties.getMessageTimeout());\n            this.queuedMaxBytes = asyncProperties.getQueuedMaxSize();\n        }\n\n        /**\n         * Launches the flush thread when {@link #messageTimeoutNanos} is greater than zero.\n         */\n        public Builder threadFactory(ThreadFactory threadFactory) {\n            if (threadFactory == null) throw new NullPointerException(\"threadFactory == null\");\n            this.threadFactory = threadFactory;\n            return this;\n        }\n\n        /**\n         * Aggregates and reports reporter metrics to a monitoring system. Defaults to no-op.\n         */\n        public Builder metrics(AsyncReporterMetrics metrics) {\n            if (metrics == null) throw new NullPointerException(\"metrics == null\");\n            this.metrics = metrics;\n            return this;\n        }\n\n        /**\n         * Maximum bytes per message package including overhead.\n         */\n        public Builder messageMaxBytes(int messageMaxBytes) {\n            if (messageMaxBytes < 0) {\n                throw new IllegalArgumentException(\"messageMaxBytes < 0: \" + messageMaxBytes);\n            }\n            this.messageMaxBytes = Math.min(messageMaxBytes, props.getMessageMaxBytes());\n            return this;\n        }\n\n        /**\n         * Default 1 second. 0 implies spans are {@link #flush() flushed} externally.\n         *\n         * <p>Instead of sending one message at a time, spans are bundled into messages.\n         * This timeout ensures that spans are not stuck in an incomplete\n         * message.\n         *\n         * <p>Note: this timeout starts when the first unsent span is reported.\n         */\n        public Builder messageTimeout(long timeout, TimeUnit unit) {\n            if (timeout < 0) throw new IllegalArgumentException(\"messageTimeout < 0: \" + timeout);\n            if (unit == null) throw new NullPointerException(\"unit == null\");\n            this.messageTimeoutNanos = unit.toNanos(timeout);\n            return this;\n        }\n\n        /** How long to block for in-flight spans to send out-of-process on close. Default 1 second */\n        public Builder closeTimeout(long timeout, TimeUnit unit) {\n            if (timeout < 0) throw new IllegalArgumentException(\"closeTimeout < 0: \" + timeout);\n            if (unit == null) throw new NullPointerException(\"unit == null\");\n            this.closeTimeoutNanos = unit.toNanos(timeout);\n            return this;\n        }\n\n        /** Maximum backlog of items, such as Spans, reported vs sent. Default 10000 */\n        public Builder queuedMaxItems(int queuedMaxItems) {\n            this.queuedMaxItems = queuedMaxItems;\n            return this;\n        }\n\n        /** Maximum backlog of items, such as Spans,  bytes reported vs sent. Default 1% of heap */\n        public Builder queuedMaxBytes(int queuedMaxBytes) {\n            this.queuedMaxBytes = queuedMaxBytes;\n            return this;\n        }\n\n        /**\n         * Builds an async reporter that encodes arbitrary spans as they are reported.\n         */\n        private <S> DefaultAsyncReporter<S> build() {\n            Encoder<S> encoder = this.sender.getEncoder();\n            if (encoder == null) {\n                throw new NullPointerException(\"encoder == null\");\n            }\n\n            final DefaultAsyncReporter<S> result = new DefaultAsyncReporter<>(this, this.props);\n\n            if (this.messageTimeoutNanos > 0) {\n                // Start a thread that flushes the queue in a loop.\n                List<Thread> flushThreads = new CopyOnWriteArrayList<>();\n                for (int i = 0; i < this.props.getReportThread(); i++) {\n                    // Multiple consumer consumption\n                    final AgentBufferNextMessage<S> consumer =\n                        AgentBufferNextMessage.create(encoder, this.messageMaxBytes, this.messageTimeoutNanos);\n\n                    Thread flushThread = this.threadFactory\n                        .newThread(new Flusher<>(result, consumer));\n                    flushThread.setName(NAME_PREFIX + \"{\" + this.sender + \"}\");\n                    flushThread.setDaemon(true);\n                    flushThread.start();\n                    flushThreads.add(flushThread);\n                }\n                result.setFlushThreads(flushThreads);\n                result.setThreadFactory(this.threadFactory);\n                result.setSender(this.sender);\n            }\n\n            return result;\n        }\n    }\n\n    public static final class Flusher<S> implements Runnable {\n        static final Logger logger = Logger.getLogger(Flusher.class.getName());\n\n        final DefaultAsyncReporter<S> reporter;\n        final AgentBufferNextMessage<S> consumer;\n\n        Flusher(DefaultAsyncReporter<S> reporter, AgentBufferNextMessage<S> consumer) {\n            this.reporter = reporter;\n            this.consumer = consumer;\n        }\n\n        @Override\n        public void run() {\n            try {\n                while (!reporter.closed.get() && reporter.check()) {\n                    // flush will be block if there is no data ready, don't check trace is enabled,\n                    // otherwise the cpu will spin.\n                    reporter.flush(consumer, reporter.pending);\n                }\n            } finally {\n                int count = consumer.count();\n                if (count > 0) {\n                    reporter.metrics.incrementItemsDropped(count);\n                    logger.log(WARNING,\"Dropped {0} spans due to AsyncReporter.close()\", count);\n                }\n                reporter.close.countDown();\n            }\n        }\n\n        @Override\n        public String toString() {\n            return NAME_PREFIX + \"{\" + reporter.sender + \"}\";\n        }\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/async/log/AccessLogReporter.java",
    "content": "/*\n * Copyright (c) 2022, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.report.async.log;\n\nimport com.megaease.easeagent.config.ConfigUtils;\nimport com.megaease.easeagent.config.Configs;\nimport com.megaease.easeagent.config.report.ReportConfigConst;\nimport com.megaease.easeagent.plugin.api.config.ChangeItem;\nimport com.megaease.easeagent.plugin.api.config.Config;\nimport com.megaease.easeagent.plugin.api.config.ConfigChangeListener;\nimport com.megaease.easeagent.plugin.api.logging.AccessLogInfo;\nimport com.megaease.easeagent.plugin.utils.common.StringUtils;\nimport com.megaease.easeagent.report.async.AsyncProps;\nimport com.megaease.easeagent.report.async.AsyncReporter;\nimport com.megaease.easeagent.report.async.DefaultAsyncReporter;\nimport com.megaease.easeagent.report.plugin.ReporterRegistry;\nimport com.megaease.easeagent.report.sender.SenderWithEncoder;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.TimeUnit;\n\nimport static com.megaease.easeagent.config.report.ReportConfigConst.*;\n\n@SuppressWarnings(\"unused\")\npublic class AccessLogReporter implements ConfigChangeListener {\n    Config config;\n    AsyncReporter<AccessLogInfo> asyncReporter;\n\n    public AccessLogReporter(Config configs) {\n        Map<String, String> cfg = ConfigUtils.extractByPrefix(configs.getConfigs(), LOG_ACCESS);\n        cfg.putAll(ConfigUtils.extractByPrefix(configs.getConfigs(), OUTPUT_SERVER_V2));\n        cfg.putAll(ConfigUtils.extractByPrefix(configs.getConfigs(), LOG_ASYNC));\n\n        this.config = new Configs(cfg);\n        configs.addChangeListener(this);\n\n        AsyncProps asyncProperties = new LogAsyncProps(this.config, LOG_ACCESS);\n        SenderWithEncoder sender = ReporterRegistry.getSender(ReportConfigConst.LOG_ACCESS_SENDER, this.config);\n        this.asyncReporter = DefaultAsyncReporter.builderAsyncReporter(sender, asyncProperties);\n        this.asyncReporter.startFlushThread();\n    }\n\n    public void report(AccessLogInfo log) {\n        this.asyncReporter.report(log);\n    }\n\n    @Override\n    public void onChange(List<ChangeItem> list) {\n        Map<String, String> changes = filterChanges(list);\n        if (changes.isEmpty()) {\n            return;\n        }\n        this.config.updateConfigs(changes);\n        this.refresh(this.config.getConfigs());\n    }\n\n    private Map<String, String> filterChanges(List<ChangeItem> list) {\n        Map<String, String> cfg = new HashMap<>();\n        list.stream()\n            .filter(item -> item.getFullName().startsWith(LOG_ACCESS)\n                || item.getFullName().startsWith(LOG_ASYNC)\n                || item.getFullName().startsWith(OUTPUT_SERVER_V2))\n            .forEach(one -> cfg.put(one.getFullName(), one.getNewValue()));\n        return cfg;\n    }\n\n    public synchronized void refresh(Map<String, String> cfg) {\n        String name = cfg.get(LOG_ACCESS_SENDER_NAME);\n        SenderWithEncoder sender = asyncReporter.getSender();\n        if (sender != null) {\n            if (StringUtils.isNotEmpty(name) && !sender.name().equals(name)) {\n                try {\n                    sender.close();\n                } catch (Exception ignored) {\n                    // ignored\n                }\n                sender = ReporterRegistry.getSender(LOG_ACCESS_SENDER, this.config);\n                asyncReporter.setSender(sender);\n            }\n        } else {\n            sender = ReporterRegistry.getSender(LOG_ACCESS_SENDER, this.config);\n            asyncReporter.setSender(sender);\n        }\n\n        AsyncProps asyncProperties = new LogAsyncProps(this.config, LOG_ACCESS);\n        asyncReporter.closeFlushThread();\n        asyncReporter.setPending(asyncProperties.getQueuedMaxItems(), asyncProperties.getQueuedMaxSize());\n        asyncReporter.setMessageTimeoutNanos(messageTimeout(asyncProperties.getMessageTimeout()));\n        asyncReporter.startFlushThread(); // start thread\n    }\n\n    protected long messageTimeout(long timeout) {\n        if (timeout < 0) {\n            timeout = 1000L;\n        }\n        return TimeUnit.MILLISECONDS.toNanos(timeout);\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/async/log/ApplicationLogReporter.java",
    "content": "/*\n * Copyright (c) 2022, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.report.async.log;\n\nimport com.megaease.easeagent.config.ConfigUtils;\nimport com.megaease.easeagent.config.Configs;\nimport com.megaease.easeagent.config.report.ReportConfigConst;\nimport com.megaease.easeagent.plugin.api.config.ChangeItem;\nimport com.megaease.easeagent.plugin.api.config.Config;\nimport com.megaease.easeagent.plugin.api.config.ConfigChangeListener;\nimport com.megaease.easeagent.plugin.utils.common.StringUtils;\nimport com.megaease.easeagent.report.async.AsyncProps;\nimport com.megaease.easeagent.report.async.AsyncReporter;\nimport com.megaease.easeagent.report.async.DefaultAsyncReporter;\nimport com.megaease.easeagent.report.plugin.ReporterRegistry;\nimport com.megaease.easeagent.report.sender.SenderWithEncoder;\n\nimport io.opentelemetry.sdk.logs.data.LogData;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.TimeUnit;\n\nimport static com.megaease.easeagent.config.report.ReportConfigConst.*;\n\n@SuppressWarnings(\"unused\")\npublic class ApplicationLogReporter implements ConfigChangeListener {\n    Config config;\n    AsyncReporter<LogData> asyncReporter;\n\n    public ApplicationLogReporter(Config configs) {\n        Map<String, String> cfg = ConfigUtils.extractByPrefix(configs.getConfigs(), LOGS);\n        cfg.putAll(ConfigUtils.extractByPrefix(configs.getConfigs(), OUTPUT_SERVER_V2));\n        cfg.putAll(ConfigUtils.extractByPrefix(configs.getConfigs(), LOG_ASYNC));\n        this.config = new Configs(cfg);\n        configs.addChangeListener(this);\n\n        SenderWithEncoder sender = ReporterRegistry.getSender(ReportConfigConst.LOG_SENDER, configs);\n        AsyncProps asyncProperties = new LogAsyncProps(this.config, null);\n        this.asyncReporter = DefaultAsyncReporter.builderAsyncReporter(sender, asyncProperties);\n        this.asyncReporter.startFlushThread();\n    }\n\n    public void report(LogData log) {\n        this.asyncReporter.report(log);\n    }\n\n    @Override\n    public void onChange(List<ChangeItem> list) {\n        Map<String, String> changes = filterChanges(list);\n        if (changes.isEmpty()) {\n            return;\n        }\n        this.config.updateConfigs(changes);\n        this.refresh(this.config.getConfigs());\n    }\n\n    private Map<String, String> filterChanges(List<ChangeItem> list) {\n        Map<String, String> cfg = new HashMap<>();\n        list.stream()\n            .filter(item -> item.getFullName().startsWith(LOGS)\n                || item.getFullName().startsWith(OUTPUT_SERVER_V2))\n            .forEach(one -> cfg.put(one.getFullName(), one.getNewValue()));\n        return cfg;\n    }\n\n    public synchronized void refresh(Map<String, String> cfg) {\n        String name = cfg.get(LOG_ACCESS_SENDER_NAME);\n        SenderWithEncoder sender = asyncReporter.getSender();\n        if (sender != null) {\n            if (StringUtils.isNotEmpty(name) && !sender.name().equals(name)) {\n                try {\n                    sender.close();\n                } catch (Exception ignored) {\n                    // ignored\n                }\n                sender = ReporterRegistry.getSender(LOG_SENDER, this.config);\n                asyncReporter.setSender(sender);\n            }\n        } else {\n            sender = ReporterRegistry.getSender(LOG_SENDER, this.config);\n            asyncReporter.setSender(sender);\n        }\n\n        AsyncProps asyncProperties = new LogAsyncProps(this.config, null);\n        asyncReporter.closeFlushThread();\n        asyncReporter.setPending(asyncProperties.getQueuedMaxItems(), asyncProperties.getQueuedMaxSize());\n        asyncReporter.setMessageTimeoutNanos(messageTimeout(asyncProperties.getMessageTimeout()));\n        asyncReporter.startFlushThread(); // start thread\n    }\n\n    protected long messageTimeout(long timeout) {\n        if (timeout < 0) {\n            timeout = 1000L;\n        }\n        return TimeUnit.MILLISECONDS.toNanos(timeout);\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/async/log/LogAsyncProps.java",
    "content": "/*\n * Copyright (c) 2022, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.report.async.log;\n\nimport com.megaease.easeagent.plugin.api.config.Config;\nimport com.megaease.easeagent.plugin.utils.common.StringUtils;\nimport com.megaease.easeagent.report.async.AsyncProps;\n\nimport static com.megaease.easeagent.config.ConfigUtils.bindProp;\nimport static com.megaease.easeagent.config.report.ReportConfigConst.*;\n\npublic class LogAsyncProps implements AsyncProps {\n    private volatile int reportThread;\n    private volatile int queuedMaxLogs;\n    private volatile int queuedMaxSize;\n    private volatile int messageTimeout;\n    private volatile int messageMaxBytes;\n\n    public LogAsyncProps(Config config, String prefix) {\n        int onePercentageMemory = AsyncProps.onePercentOfMemory();\n        String keyPrefix = StringUtils.isEmpty(prefix) ? LOG_ASYNC : prefix;\n\n        bindProp(join(keyPrefix, join(ASYNC_KEY, ASYNC_THREAD_KEY)),\n            config, Config::getInt, v -> this.reportThread = v, 1);\n\n        bindProp(join(keyPrefix, join(ASYNC_KEY, ASYNC_QUEUE_MAX_SIZE_KEY)),\n            config, Config::getInt, v -> this.queuedMaxSize = v, onePercentageMemory);\n\n        bindProp(join(keyPrefix, join(ASYNC_KEY, ASYNC_QUEUE_MAX_LOGS_KEY)),\n            config, Config::getInt, v -> this.queuedMaxLogs = v, 500);\n\n        bindProp(join(keyPrefix, join(ASYNC_KEY, ASYNC_MSG_MAX_BYTES_KEY)),\n            config, Config::getInt, v -> this.messageMaxBytes = v, 999900);\n\n        bindProp(join(keyPrefix, join(ASYNC_KEY, ASYNC_MSG_TIMEOUT_KEY)),\n            config, Config::getInt, v -> this.messageTimeout = v, 1000);\n    }\n\n    @Override\n    public int getReportThread() {\n        return this.reportThread;\n    }\n\n    @Override\n    public int getQueuedMaxItems() {\n        return this.queuedMaxLogs;\n    }\n\n    @Override\n    public long getMessageTimeout() {\n        return this.messageTimeout;\n    }\n\n    @Override\n    public int getQueuedMaxSize() {\n        return this.queuedMaxSize;\n    }\n\n    @Override\n    public int getMessageMaxBytes() {\n        return this.messageMaxBytes;\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/async/trace/SDKAsyncReporter.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.report.async.trace;\n\nimport com.megaease.easeagent.plugin.report.EncodedData;\nimport com.megaease.easeagent.plugin.report.Encoder;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport com.megaease.easeagent.report.async.AsyncProps;\nimport com.megaease.easeagent.report.async.zipkin.AgentBufferNextMessage;\nimport com.megaease.easeagent.report.async.zipkin.AgentByteBoundedQueue;\nimport com.megaease.easeagent.report.encoder.PackedMessage;\nimport com.megaease.easeagent.report.encoder.PackedMessage.DefaultPackedMessage;\nimport com.megaease.easeagent.report.encoder.span.GlobalExtrasSupplier;\nimport com.megaease.easeagent.report.sender.SenderWithEncoder;\nimport com.megaease.easeagent.report.util.SpanUtils;\nimport lombok.SneakyThrows;\nimport zipkin2.Call;\nimport zipkin2.CheckResult;\nimport zipkin2.reporter.AsyncReporter;\nimport zipkin2.reporter.ReporterMetrics;\n\nimport java.io.IOException;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.concurrent.*;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\n\nimport static java.lang.String.format;\nimport static java.util.logging.Level.FINE;\nimport static java.util.logging.Level.WARNING;\n\npublic class SDKAsyncReporter<S> extends AsyncReporter<S> {\n    static final Logger logger = Logger.getLogger(SDKAsyncReporter.class.getName());\n\n    private static final String NAME_PREFIX = \"AsyncReporter\";\n\n    final AtomicBoolean closed = new AtomicBoolean(false);\n    SenderWithEncoder sender;\n    Encoder<S> encoder;\n\n    AgentByteBoundedQueue<S> pending;\n    final int messageMaxBytes;\n    long messageTimeoutNanos;\n    final long closeTimeoutNanos;\n    final CountDownLatch close;\n    final ReporterMetrics metrics;\n    AsyncProps traceProperties;\n\n    ThreadFactory threadFactory;\n\n    /*\n     * Tracks if we should log the first instance of an exception in flush().\n     */\n    private boolean shouldWarnException = true;\n\n    List<Thread> flushThreads;\n\n    SDKAsyncReporter(Builder builder, Encoder<S> encoder, AsyncProps traceProperties) {\n        this.pending = new AgentByteBoundedQueue<>(builder.queuedMaxItems, builder.queuedMaxBytes);\n        this.sender = builder.sender;\n        this.messageMaxBytes = builder.messageMaxBytes;\n        this.messageTimeoutNanos = builder.messageTimeoutNanos;\n        this.closeTimeoutNanos = builder.closeTimeoutNanos;\n        this.close = new CountDownLatch(builder.messageTimeoutNanos > 0 ? 1 : 0);\n        this.metrics = builder.metrics;\n        this.encoder = encoder;\n        this.traceProperties = traceProperties;\n    }\n\n    public static SDKAsyncReporter<ReportSpan> builderSDKAsyncReporter(SenderWithEncoder sender,\n                                                                       AsyncProps traceProperties,\n                                                                       GlobalExtrasSupplier extrasSupplier) {\n        final SDKAsyncReporter<ReportSpan> reporter = new Builder(sender, traceProperties)\n            .globalExtractor(extrasSupplier)\n            .build();\n\n        reporter.setTraceProperties(traceProperties);\n        return reporter;\n    }\n\n    public void setFlushThreads(List<Thread> flushThreads) {\n        this.flushThreads = flushThreads;\n    }\n\n    //modify sender\n    public SenderWithEncoder getSender() {\n        return this.sender;\n    }\n\n    //modify sender\n    public void setSender(SenderWithEncoder sender) {\n        this.sender = sender;\n        this.encoder = sender.getEncoder();\n    }\n\n    public void setTraceProperties(AsyncProps traceProperties) {\n        this.traceProperties = traceProperties;\n    }\n\n    public void setPending(int queuedMaxSpans, int queuedMaxBytes) {\n        AgentByteBoundedQueue<S> copyPending = this.pending;\n        this.pending = new AgentByteBoundedQueue<>(queuedMaxSpans, queuedMaxBytes);\n        consumerData(copyPending);\n    }\n\n    private void consumerData(final AgentByteBoundedQueue<S> copyPending) {\n        Thread flushThread = this.threadFactory.newThread((() -> {\n            final AgentBufferNextMessage<S> bufferNextMessage = AgentBufferNextMessage\n                .create(encoder, messageMaxBytes, 0);\n            while (copyPending.getCount() > 0) {\n                flush(bufferNextMessage, copyPending);\n            }\n        }));\n        flushThread.setName(\"TempAsyncReporter{\" + this.sender + \"}\");\n        flushThread.setDaemon(true);\n        flushThread.start();\n    }\n\n    public void setMessageTimeoutNanos(long messageTimeoutNanos) {\n        this.messageTimeoutNanos = messageTimeoutNanos;\n    }\n\n    /**\n     * Returns true if the was encoded and accepted onto the queue.\n     */\n    @SneakyThrows\n    @Override\n    public void report(S next) {\n        if (!this.sender.isAvailable()) {\n            return;\n        }\n\n        if (!SpanUtils.isValidSpan(next)) {\n            return;\n        }\n\n        metrics.incrementSpans(1);\n        int nextSizeInBytes = encoder.sizeInBytes(next);\n        int messageSizeOfNextSpan = encoder.packageSizeInBytes(Collections.singletonList(nextSizeInBytes));\n        metrics.incrementSpanBytes(nextSizeInBytes);\n        if (closed.get() ||\n            // don't enqueue something larger than we can drain\n            messageSizeOfNextSpan > messageMaxBytes ||\n            !pending.offer(next, nextSizeInBytes)) {\n            metrics.incrementSpansDropped(1);\n        }\n    }\n\n    @Override\n    public final void flush() {\n        if (!this.sender.isAvailable()) {\n            return;\n        }\n\n        flush(AgentBufferNextMessage.create(encoder, messageMaxBytes, 0), pending);\n    }\n\n\n    void flush(AgentBufferNextMessage<S> bundler, AgentByteBoundedQueue<S> pending) {\n        if (closed.get()) {\n            throw new IllegalStateException(\"closed\");\n        }\n\n        pending.drainTo(bundler, bundler.remainingNanos());\n\n        // record after flushing reduces the amount of gauge events vs on doing this on report\n        metrics.updateQueuedSpans(pending.getCount());\n        metrics.updateQueuedBytes(pending.getSizeInBytes());\n\n        // loop around if we are running, and the bundle isn't full\n        // if we are closed, try to send what's pending\n        if (!bundler.isReady() && !closed.get()) return;\n\n        // Signal that we are about to send a message of a known size in bytes\n        metrics.incrementMessages();\n        metrics.incrementMessageBytes(bundler.sizeInBytes());\n\n        // Create the next message. Since we are outside the lock shared with writers, we can encode\n        PackedMessage message = new DefaultPackedMessage(bundler.count(), encoder);\n        bundler.drain((next, nextSizeInBytes) -> {\n            if (message.calculateAppendSize(nextSizeInBytes) <= messageMaxBytes) {\n                message.addMessage(encoder.encode(next));\n                return true;\n            } else {\n                return false;\n            }\n        });\n\n        List<EncodedData> nextMessage = message.getMessages();\n        try {\n            sender.send(nextMessage).execute();\n        } catch (IOException | RuntimeException t) {\n            // In failure case, we increment messages and spans dropped.\n            int count = nextMessage.size();\n            Call.propagateIfFatal(t);\n            metrics.incrementMessagesDropped(t);\n            metrics.incrementSpansDropped(count);\n\n            Level logLevel = FINE;\n\n            if (shouldWarnException) {\n                logger.log(WARNING, \"Spans were dropped due to exceptions. \"\n                    + \"All subsequent errors will be logged at FINE level.\");\n                logLevel = WARNING;\n                shouldWarnException = false;\n            }\n\n            if (logger.isLoggable(logLevel)) {\n                logger.log(logLevel,\n                    format(\"Dropped %s spans due to %s(%s)\", count, t.getClass().getSimpleName(),\n                        t.getMessage() == null ? \"\" : t.getMessage()), t);\n            }\n\n            // Raise in case the sender was closed out-of-band.\n            if (t instanceof IllegalStateException) throw (IllegalStateException) t;\n        }\n    }\n\n    @Override\n    public CheckResult check() {\n        if (sender.isAvailable()) {\n            return CheckResult.OK;\n        } else {\n            return CheckResult.failed(new IOException(\"Sender is unavailable\"));\n        }\n    }\n\n    @Override\n    public void close() {\n        if (!closed.compareAndSet(false, true)) return; // already closed\n        try {\n            // wait for in-flight spans to send\n            if (!close.await(closeTimeoutNanos, TimeUnit.NANOSECONDS)) {\n                logger.warning(\"Timed out waiting for in-flight spans to send\");\n            }\n        } catch (InterruptedException e) {\n            logger.warning(\"Interrupted waiting for in-flight spans to send\");\n            Thread.currentThread().interrupt();\n        }\n        int count = pending.clear();\n        if (count > 0) {\n            metrics.incrementSpansDropped(count);\n            logger.log(WARNING, \"Dropped {0} spans due to AsyncReporter.close()\", count);\n        }\n    }\n\n    @Override\n    public String toString() {\n        return NAME_PREFIX + \"{\" + sender + \"}\";\n    }\n\n    public void setThreadFactory(ThreadFactory threadFactory) {\n        this.threadFactory = threadFactory;\n    }\n\n    public void startFlushThread() {\n        if (this.messageTimeoutNanos > 0) {\n            List<Thread> threads = new CopyOnWriteArrayList<>();\n            for (int i = 0; i < traceProperties.getReportThread(); i++) { // Multiple consumer consumption\n                final AgentBufferNextMessage<S> consumer =\n                    AgentBufferNextMessage.create(encoder, this.messageMaxBytes, this.messageTimeoutNanos);\n                Thread flushThread = this.threadFactory.newThread(new Flusher<>(this, consumer, this.sender));\n                flushThread.setName(NAME_PREFIX + \"{\" + this.sender + \"}\");\n                flushThread.setDaemon(true);\n                flushThread.start();\n            }\n            this.setFlushThreads(threads);\n        }\n    }\n\n    // 关掉flushThread\n    public void closeFlushThread() {\n        for (Thread thread : this.flushThreads) {\n            thread.interrupt();\n        }\n    }\n\n    @SuppressWarnings(\"unused\")\n    public static final class Builder {\n        final SenderWithEncoder sender;\n        ThreadFactory threadFactory = Executors.defaultThreadFactory();\n        ReporterMetrics metrics = ReporterMetrics.NOOP_METRICS;\n        int messageMaxBytes;\n        long messageTimeoutNanos;\n        long closeTimeoutNanos = TimeUnit.SECONDS.toNanos(1);\n        int queuedMaxItems;\n        int queuedMaxBytes;\n        AsyncProps props;\n        GlobalExtrasSupplier globalExtrasSupplier;\n\n\n        Builder(SenderWithEncoder sender, AsyncProps traceProperties) {\n            if (sender == null) {\n                throw new NullPointerException(\"sender == null\");\n            }\n            this.props = traceProperties;\n            this.sender = sender;\n            this.messageMaxBytes = traceProperties.getMessageMaxBytes();\n            this.queuedMaxItems = traceProperties.getQueuedMaxItems();\n            this.messageTimeoutNanos = TimeUnit.MILLISECONDS.toNanos(traceProperties.getMessageTimeout());\n            this.queuedMaxBytes = traceProperties.getQueuedMaxSize();\n        }\n\n        /**\n         * Launches the flush thread when {@link #messageTimeoutNanos} is greater than zero.\n         */\n        public Builder threadFactory(ThreadFactory threadFactory) {\n            if (threadFactory == null) throw new NullPointerException(\"threadFactory == null\");\n            this.threadFactory = threadFactory;\n            return this;\n        }\n\n        /**\n         * Global properties extractor\n         */\n        public Builder globalExtractor(GlobalExtrasSupplier supplier) {\n            this.globalExtrasSupplier = supplier;\n            return this;\n        }\n\n        /**\n         * Aggregates and reports reporter metrics to a monitoring system. Defaults to no-op.\n         */\n        public Builder metrics(ReporterMetrics metrics) {\n            if (metrics == null) throw new NullPointerException(\"metrics == null\");\n            this.metrics = metrics;\n            return this;\n        }\n\n        /**\n         * Maximum bytes per message package including overhead.\n         */\n        public Builder messageMaxBytes(int messageMaxBytes) {\n            if (messageMaxBytes < 0) {\n                throw new IllegalArgumentException(\"messageMaxBytes < 0: \" + messageMaxBytes);\n            }\n            this.messageMaxBytes = Math.min(messageMaxBytes, props.getMessageMaxBytes());\n            return this;\n        }\n\n        /**\n         * Default 1 second. 0 implies spans are {@link #flush() flushed} externally.\n         *\n         * <p>Instead of sending one message at a time, spans are bundled into messages.\n         * This timeout ensures that spans are not stuck in an incomplete\n         * message.\n         *\n         * <p>Note: this timeout starts when the first unsent span is reported.\n         */\n        public Builder messageTimeout(long timeout, TimeUnit unit) {\n            if (timeout < 0) throw new IllegalArgumentException(\"messageTimeout < 0: \" + timeout);\n            if (unit == null) throw new NullPointerException(\"unit == null\");\n            this.messageTimeoutNanos = unit.toNanos(timeout);\n            return this;\n        }\n\n        /** How long to block for in-flight spans to send out-of-process on close. Default 1 second */\n        public Builder closeTimeout(long timeout, TimeUnit unit) {\n            if (timeout < 0) throw new IllegalArgumentException(\"closeTimeout < 0: \" + timeout);\n            if (unit == null) throw new NullPointerException(\"unit == null\");\n            this.closeTimeoutNanos = unit.toNanos(timeout);\n            return this;\n        }\n\n        /** Maximum backlog of items, such as Spans, reported vs sent. Default 10000 */\n        public Builder queuedMaxItems(int queuedMaxItems) {\n            this.queuedMaxItems = queuedMaxItems;\n            return this;\n        }\n\n        /** Maximum backlog of items, such as Spans,  bytes reported vs sent. Default 1% of heap */\n        public Builder queuedMaxBytes(int queuedMaxBytes) {\n            this.queuedMaxBytes = queuedMaxBytes;\n            return this;\n        }\n\n        /**\n         * Builds an async reporter that encodes arbitrary spans as they are reported.\n         */\n        private <S> SDKAsyncReporter<S> build() {\n            Encoder<S> encoder = this.sender.getEncoder();\n            if (encoder == null) {\n                throw new NullPointerException(\"encoder == null\");\n            }\n\n            final SDKAsyncReporter<S> result = new SDKAsyncReporter<>(this, encoder, this.props);\n\n            if (this.messageTimeoutNanos > 0) {\n                // Start a thread that flushes the queue in a loop.\n                List<Thread> flushThreads = new CopyOnWriteArrayList<>();\n                for (int i = 0; i < this.props.getReportThread(); i++) {\n                    // Multiple consumer consumption\n                    final AgentBufferNextMessage<S> consumer =\n                        AgentBufferNextMessage.create(encoder, this.messageMaxBytes, this.messageTimeoutNanos);\n\n                    Thread flushThread = this.threadFactory\n                        .newThread(new Flusher<>(result, consumer, this.sender));\n                    flushThread.setName(NAME_PREFIX + \"{\" + this.sender + \"}\");\n                    flushThread.setDaemon(true);\n                    flushThread.start();\n                    flushThreads.add(flushThread);\n                }\n                result.setFlushThreads(flushThreads);\n                result.setThreadFactory(this.threadFactory);\n                result.setSender(this.sender);\n            }\n\n            return result;\n        }\n    }\n\n    public static final class Flusher<S> implements Runnable {\n        static final Logger logger = Logger.getLogger(Flusher.class.getName());\n\n        final SDKAsyncReporter<S> result;\n        final AgentBufferNextMessage<S> consumer;\n        final SenderWithEncoder sender;\n\n        Flusher(SDKAsyncReporter<S> result, AgentBufferNextMessage<S> consumer, SenderWithEncoder sender) {\n            this.result = result;\n            this.consumer = consumer;\n            this.sender = sender;\n        }\n\n        @Override\n        public void run() {\n            try {\n                while (!result.closed.get() && sender.isAvailable()) {\n                    // flush will be block if there is no data ready, don't check trace is enabled,\n                    // otherwise the cpu will spin.\n                    result.flush(consumer, result.pending);\n                }\n            } finally {\n                int count = consumer.count();\n                if (count > 0) {\n                    result.metrics.incrementSpansDropped(count);\n                    logger.log(WARNING,\"Dropped {0} spans due to AsyncReporter.close()\", count);\n                }\n                result.close.countDown();\n            }\n        }\n\n        @Override\n        public String toString() {\n            return NAME_PREFIX + \"{\" + result.sender + \"}\";\n        }\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/async/trace/TraceAsyncProps.java",
    "content": "/*\n * Copyright (c) 2022, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.report.async.trace;\n\nimport com.megaease.easeagent.plugin.api.config.Config;\nimport com.megaease.easeagent.report.async.AsyncProps;\n\nimport static com.megaease.easeagent.config.ConfigUtils.bindProp;\nimport static com.megaease.easeagent.config.report.ReportConfigConst.*;\n\npublic class TraceAsyncProps implements AsyncProps {\n    private volatile int reportThread;\n    private volatile int queuedMaxSpans;\n    private volatile int queuedMaxSize;\n    private volatile int messageTimeout;\n    private volatile int messageMaxBytes;\n\n    public TraceAsyncProps(Config config) {\n        int onePercentageMemory = AsyncProps.onePercentOfMemory();\n        bindProp(TRACE_ASYNC_REPORT_THREAD_V2, config, Config::getInt, v -> this.reportThread = v, 1);\n        bindProp(TRACE_ASYNC_QUEUED_MAX_SIZE_V2, config, Config::getInt, v -> this.queuedMaxSize = v, onePercentageMemory);\n        bindProp(TRACE_ASYNC_QUEUED_MAX_SPANS_V2, config, Config::getInt, v -> this.queuedMaxSpans = v, 1000);\n        bindProp(TRACE_ASYNC_MESSAGE_MAX_BYTES_V2, config, Config::getInt, v -> this.messageMaxBytes = v, 999900);\n        bindProp(TRACE_ASYNC_MESSAGE_TIMEOUT_V2, config, Config::getInt, v -> this.messageTimeout = v, 1000);\n    }\n\n    @Override\n    public int getReportThread() {\n        return this.reportThread;\n    }\n\n    @Override\n    public int getQueuedMaxItems() {\n        return this.queuedMaxSpans;\n    }\n\n    @Override\n    public long getMessageTimeout() {\n        return this.messageTimeout;\n    }\n\n    @Override\n    public int getQueuedMaxSize() {\n        return this.queuedMaxSize;\n    }\n\n    @Override\n    public int getMessageMaxBytes() {\n        return this.messageMaxBytes;\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/async/zipkin/AgentBufferNextMessage.java",
    "content": "package com.megaease.easeagent.report.async.zipkin;\n\n/*\n * Copyright 2016-2019 The OpenZipkin Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except\n * in compliance with the License. You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under the License\n * is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express\n * or implied. See the License for the specific language governing permissions and limitations under\n * the License.\n */\n\nimport com.megaease.easeagent.plugin.report.Encoder;\n\nimport java.util.ArrayList;\nimport java.util.Iterator;\n\n/** Use of this type happens off the application's main thread. This type is not thread-safe */\n@SuppressWarnings(\"unused\")\npublic class AgentBufferNextMessage<S> implements WithSizeConsumer<S> {\n    public static <S> AgentBufferNextMessage<S> create(Encoder<S> encoder, int maxBytes, long timeoutNanos) {\n        return new AgentBufferNextMessage<>(encoder, maxBytes, timeoutNanos);\n    }\n\n    final Encoder<S> encoder;\n    final int maxBytes;\n    final long timeoutNanos;\n    final ArrayList<S> spans = new ArrayList<>();\n    final ArrayList<Integer> sizes = new ArrayList<>();\n\n    long deadlineNanoTime;\n    int packageSizeInBytes;\n    boolean bufferFull;\n\n    AgentBufferNextMessage(Encoder<S> coder, int maxBytes, long timeoutNanos) {\n        this.maxBytes = maxBytes;\n        this.timeoutNanos = timeoutNanos;\n        this.encoder = coder;\n        resetMessageSizeInBytes();\n    }\n\n    int messageSizeInBytes(int nextSizeInBytes) {\n        return packageSizeInBytes + encoder.appendSizeInBytes(nextSizeInBytes);\n    }\n\n    void resetMessageSizeInBytes() {\n        packageSizeInBytes = encoder.packageSizeInBytes(sizes);\n    }\n\n    /** This is done inside a lock that holds up writers, so has to be fast. No encoding! */\n    public boolean offer(S next, int nextSizeInBytes) {\n        int x = messageSizeInBytes(nextSizeInBytes);\n        int includingNextVsMaxBytes = Integer.compare(x, maxBytes); // Integer.compare, but JRE 6\n\n        if (includingNextVsMaxBytes > 0) {\n            bufferFull = true;\n            return false; // can't fit the next message into this buffer\n        }\n\n        addSpanToBuffer(next, nextSizeInBytes);\n        packageSizeInBytes = x;\n\n        if (includingNextVsMaxBytes == 0) bufferFull = true;\n        return true;\n    }\n\n    void addSpanToBuffer(S next, int nextSizeInBytes) {\n        spans.add(next);\n        sizes.add(nextSizeInBytes);\n    }\n\n    public long remainingNanos() {\n        if (spans.isEmpty()) {\n            deadlineNanoTime = System.nanoTime() + timeoutNanos;\n        }\n        return Math.max(deadlineNanoTime - System.nanoTime(), 0);\n    }\n\n    public boolean isReady() {\n        return bufferFull || remainingNanos() <= 0;\n    }\n\n    // this occurs off the application thread\n    public void drain(WithSizeConsumer<S> consumer) {\n        Iterator<S> spanIterator = spans.iterator();\n        Iterator<Integer> sizeIterator = sizes.iterator();\n        while (spanIterator.hasNext()) {\n            if (consumer.offer(spanIterator.next(), sizeIterator.next())) {\n                bufferFull = false;\n                spanIterator.remove();\n                sizeIterator.remove();\n            } else {\n                break;\n            }\n        }\n\n        resetMessageSizeInBytes();\n        // regardless, reset the clock\n        deadlineNanoTime = 0;\n    }\n\n    public int count() {\n        return spans.size();\n    }\n\n    public int sizeInBytes() {\n        return packageSizeInBytes;\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/async/zipkin/AgentByteBoundedQueue.java",
    "content": "package com.megaease.easeagent.report.async.zipkin;\n\n/*\n * Copyright 2016-2019 The OpenZipkin Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except\n * in compliance with the License. You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under the License\n * is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express\n * or implied. See the License for the specific language governing permissions and limitations under\n * the License.\n */\n\nimport lombok.Data;\n\nimport java.util.concurrent.LinkedTransferQueue;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.concurrent.atomic.LongAdder;\nimport java.util.concurrent.locks.LockSupport;\n\n/**\n * Multi-producer, multi-consumer queue that is bounded by both count and size.\n *\n * <p>\n * This queue is implemented based on LinkedTransferQueue and implements the maximum number and the maximum number of bytes\n * on the basis of LinkedTransferQueue. Taking advantage of the lock-free performance of LinkedTransferQueue in\n * inserting data, the performance problem of locking in the old version can be avoided.\n * </p>\n */\npublic final class AgentByteBoundedQueue<S> implements WithSizeConsumer<S> {\n\n    private final LinkedTransferQueue<DataWrapper<S>> queue = new LinkedTransferQueue<>();\n\n    private final AtomicInteger sizeInBytes = new AtomicInteger(0);\n\n    private final int maxSize;\n\n    private final int maxBytes;\n\n    private final LongAdder loseCounter = new LongAdder();\n\n    public AgentByteBoundedQueue(int maxSize, int maxBytes) {\n        this.maxSize = maxSize;\n        this.maxBytes = maxBytes;\n    }\n\n    @Override\n    public boolean offer(S next, int nextSizeInBytes) {\n        if (maxSize == queue.size()) {\n            loseCounter.increment();\n            return false;\n        }\n        if (sizeInBytes.updateAndGet(pre -> pre + nextSizeInBytes) > maxBytes) {\n            loseCounter.increment();\n            sizeInBytes.updateAndGet(pre -> pre - nextSizeInBytes);\n            return false;\n        }\n        queue.offer(new DataWrapper<>(next, nextSizeInBytes));\n        return true;\n    }\n\n    int doDrain(WithSizeConsumer<S> consumer, DataWrapper<S> firstPoll) {\n        int drainedCount = 0;\n        int drainedSizeInBytes = 0;\n        DataWrapper<S> next = firstPoll;\n        do {\n            int nextSizeInBytes = next.getSizeInBytes();\n            if (consumer.offer(next.getElement(), nextSizeInBytes)) {\n                drainedCount++;\n                drainedSizeInBytes += nextSizeInBytes;\n            } else {\n                queue.offer(next);\n                break;\n            }\n        } while ((next = queue.poll()) != null);\n        final int updateValue = drainedSizeInBytes;\n        sizeInBytes.updateAndGet(pre -> pre - updateValue);\n        return drainedCount;\n    }\n\n    public int drainTo(WithSizeConsumer<S> consumer, long nanosTimeout) {\n        DataWrapper<S> firstPoll;\n        try {\n            firstPoll = queue.poll(nanosTimeout, TimeUnit.NANOSECONDS);\n        } catch (InterruptedException e) {\n            return 0;\n        }\n        if (firstPoll == null) {\n            return 0;\n        }\n        return doDrain(consumer, firstPoll);\n    }\n\n    public int getCount() {\n        return queue.size();\n    }\n\n    public int getSizeInBytes() {\n        return sizeInBytes.get();\n    }\n\n    public int clear() {\n        DataWrapper<S> data;\n        int result = 0;\n        int removeBytes = 0;\n        while ((data = queue.poll()) != null) {\n            removeBytes += data.getSizeInBytes();\n            result++;\n        }\n        sizeInBytes.addAndGet(removeBytes * -1);\n        return result;\n    }\n\n    public long getLoseCount() {\n        return loseCounter.longValue();\n    }\n\n    @Data\n    private static class DataWrapper<S> {\n\n        private final S element;\n\n        private final int sizeInBytes;\n    }\n\n}\n\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/async/zipkin/WithSizeConsumer.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.report.async.zipkin;\n\npublic interface WithSizeConsumer<S> {\n    /** Returns true if the element could be added or false if it could not due to its size. */\n    boolean offer(S next, int nextSizeInBytes);\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/encoder/PackedMessage.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.report.encoder;\n\nimport com.megaease.easeagent.plugin.report.EncodedData;\nimport com.megaease.easeagent.plugin.report.Packer;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic interface PackedMessage {\n    List<EncodedData> getMessages();\n\n    int packSize();\n\n    int calculateAppendSize(int size);\n\n    void addMessage(EncodedData msg);\n\n    class DefaultPackedMessage implements PackedMessage {\n        ArrayList<EncodedData> items;\n        int packSize;\n        Packer packer;\n\n        public DefaultPackedMessage(int count, Packer packer) {\n            this.items = new ArrayList<>(count);\n            this.packSize = 0;\n            this.packer = packer;\n        }\n\n        @Override\n        public List<EncodedData> getMessages() {\n            return items;\n        }\n\n        @Override\n        public int packSize() {\n            return packSize;\n        }\n\n        @Override\n        public int calculateAppendSize(int size) {\n            return this.packSize + this.packer.appendSizeInBytes(size);\n        }\n\n        public void addMessage(EncodedData msg) {\n            this.items.add(msg);\n            if (packSize == 0) {\n                this.packSize = this.packer.messageSizeInBytes(items);\n            } else {\n                this.packSize += this.packer.appendSizeInBytes(msg.size());\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/encoder/log/AccessLogJsonEncoder.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.report.encoder.log;\n\nimport com.google.auto.service.AutoService;\nimport com.megaease.easeagent.config.report.ReportConfigConst;\nimport com.megaease.easeagent.plugin.api.config.Config;\nimport com.megaease.easeagent.plugin.api.logging.AccessLogInfo;\nimport com.megaease.easeagent.plugin.report.ByteWrapper;\nimport com.megaease.easeagent.plugin.report.EncodedData;\nimport com.megaease.easeagent.plugin.report.Encoder;\nimport com.megaease.easeagent.plugin.report.encoder.JsonEncoder;\nimport zipkin2.internal.JsonCodec;\n\n@AutoService(Encoder.class)\npublic class AccessLogJsonEncoder extends JsonEncoder<AccessLogInfo> {\n    public static final String ENCODER_NAME = ReportConfigConst.ACCESS_LOG_JSON_ENCODER_NAME;\n\n    AccessLogWriter writer;\n\n    @Override\n    public String name() {\n        return ENCODER_NAME;\n    }\n\n    @Override\n    public void init(Config config) {\n        this.writer = new AccessLogWriter();\n    }\n\n    @Override\n    public int sizeInBytes(AccessLogInfo input) {\n        if (input.getEncodedData() != null) {\n            return input.getEncodedData().size();\n        }\n        return this.writer.sizeInBytes(input);\n    }\n\n    @Override\n    public EncodedData encode(AccessLogInfo input) {\n        try {\n            EncodedData d = input.getEncodedData();\n            if (d == null) {\n                d = new ByteWrapper(JsonCodec.write(writer, input));\n                input.setEncodedData(d);\n            }\n            return d;\n        } catch (Exception e) {\n            return new ByteWrapper(new byte[0]);\n        }\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/encoder/log/AccessLogWriter.java",
    "content": "/*\n * Copyright (c) 2022, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.report.encoder.log;\n\nimport com.megaease.easeagent.plugin.api.logging.AccessLogInfo;\nimport zipkin2.internal.JsonEscaper;\nimport zipkin2.internal.WriteBuffer;\n\nimport java.util.Map;\n\npublic class AccessLogWriter implements WriteBuffer.Writer<AccessLogInfo> {\n    static final String TYPE_FIELD_NAME = \"\\\"type\\\":\\\"\";\n    static final String TRACE_ID_FIELD_NAME = \",\\\"trace_id\\\":\\\"\";\n    static final String SPAN_ID_FIELD_NAME = \",\\\"span_id\\\":\\\"\";\n    static final String PARENT_ID_FIELD_NAME = \",\\\"pspan_id\\\":\\\"\";\n    static final String SERVICE_FIELD_NAME = \",\\\"service\\\":\\\"\";\n    static final String SYSTEM_FIELD_NAME = \",\\\"system\\\":\\\"\";\n    static final String CLIENT_IP_FIELD_NAME = \",\\\"client_ip\\\":\\\"\";\n    static final String USER_FIELD_NAME = \",\\\"user\\\":\\\"\";\n    static final String RESPONSE_SIZE_FIELD_NAME = \",\\\"response_size\\\":\";\n    static final String REQUEST_TIME_FIELD_NAME = \",\\\"request_time\\\":\";\n    static final String CPU_ELAPSED_TIME_FIELD_NAME = \",\\\"cpuElapsedTime\\\":\";\n    static final String URL_FIELD_NAME = \",\\\"url\\\":\\\"\";\n    static final String METHOD_FIELD_NAME = \",\\\"method\\\":\\\"\";\n    static final String STATUS_CODE_FIELD_NAME = \",\\\"status_code\\\":\\\"\";\n    static final String HOST_NAME_FIELD_NAME = \",\\\"host_name\\\":\\\"\";\n    static final String HOST_IPV4_FIELD_NAME = \",\\\"host_ipv4\\\":\\\"\";\n    static final String CATEGORY_FIELD_NAME = \",\\\"category\\\":\\\"\";\n    static final String MATCH_URL_FIELD_NAME = \",\\\"match_url\\\":\\\"\";\n    static final String TIMESTAMP_FIELD_NAME = \",\\\"timestamp\\\":\";\n    static final String HEADERS_FIELD_NAME = \",\\\"headers\\\":{\";\n    static final String QUERIES_FIELD_NAME = \",\\\"queries\\\":{\";\n\n    static final int STATIC_SIZE = 2\n        + TYPE_FIELD_NAME.length() + 1\n        + URL_FIELD_NAME.length() + 1\n        + TRACE_ID_FIELD_NAME.length() + 1\n        + SPAN_ID_FIELD_NAME.length() + 1\n        // + PARENT_ID_FIELD_NAME.length() + 1\n        + SERVICE_FIELD_NAME.length() + 1\n        + SYSTEM_FIELD_NAME.length() + 1\n        + METHOD_FIELD_NAME.length() + 1\n        + CATEGORY_FIELD_NAME.length() + 1\n        + HEADERS_FIELD_NAME.length()\n        + QUERIES_FIELD_NAME.length()\n        + STATUS_CODE_FIELD_NAME.length() + 1\n        + CLIENT_IP_FIELD_NAME.length() + 1\n        + USER_FIELD_NAME.length() + 1\n        + RESPONSE_SIZE_FIELD_NAME.length()\n        + REQUEST_TIME_FIELD_NAME.length()\n        + CPU_ELAPSED_TIME_FIELD_NAME.length()\n        + HOST_NAME_FIELD_NAME.length() + 1\n        + HOST_IPV4_FIELD_NAME.length() + 1\n        + MATCH_URL_FIELD_NAME.length() + 1\n        + TIMESTAMP_FIELD_NAME.length();\n\n\n    @Override\n    public int sizeInBytes(AccessLogInfo value) {\n        if (value.getEncodedData() != null) {\n            return value.getEncodedData().size();\n        }\n\n        int size = STATIC_SIZE;\n        size += value.getType().length();\n\n        size += JsonEscaper.jsonEscapedSizeInBytes(value.getUrl());\n\n        size += value.getTraceId().length();\n        size += value.getSpanId().length();\n\n        if (value.getParentSpanId() != null) {\n            size += PARENT_ID_FIELD_NAME.length() + 1;\n            size += stringSizeInBytes(value.getParentSpanId());\n        }\n\n        size += JsonEscaper.jsonEscapedSizeInBytes(value.getService());\n        size += JsonEscaper.jsonEscapedSizeInBytes(value.getSystem());\n\n        size +=value.getMethod().length();\n        size +=value.getCategory().length();\n\n        size += mapSizeInBytes(value.getHeaders());\n        size += mapSizeInBytes(value.getQueries());\n\n        size +=value.getStatusCode().length();\n\n        size +=value.getClientIP().length();\n\n        size += JsonEscaper.jsonEscapedSizeInBytes(value.getUser());\n\n        size += WriteBuffer.asciiSizeInBytes(value.getResponseSize());\n        size += WriteBuffer.asciiSizeInBytes(value.getRequestTime());\n        size += WriteBuffer.asciiSizeInBytes(value.getCpuElapsedTime());\n        size += JsonEscaper.jsonEscapedSizeInBytes(value.getHostName());\n\n        size += value.getHostIpv4().length();\n        size += JsonEscaper.jsonEscapedSizeInBytes(value.getMatchUrl());\n        size += WriteBuffer.asciiSizeInBytes(value.getTimestamp());\n\n        return size;\n    }\n\n    @Override\n    public void write(AccessLogInfo value, WriteBuffer b) {\n        b.writeByte(123);\n        b.writeAscii(TYPE_FIELD_NAME);\n        b.writeAscii(value.getType());\n        b.writeByte('\\\"');\n\n        b.writeAscii(URL_FIELD_NAME);\n        b.writeUtf8(JsonEscaper.jsonEscape(value.getUrl()));\n        b.writeByte('\\\"');\n\n        b.writeAscii(TRACE_ID_FIELD_NAME);\n        b.writeAscii(value.getTraceId());\n        b.writeByte('\\\"');\n\n        b.writeAscii(SPAN_ID_FIELD_NAME);\n        b.writeAscii(value.getSpanId());\n        b.writeByte('\\\"');\n\n        if (value.getParentSpanId() != null) {\n            b.writeAscii(PARENT_ID_FIELD_NAME);\n            writeAscii(value.getParentSpanId(), b);\n            b.writeByte('\\\"');\n        }\n        b.writeAscii(SERVICE_FIELD_NAME);\n        b.writeUtf8(JsonEscaper.jsonEscape(value.getService()));\n        b.writeByte('\\\"');\n\n        b.writeAscii(SYSTEM_FIELD_NAME);\n        b.writeUtf8(JsonEscaper.jsonEscape(value.getSystem()));\n        b.writeByte('\\\"');\n\n        b.writeAscii(METHOD_FIELD_NAME);\n        b.writeAscii(value.getMethod());\n        b.writeByte('\\\"');\n\n        b.writeAscii(CATEGORY_FIELD_NAME);\n        b.writeAscii(value.getCategory());\n        b.writeByte('\\\"');\n\n        b.writeAscii(HEADERS_FIELD_NAME);\n        writeMap(value.getHeaders(), b);\n\n        b.writeAscii(QUERIES_FIELD_NAME);\n        writeMap(value.getQueries(), b);\n\n        b.writeAscii(STATUS_CODE_FIELD_NAME);\n        b.writeAscii(value.getStatusCode());\n        b.writeByte('\\\"');\n\n        b.writeAscii(CLIENT_IP_FIELD_NAME);\n        b.writeAscii(value.getClientIP());\n        b.writeByte('\\\"');\n\n        b.writeAscii(USER_FIELD_NAME);\n        b.writeUtf8(JsonEscaper.jsonEscape(value.getUser()));\n        b.writeByte('\\\"');\n\n        b.writeAscii(RESPONSE_SIZE_FIELD_NAME);\n        b.writeAscii(value.getResponseSize());\n\n        b.writeAscii(REQUEST_TIME_FIELD_NAME);\n        b.writeAscii(value.getRequestTime());\n\n        b.writeAscii(CPU_ELAPSED_TIME_FIELD_NAME);\n        b.writeAscii(value.getCpuElapsedTime());\n\n        b.writeAscii(HOST_NAME_FIELD_NAME);\n        b.writeUtf8(JsonEscaper.jsonEscape(value.getHostName()));\n        b.writeByte('\\\"');\n\n        b.writeAscii(HOST_IPV4_FIELD_NAME);\n        b.writeAscii(value.getHostIpv4());\n        b.writeByte('\\\"');\n\n        b.writeAscii(MATCH_URL_FIELD_NAME);\n        b.writeUtf8(JsonEscaper.jsonEscape(value.getMatchUrl()));\n        b.writeByte('\\\"');\n\n        b.writeAscii(TIMESTAMP_FIELD_NAME);\n        b.writeAscii(value.getTimestamp());\n        b.writeByte(125);\n    }\n\n\n    private int mapSizeInBytes(Map<String, String> vs) {\n        int s = 1;\n        if (vs.isEmpty()) {\n            return s;\n        }\n        for (Map.Entry<String, String> kv : vs.entrySet()) {\n            if (s > 1)  {\n                s += 6;\n            } else {\n                s += 5;\n            }\n            s += JsonEscaper.jsonEscapedSizeInBytes(kv.getKey());\n            s += JsonEscaper.jsonEscapedSizeInBytes(kv.getValue());\n        }\n        return s;\n    }\n\n    private void writeMap(Map<String, String> vs, WriteBuffer b) {\n        int idx = 0;\n        for (Map.Entry<String, String> kv : vs.entrySet()) {\n            if (idx++ > 0)  {\n                b.writeByte(',');\n            }\n            b.writeByte('\\\"');\n            b.writeUtf8(JsonEscaper.jsonEscape(kv.getKey()));\n            b.writeByte('\\\"');\n            b.writeByte(':');\n            b.writeByte('\\\"');\n            b.writeUtf8(JsonEscaper.jsonEscape(kv.getValue()));\n            b.writeByte('\\\"');\n        }\n        b.writeByte('}');\n    }\n\n    private int stringSizeInBytes(String v) {\n        if (v == null) {\n            return \"null\".length() + 2;\n            // return 0;\n        } else {\n            return v.length();\n        }\n    }\n\n    private void writeAscii(String v, WriteBuffer b) {\n        if (v == null) {\n            b.writeAscii(\"null\");\n        } else {\n            b.writeAscii(v);\n        }\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/encoder/log/LogDataJsonEncoder.java",
    "content": "/*\n * Copyright (c) 2022, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.report.encoder.log;\n\nimport com.google.auto.service.AutoService;\nimport com.megaease.easeagent.config.Configs;\nimport com.megaease.easeagent.config.report.ReportConfigConst;\nimport com.megaease.easeagent.plugin.api.config.ChangeItem;\nimport com.megaease.easeagent.plugin.api.config.Config;\nimport com.megaease.easeagent.plugin.api.config.ConfigChangeListener;\nimport com.megaease.easeagent.plugin.api.otlp.common.AgentLogData;\nimport com.megaease.easeagent.plugin.report.ByteWrapper;\nimport com.megaease.easeagent.plugin.report.EncodedData;\nimport com.megaease.easeagent.plugin.report.Encoder;\nimport com.megaease.easeagent.plugin.report.encoder.JsonEncoder;\nimport zipkin2.internal.JsonCodec;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.TreeMap;\n\nimport static com.megaease.easeagent.config.report.ReportConfigConst.ENCODER_KEY;\n\n@AutoService(Encoder.class)\npublic class LogDataJsonEncoder extends JsonEncoder<AgentLogData> implements ConfigChangeListener {\n    public static final String ENCODER_NAME = ReportConfigConst.LOG_DATA_JSON_ENCODER_NAME;\n\n    Config encoderConfig;\n    LogDataWriter writer;\n\n    @Override\n    public void init(Config config) {\n        config.addChangeListener(this);\n        this.encoderConfig = new Configs(getEncoderConfig(config.getConfigs()));\n        this.writer = new LogDataWriter(this.encoderConfig);\n    }\n\n    @Override\n    public int sizeInBytes(AgentLogData input) {\n        return this.writer.sizeInBytes(input);\n    }\n\n    @Override\n    public EncodedData encode(AgentLogData input) {\n        try {\n            EncodedData d = input.getEncodedData();\n            if (d == null) {\n                d = new ByteWrapper(JsonCodec.write(writer, input));\n                input.setEncodedData(d);\n            }\n            return d;\n        } catch (Exception e) {\n            return new ByteWrapper(new byte[0]);\n        }\n    }\n\n    @Override\n    public String name() {\n        return ENCODER_NAME;\n    }\n\n    @Override\n    public void onChange(List<ChangeItem> list) {\n        if (list.isEmpty()) {\n            return;\n        }\n        Map<String, String> changes = new HashMap<>();\n        list.forEach(change -> changes.put(change.getFullName(), change.getNewValue()));\n        Map<String, String> encoderChanges = getEncoderConfig(changes);\n        if (encoderChanges.isEmpty()) {\n            return;\n        }\n        Map<String, String> cfg = this.encoderConfig.getConfigs();\n        cfg.putAll(encoderChanges);\n        this.encoderConfig = new Configs(cfg);\n        this.writer = new LogDataWriter(this.encoderConfig);\n    }\n\n    private Map<String, String> getEncoderConfig(Map<String, String> cfgMap) {\n        Map<String, String> encoderMap = new TreeMap<>();\n\n        cfgMap.forEach((k, v) -> {\n            if (k.contains(ENCODER_KEY) && !k.endsWith(ENCODER_KEY)) {\n                encoderMap.put(k.substring(k.lastIndexOf('.') + 1), v);\n            }\n        });\n\n        return encoderMap;\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/encoder/log/LogDataWriter.java",
    "content": "/*\n * Copyright (c) 2022, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.report.encoder.log;\n\nimport com.megaease.easeagent.plugin.api.config.Config;\nimport com.megaease.easeagent.plugin.api.otlp.common.AgentLogData;\nimport com.megaease.easeagent.plugin.utils.common.StringUtils;\nimport com.megaease.easeagent.report.encoder.log.pattern.LogDataPatternFormatter;\nimport io.opentelemetry.api.trace.SpanContext;\nimport org.apache.logging.log4j.core.layout.PatternLayout;\nimport org.apache.logging.log4j.core.pattern.PatternParser;\nimport zipkin2.internal.JsonEscaper;\nimport zipkin2.internal.WriteBuffer;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n<providers>\n    <timestamp>\n        <fieldName>timestamp</fieldName>\n        <pattern>[UNIX_TIMESTAMP_AS_NUMBER]</pattern>\n        <timeZone>UTC</timeZone>\n    </timestamp>\n    <pattern>\n        <pattern>\n            {\n                \"service\": \"${APP_NAME}\",\n                \"traceId\": \"%X{traceId}\",\n                \"id\": \"%X{spanId}\",\n                \"logLevel\": \"%-5level\",\n                \"threadId\": \"%thread\",\n                \"location\": \"%logger{36}\",\n                \"message\": \"%msg%n\",\n                \"type\": \"application-log\"\n            }\n        </pattern>\n    </pattern>\n</providers>\n**/\npublic class LogDataWriter implements WriteBuffer.Writer<AgentLogData> {\n    static final String TYPE_FIELD_NAME = \"\\\"type\\\":\\\"application-log\\\"\";\n\n    static final String TRACE_ID_FIELD_NAME = \",\\\"traceId\\\":\\\"\";\n    static final String SPAN_ID_FIELD_NAME = \",\\\"id\\\":\\\"\";\n\n    static final String SERVICE_FIELD_NAME = \",\\\"service\\\":\\\"\";\n    static final String SYSTEM_FIELD_NAME = \",\\\"system\\\":\\\"\";\n    static final String TIMESTAMP_FILED_NAME = \",\\\"timestamp\\\":\\\"\";\n    static final String TIMESTAMP_NUM_FILED_NAME = \",\\\"timestamp\\\":\";\n    static final String LOG_LEVEL_FIELD_NAME = \",\\\"logLevel\\\":\\\"\";\n    static final String THREAD_ID_FIELD_NAME = \",\\\"threadId\\\":\\\"\";\n    static final String LOCATION_FIELD_NAME = \",\\\"location\\\":\\\"\";\n    static final String MESSAGE_FIELD_NAME = \",\\\"message\\\":\\\"\";\n\n    static final String TIMESTAMP = \"timestamp\";\n    static final String LOG_LEVEL = \"logLevel\";\n    static final String THREAD_ID = \"threadId\";\n    static final String LOCATION = \"location\";\n    static final String MESSAGE = \"message\";\n\n    static final int STATIC_SIZE = 2\n        + TYPE_FIELD_NAME.length()\n        + SERVICE_FIELD_NAME.length() + 1\n        + SYSTEM_FIELD_NAME.length() + 1;\n\n    private static final ThreadLocal<StringBuilder> threadLocal = new ThreadLocal<>();\n\n    protected static final int DEFAULT_STRING_BUILDER_SIZE = 1024;\n    protected static final int MAX_STRING_BUILDER_SIZE = 2048;\n\n    Config config;\n\n    PatternParser parser;\n\n    boolean dateTypeIsNumber = false;\n    List<LogDataPatternFormatter> dateFormats;\n    List<LogDataPatternFormatter> threadIdFormats;\n    List<LogDataPatternFormatter> levelFormats;\n    List<LogDataPatternFormatter> locationFormats;\n    List<LogDataPatternFormatter> messageFormats;\n\n    Map<String, List<LogDataPatternFormatter>> customFields = new HashMap<>();\n\n    public LogDataWriter(Config cfg) {\n        this.config = cfg;\n        this.parser = PatternLayout.createPatternParser(null);\n        initFormatters();\n    }\n\n    @Override\n    public int sizeInBytes(AgentLogData value) {\n        int size = STATIC_SIZE;\n\n        size += JsonEscaper.jsonEscapedSizeInBytes(value.getAgentResource().getService());\n        size += JsonEscaper.jsonEscapedSizeInBytes(value.getAgentResource().getSystem());\n\n        if (!value.getSpanContext().equals(SpanContext.getInvalid())) {\n            size += TRACE_ID_FIELD_NAME.length() + value.getSpanContext().getTraceId().length() + 1;\n            size += SPAN_ID_FIELD_NAME.length() + value.getSpanContext().getSpanId().length() + 1;\n        }\n\n        StringBuilder sb = getStringBuilder();\n        if (this.dateTypeIsNumber) {\n            size += TIMESTAMP_NUM_FILED_NAME.length();\n            size += WriteBuffer.asciiSizeInBytes(value.getEpochMillis());\n        } else {\n            size += kvLength(TIMESTAMP_FILED_NAME, value, this.dateFormats, sb, false);\n        }\n\n        size += kvLength(LOG_LEVEL_FIELD_NAME, value, this.levelFormats, sb, false);\n        size += kvLength(THREAD_ID_FIELD_NAME, value, this.threadIdFormats, sb, false);\n\n        // instrumentInfo - loggerName\n        size += kvLength(LOCATION_FIELD_NAME, value, this.locationFormats, sb, false);\n\n        if (!this.customFields.isEmpty()) {\n            for (Map.Entry<String, List<LogDataPatternFormatter>> c : this.customFields.entrySet()) {\n                size += kvLength(c.getKey(), value, c.getValue(), sb, true);\n            }\n        }\n\n        size += kvLength(MESSAGE_FIELD_NAME, value, this.messageFormats, sb, true);\n\n        return size;\n    }\n\n    @Override\n    public void write(AgentLogData value, WriteBuffer b) {\n        StringBuilder sb = getStringBuilder();\n\n        // fix items\n        b.writeByte(123);\n        b.writeAscii(TYPE_FIELD_NAME);\n\n        if (!value.getSpanContext().equals(SpanContext.getInvalid())) {\n            // traceId\n            b.writeAscii(TRACE_ID_FIELD_NAME);\n            b.writeAscii(value.getSpanContext().getTraceId());\n            b.writeByte('\\\"');\n\n            b.writeAscii(SPAN_ID_FIELD_NAME);\n            b.writeAscii(value.getSpanContext().getSpanId());\n            b.writeByte('\\\"');\n        }\n\n        // resource - system/service\n        b.writeAscii(SERVICE_FIELD_NAME);\n        b.writeUtf8(JsonEscaper.jsonEscape(value.getAgentResource().getService()));\n        b.writeByte('\\\"');\n\n        b.writeAscii(SYSTEM_FIELD_NAME);\n        b.writeUtf8(JsonEscaper.jsonEscape(value.getAgentResource().getSystem()));\n        b.writeByte('\\\"');\n\n        if (this.dateTypeIsNumber) {\n            b.writeAscii(TIMESTAMP_NUM_FILED_NAME);\n            b.writeAscii(value.getEpochMillis());\n        } else {\n            writeKeyValue(b, TIMESTAMP_FILED_NAME, value, this.dateFormats, sb, false);\n        }\n\n        writeKeyValue(b, LOG_LEVEL_FIELD_NAME, value, this.levelFormats, sb, false);\n        writeKeyValue(b, THREAD_ID_FIELD_NAME, value, this.threadIdFormats, sb, false);\n\n        // instrumentInfo - loggerName\n        writeKeyValue(b, LOCATION_FIELD_NAME, value, this.locationFormats, sb, false);\n\n        // attribute and custom\n        if (!this.customFields.isEmpty()) {\n            for (Map.Entry<String, List<LogDataPatternFormatter>> c : this.customFields.entrySet()) {\n                writeKeyValue(b, c.getKey(), value, c.getValue(), sb, true);\n            }\n        }\n\n        writeKeyValue(b, MESSAGE_FIELD_NAME, value, this.messageFormats, sb, true);\n\n        b.writeByte(125);\n\n    }\n\n    /**\n     *  count size written by @{link writeKeyValue}\n     * @return size\n     */\n    private int kvLength(String key, AgentLogData value,\n                              List<LogDataPatternFormatter> formatters, StringBuilder sb, boolean escape) {\n        String d = value.getPatternMap().get(key);\n        if (d == null) {\n            sb.setLength(0);\n            d = toSerializable(value, formatters, sb);\n            value.getPatternMap().put(key, d);\n        }\n\n        if (StringUtils.isEmpty(d)) {\n            return 0;\n        } else {\n            if (escape) {\n                return key.length() + JsonEscaper.jsonEscapedSizeInBytes(d) + 1;\n            } else {\n                return key.length() + d.length() + 1;\n            }\n        }\n    }\n\n    private void writeKeyValue(WriteBuffer b, String key, AgentLogData value,\n                               List<LogDataPatternFormatter> formatters,\n                               StringBuilder sb, boolean escape) {\n        String d = value.getPatternMap().get(key);\n        if (d == null) {\n            sb.setLength(0);\n            d = toSerializable(value, formatters, sb);\n        }\n\n        if (!StringUtils.isEmpty(d)) {\n            b.writeAscii(key);\n            if (escape) {\n                b.writeUtf8(JsonEscaper.jsonEscape(d));\n            } else {\n                b.writeAscii(d);\n            }\n            b.writeByte('\\\"');\n        }\n    }\n\n    private void initFormatters() {\n        this.config.getConfigs().forEach((k, v) -> {\n            List<LogDataPatternFormatter> logDataFormatters = LogDataPatternFormatter.transform(v, this.parser);\n\n            switch (k) {\n                case LOG_LEVEL:\n                    this.levelFormats = logDataFormatters;\n                    break;\n                case THREAD_ID:\n                    this.threadIdFormats = logDataFormatters;\n                    break;\n                case LOCATION:\n                    this.locationFormats = logDataFormatters;\n                    break;\n                case TIMESTAMP:\n                    this.dateFormats = logDataFormatters;\n                    this.dateTypeIsNumber = v.equals(\"%d{UNIX_MILLIS}\") || v.equals(\"%d{UNIX}\")\n                        || v.equals(\"%date{UNIX_MILLIS}\") || v.equals(\"%date{UNIX}\");\n                    break;\n                case MESSAGE:\n                    this.messageFormats = logDataFormatters;\n                    break;\n                default:\n                    // custom attribute encoder\n                    String key = \",\\\"\" + k + \"\\\":\\\"\";\n                    this.customFields.put(key, logDataFormatters);\n                    break;\n            }\n        });\n    }\n\n    /**\n     * Returns a {@code StringBuilder} that this Layout implementation can use to write the formatted log event to.\n     *\n     * @return a {@code StringBuilder}\n     */\n    protected static StringBuilder getStringBuilder() {\n        StringBuilder result = threadLocal.get();\n        if (result == null) {\n            result = new StringBuilder(DEFAULT_STRING_BUILDER_SIZE);\n            threadLocal.set(result);\n        }\n        trimToMaxSize(result, MAX_STRING_BUILDER_SIZE);\n        result.setLength(0);\n        return result;\n    }\n\n    private String toSerializable(final AgentLogData value,\n                                        List<LogDataPatternFormatter> formatters,\n                                        final StringBuilder sb) {\n        sb.setLength(0);\n        for (LogDataPatternFormatter formatter : formatters) {\n            formatter.format(value, sb);\n        }\n        return sb.toString();\n    }\n\n    public static void trimToMaxSize(final StringBuilder stringBuilder, final int maxSize) {\n        if (stringBuilder != null && stringBuilder.capacity() > maxSize) {\n            stringBuilder.setLength(maxSize);\n            stringBuilder.trimToSize();\n        }\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/encoder/log/pattern/LogDataDatePatternConverterDelegate.java",
    "content": "/*\n * Copyright (c) 2022, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.report.encoder.log.pattern;\n\nimport com.megaease.easeagent.plugin.api.otlp.common.AgentLogData;\nimport org.apache.logging.log4j.core.pattern.DatePatternConverter;\n\nimport java.util.concurrent.TimeUnit;\n\npublic class LogDataDatePatternConverterDelegate extends LogDataPatternConverter {\n    DatePatternConverter converter;\n\n    /**\n     * Create a new pattern converter.\n     *\n     * @param options  options.\n     */\n    protected LogDataDatePatternConverterDelegate(String[] options) {\n        super(\"Date\", \"date\");\n        this.converter = DatePatternConverter.newInstance(options);\n    }\n\n    public LogDataDatePatternConverterDelegate(DatePatternConverter dateConverter) {\n        super(\"Date\", \"date\");\n        this.converter = dateConverter;\n    }\n\n    @Override\n    public void format(AgentLogData event, StringBuilder toAppendTo) {\n        this.converter.format(event.getEpochMillis(), toAppendTo);\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/encoder/log/pattern/LogDataLevelPatternConverter.java",
    "content": "/*\n * Copyright (c) 2022, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.report.encoder.log.pattern;\n\nimport com.megaease.easeagent.plugin.api.otlp.common.AgentLogData;\n\npublic class LogDataLevelPatternConverter extends LogDataPatternConverter {\n    /**\n     * Create a new pattern converter.\n     */\n    protected LogDataLevelPatternConverter() {\n        super(\"Level\", \"level\");\n    }\n\n    @Override\n    public void format(AgentLogData event, StringBuilder toAppendTo) {\n        toAppendTo.append(event.getSeverityText());\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/encoder/log/pattern/LogDataLineSeparatorPatternConverter.java",
    "content": "/*\n * Copyright (c) 2022, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.report.encoder.log.pattern;\n\nimport com.megaease.easeagent.plugin.api.otlp.common.AgentLogData;\n\npublic class LogDataLineSeparatorPatternConverter extends LogDataPatternConverter {\n    public static final LogDataLineSeparatorPatternConverter INSTANCE = new LogDataLineSeparatorPatternConverter();\n    @Override\n    public void format(AgentLogData event, StringBuilder toAppendTo) {\n        toAppendTo.append(System.lineSeparator());\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/encoder/log/pattern/LogDataLoggerPatternConverter.java",
    "content": "/*\n * Copyright (c) 2022, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.report.encoder.log.pattern;\n\nimport com.megaease.easeagent.plugin.api.otlp.common.AgentLogData;\n\npublic class LogDataLoggerPatternConverter extends NamePatternConverter {\n    /**\n     * Create a new pattern converter.\n     *\n     * @param options options, may be null.\n     */\n    public LogDataLoggerPatternConverter(String[] options) {\n        super(\"Logger\", \"logger\", options);\n    }\n\n    @Override\n    public void format(AgentLogData event, StringBuilder toAppendTo) {\n        abbreviate(event.getLocation(), toAppendTo);\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/encoder/log/pattern/LogDataMdcPatternConverter.java",
    "content": "/*\n * Copyright (c) 2022, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.megaease.easeagent.report.encoder.log.pattern;\n\nimport com.megaease.easeagent.plugin.api.otlp.common.AgentLogData;\nimport com.megaease.easeagent.plugin.api.otlp.common.SemanticKey;\nimport io.opentelemetry.api.common.AttributeKey;\nimport io.opentelemetry.api.common.Attributes;\nimport org.apache.logging.log4j.util.StringBuilders;\nimport org.apache.logging.log4j.util.TriConsumer;\n\nimport java.util.Arrays;\n\n/**\n * port from log4j2.MdcPatternConverter\n */\npublic class LogDataMdcPatternConverter extends LogDataPatternConverter {\n    /**\n     * Name of property to output.\n     */\n    private final String key;\n    private final String[] keys;\n    private final boolean full;\n\n    // reference to log4j2's MdcPatternConverter\n    public LogDataMdcPatternConverter(String[] options) {\n        super(options != null && options.length > 0 ? \"MDC{\" + options[0] + '}' : \"MDC\", \"mdc\");\n        if (options != null && options.length > 0) {\n            full = false;\n            if (options[0].indexOf(',') > -1) {\n                String oKey;\n                String[] oKeys = options[0].split(\",\");\n                int idx = 0;\n                for (int i = 0; i < oKeys.length; i++) {\n                    oKey = oKeys[i].trim();\n                    if (oKey.length() <= 0) {\n                        continue;\n                    }\n                    oKeys[idx++] = oKey;\n                }\n                if (idx == 0) {\n                    keys = null;\n                    key = options[0];\n                } else {\n                    keys = Arrays.copyOf(oKeys, idx);\n                    key = null;\n                }\n            } else {\n                keys = null;\n                key = options[0];\n            }\n        } else {\n            full = true;\n            key = null;\n            keys = null;\n        }\n    }\n\n    private static final TriConsumer<AttributeKey<?>, Object, StringBuilder> WRITE_KEY_VALUES_INTO = (k, value, sb) -> {\n        sb.append(k.getKey()).append('=');\n        StringBuilders.appendValue(sb, value);\n        sb.append(\", \");\n    };\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public void format(AgentLogData event, StringBuilder toAppendTo) {\n        final Attributes contextData = event.getAttributes();\n        // if there is no additional options, we output every single\n        // Key/Value pair for the MDC in a similar format to Hashtable.toString()\n        if (full) {\n            if (contextData == null || contextData.isEmpty()) {\n                toAppendTo.append(\"{}\");\n                return;\n            }\n            appendFully(contextData, toAppendTo);\n        } else if (keys != null) {\n            if (contextData == null || contextData.isEmpty()) {\n                toAppendTo.append(\"{}\");\n                return;\n            }\n            appendSelectedKeys(keys, contextData, toAppendTo);\n        } else if (contextData != null){\n            // otherwise they just want a single key output\n            final Object value = contextData.get(SemanticKey.stringKey(key));\n            if (value != null) {\n                StringBuilders.appendValue(toAppendTo, value);\n            }\n        }\n    }\n\n    private static void appendFully(final Attributes contextData, final StringBuilder toAppendTo) {\n        toAppendTo.append(\"{\");\n        final int start = toAppendTo.length();\n        contextData.forEach((k, v) -> WRITE_KEY_VALUES_INTO.accept(k, v, toAppendTo));\n        final int end = toAppendTo.length();\n        if (end > start) {\n            toAppendTo.setCharAt(end - 2, '}');\n            toAppendTo.deleteCharAt(end - 1);\n        } else {\n            toAppendTo.append('}');\n        }\n    }\n\n    private static void appendSelectedKeys(final String[] keys, final Attributes contextData, final StringBuilder sb) {\n        // Print all the keys in the array that have a value.\n        final int start = sb.length();\n        sb.append('{');\n        for (final String theKey : keys) {\n            final Object value = contextData.get(SemanticKey.stringKey(theKey));\n            if (value != null) {\n                if (sb.length() - start > 1) {\n                    sb.append(\", \");\n                }\n                sb.append(theKey).append('=');\n                StringBuilders.appendValue(sb, value);\n            }\n        }\n        sb.append('}');\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/encoder/log/pattern/LogDataPatternConverter.java",
    "content": "/*\n * Copyright (c) 2022, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.report.encoder.log.pattern;\n\nimport com.megaease.easeagent.plugin.api.otlp.common.AgentLogData;\nimport org.apache.logging.log4j.core.LogEvent;\nimport org.apache.logging.log4j.core.pattern.LogEventPatternConverter;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic abstract class LogDataPatternConverter extends LogEventPatternConverter {\n    protected LogDataPatternConverter() {\n        super(\"\", \"\");\n    }\n    /**\n     * Create a new pattern converter.\n     *\n     * @param name  name for pattern converter.\n     * @param style CSS style for formatted output.\n     */\n    protected LogDataPatternConverter(String name, String style) {\n        super(name, style);\n    }\n\n    /**\n     * Formats an event into a string buffer.\n     *\n     * @param event      event to format, may not be null.\n     * @param toAppendTo string buffer to which the formatted event will be appended.  May not be null.\n     */\n    public abstract void format(final AgentLogData event, final StringBuilder toAppendTo);\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public void format(final Object obj, final StringBuilder output) {\n        if (obj instanceof AgentLogData) {\n            format((AgentLogData) obj, output);\n        } else {\n            super.format(obj, output);\n        }\n    }\n\n    @Override\n    public void format(final LogEvent event, final StringBuilder toAppendTo) {\n        // ignored\n    }\n\n    protected String[] getOptions(String pattern, int start) {\n        ArrayList<String> options = new ArrayList<>();\n        extractOptions(pattern, start, options);\n        return options.toArray(new String[0]);\n    }\n\n    /**\n     * Extract options.\n     * borrow from log4j:PatternParser\n     *\n     * @param pattern\n     *            conversion pattern.\n     * @param start\n     *            start of options.\n     * @param options\n     *            array to receive extracted options\n     * @return position in pattern after options.\n     */\n    @SuppressWarnings(\"UnusedReturnValue\")\n    private int extractOptions(final String pattern, final int start, final List<String> options) {\n        int i = pattern.indexOf('{', start);\n        if (i < 0) {\n            return start;\n        }\n        while (i < pattern.length() && pattern.charAt(i) == '{') {\n            i++; // skip opening \"{\"\n            final int begin = i; // position of first real char\n            int depth = 1; // already inside one level\n            while (depth > 0 && i < pattern.length()) {\n                final char c = pattern.charAt(i);\n                if (c == '{') {\n                    depth++;\n                } else if (c == '}') {\n                    depth--;\n                    // TODO(?) maybe escaping of { and } with \\ or %\n                }\n                i++;\n            } // while\n\n            if (depth > 0) { // option not closed, continue with pattern after closing bracket\n                i = pattern.lastIndexOf('}');\n                if (i == -1 || i < start) {\n                    // if no closing bracket could be found or there is no closing bracket behind the starting\n                    // character of our parsing process continue parsing after the first opening bracket\n                    return begin;\n                }\n                return i + 1;\n            }\n\n            options.add(pattern.substring(begin, i - 1));\n        } // while\n\n        return i;\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/encoder/log/pattern/LogDataPatternFormatter.java",
    "content": "/*\n * Copyright (c) 2022, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.report.encoder.log.pattern;\n\nimport com.megaease.easeagent.plugin.api.otlp.common.AgentLogData;\nimport org.apache.logging.log4j.core.pattern.*;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.atomic.AtomicInteger;\n\n/**\n * Log4j pattern formats to LogData pattern formats\n */\npublic class LogDataPatternFormatter extends LogDataPatternConverter {\n    private final LogDataPatternConverter converter;\n    private final FormattingInfo field;\n    private final boolean skipFormattingInfo;\n\n    public LogDataPatternFormatter(String pattern, int patternOffset,\n                                   org.apache.logging.log4j.core.pattern.PatternFormatter formatter) {\n        super(\"\", \"\");\n        this.field = formatter.getFormattingInfo();\n        this.skipFormattingInfo = this.field == FormattingInfo.getDefault();\n        this.converter = extractConvert(formatter.getConverter(), pattern, patternOffset);\n    }\n\n    public void format(final AgentLogData event, final StringBuilder buf) {\n        if (skipFormattingInfo) {\n            converter.format(event, buf);\n        } else {\n            formatWithInfo(event, buf);\n        }\n    }\n\n    private void formatWithInfo(final AgentLogData event, final StringBuilder buf) {\n        final int startField = buf.length();\n        converter.format(event, buf);\n        field.format(startField, buf);\n    }\n\n    private LogDataPatternConverter extractConvert(LogEventPatternConverter converter, String pattern, int patternOffset) {\n        if (converter == null) {\n            return NoOpPatternConverter.INSTANCE;\n        }\n\n        // xxx: can convert to name-INSTANCE map\n        if (converter instanceof DatePatternConverter) {\n            return new LogDataDatePatternConverterDelegate((DatePatternConverter)converter);\n        } else if (converter instanceof LoggerPatternConverter) {\n            return new LogDataLoggerPatternConverter(getOptions(pattern, patternOffset));\n        } else if (converter instanceof LevelPatternConverter) {\n            return new LogDataLevelPatternConverter();\n        } else if (converter.getName().equals(\"SimpleLiteral\")) {\n            return new LogDataSimpleLiteralPatternConverter(converter);\n        } else if (converter instanceof MessagePatternConverter) {\n            return SimpleMessageConverter.INSTANCE;\n        } else if (converter instanceof ThreadNamePatternConverter) {\n            return LogDataThreadNamePatternConverter.INSTANCE;\n        } else if (converter instanceof LineSeparatorPatternConverter) {\n            return LogDataLineSeparatorPatternConverter.INSTANCE;\n        } else if (converter instanceof MdcPatternConverter) {\n            return new LogDataMdcPatternConverter(getOptions(pattern, patternOffset));\n        } else if (converter instanceof ThrowablePatternConverter) {\n            return new LogDataThrowablePatternConverter(getOptions(pattern, patternOffset));\n        } else {\n            return LogDataSimpleLiteralPatternConverter.UNKNOWN;\n        }\n    }\n\n    public static List<LogDataPatternFormatter> transform(String pattern, PatternParser parser) {\n        final List<PatternFormatter> formatters = parser.parse(pattern,\n            false, false, false);\n\n        final List<LogDataPatternFormatter> logDataFormatters = new ArrayList<>();\n        final AtomicInteger patternOffset = new AtomicInteger(0);\n\n        formatters.forEach(f -> {\n            if (!f.getConverter().getName().equals(\"SimpleLiteral\")) {\n                patternOffset.set(pattern.indexOf('%', patternOffset.get()) + 1);\n            }\n            logDataFormatters.add(new LogDataPatternFormatter(pattern, patternOffset.get(), f));\n        });\n\n        return logDataFormatters;\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/encoder/log/pattern/LogDataSimpleLiteralPatternConverter.java",
    "content": "/*\n * Copyright (c) 2022, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.report.encoder.log.pattern;\n\nimport com.megaease.easeagent.plugin.api.otlp.common.AgentLogData;\nimport org.apache.logging.log4j.core.LogEvent;\nimport org.apache.logging.log4j.core.pattern.LogEventPatternConverter;\n\npublic class LogDataSimpleLiteralPatternConverter extends LogDataPatternConverter {\n    public static final LogDataPatternConverter UNKNOWN = new SimpleLiteralConverter(\"-Unknown Pattern-\");\n\n    LogEventPatternConverter converter;\n\n    /**\n     * Create a new pattern converter.\n     */\n    public LogDataSimpleLiteralPatternConverter(LogEventPatternConverter converter) {\n        super(\"SimpleLiteral\", \"literal\");\n        this.converter = converter;\n    }\n\n    @Override\n    public void format(final Object obj, final StringBuilder output) {\n        this.converter.format(obj, output);\n    }\n\n    @Override\n    public void format(AgentLogData event, StringBuilder toAppendTo) {\n        this.format((Object) event, toAppendTo);\n    }\n\n    public static class SimpleLiteralConverter extends LogDataPatternConverter {\n        String literal;\n\n        /**\n         * Constructs an instance of LoggingEventPatternConverter.\n         */\n        protected SimpleLiteralConverter(String literal) {\n            super(\"SimpleLiteral\", \"literal\");\n            this.literal = literal;\n        }\n\n        @Override\n        public void format(AgentLogData event, StringBuilder toAppendTo) {\n            toAppendTo.append(this.literal);\n        }\n\n        @Override\n        public void format(LogEvent event, StringBuilder toAppendTo) {\n            toAppendTo.append(this.literal);\n        }\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/encoder/log/pattern/LogDataThreadNamePatternConverter.java",
    "content": "/*\n * Copyright (c) 2022, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.report.encoder.log.pattern;\n\nimport com.megaease.easeagent.plugin.api.otlp.common.AgentLogData;\n\npublic class LogDataThreadNamePatternConverter extends LogDataPatternConverter {\n    public static final LogDataThreadNamePatternConverter INSTANCE = new LogDataThreadNamePatternConverter();\n    /**\n     * Create a new pattern converter.\n     */\n    protected LogDataThreadNamePatternConverter() {\n        super(\"Thread\", \"thread\");\n    }\n\n    @Override\n    public void format(AgentLogData event, StringBuilder toAppendTo) {\n        toAppendTo.append(event.getThreadName());\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/encoder/log/pattern/LogDataThrowablePatternConverter.java",
    "content": "/*\n * Copyright (c) 2022, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.megaease.easeagent.report.encoder.log.pattern;\n\nimport com.megaease.easeagent.plugin.api.otlp.common.AgentLogData;\nimport org.apache.logging.log4j.core.impl.ThrowableFormatOptions;\nimport org.apache.logging.log4j.core.layout.PatternLayout;\nimport org.apache.logging.log4j.core.pattern.PatternParser;\nimport org.apache.logging.log4j.core.util.StringBuilderWriter;\nimport org.apache.logging.log4j.util.Strings;\n\nimport java.io.PrintWriter;\nimport java.io.StringWriter;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * ported from log4j2.ThrowablePatternConverter\n */\npublic class LogDataThrowablePatternConverter extends LogDataPatternConverter {\n    /**\n     * Lists {@link LogDataPatternFormatter}s for the suffix attribute.\n     */\n    protected final List<LogDataPatternFormatter> formatters;\n    private String rawOption;\n    private final boolean subShortOption;\n    private final boolean nonStandardLineSeparator;\n\n    /**\n     * Options.\n     */\n    protected final ThrowableFormatOptions options;\n\n    public LogDataThrowablePatternConverter(String[] options) {\n        super(\"Throwable\", \"throwable\");\n        this.options = ThrowableFormatOptions.newInstance(options);\n        if (options != null && options.length > 0) {\n            rawOption = options[0];\n        }\n        if (this.options.getSuffix() != null) {\n            final PatternParser parser = PatternLayout.createPatternParser(null);\n            final String suffixPattern = this.options.getSuffix();\n\n            final List<LogDataPatternFormatter> parsedSuffixFormatters = LogDataPatternFormatter.transform(suffixPattern, parser);\n\n            // filter out nested formatters that will handle throwable\n            boolean hasThrowableSuffixFormatter = false;\n            for (final LogDataPatternFormatter suffixFormatter : parsedSuffixFormatters) {\n                if (suffixFormatter.handlesThrowable()) {\n                    hasThrowableSuffixFormatter = true;\n                }\n            }\n            if (!hasThrowableSuffixFormatter) {\n                this.formatters = parsedSuffixFormatters;\n            } else {\n                final List<LogDataPatternFormatter> suffixFormatters = new ArrayList<>();\n                for (final LogDataPatternFormatter suffixFormatter : parsedSuffixFormatters) {\n                    if (!suffixFormatter.handlesThrowable()) {\n                        suffixFormatters.add(suffixFormatter);\n                    }\n                }\n                this.formatters = suffixFormatters;\n            }\n        } else {\n            this.formatters = Collections.emptyList();\n        }\n        subShortOption = ThrowableFormatOptions.MESSAGE.equalsIgnoreCase(rawOption) ||\n            ThrowableFormatOptions.LOCALIZED_MESSAGE.equalsIgnoreCase(rawOption) ||\n            ThrowableFormatOptions.FILE_NAME.equalsIgnoreCase(rawOption) ||\n            ThrowableFormatOptions.LINE_NUMBER.equalsIgnoreCase(rawOption) ||\n            ThrowableFormatOptions.METHOD_NAME.equalsIgnoreCase(rawOption) ||\n            ThrowableFormatOptions.CLASS_NAME.equalsIgnoreCase(rawOption);\n        nonStandardLineSeparator = !Strings.LINE_SEPARATOR.equals(this.options.getSeparator());\n    }\n\n    @Override\n    public void format(AgentLogData event, StringBuilder buffer) {\n        final Throwable t = event.getThrowable();\n\n        if (subShortOption) {\n            formatSubShortOption(t, getSuffix(event), buffer);\n        }\n        else if (t != null && options.anyLines()) {\n            formatOption(t, getSuffix(event), buffer);\n        }\n    }\n\n    private void formatSubShortOption(final Throwable t, final String suffix, final StringBuilder buffer) {\n        StackTraceElement[] trace;\n        StackTraceElement throwingMethod = null;\n        int len;\n\n        if (t != null) {\n            trace = t.getStackTrace();\n            if (trace !=null && trace.length > 0) {\n                throwingMethod = trace[0];\n            }\n        }\n\n        if (t != null && throwingMethod != null) {\n            String toAppend = Strings.EMPTY;\n\n            if (ThrowableFormatOptions.CLASS_NAME.equalsIgnoreCase(rawOption)) {\n                toAppend = throwingMethod.getClassName();\n            }\n            else if (ThrowableFormatOptions.METHOD_NAME.equalsIgnoreCase(rawOption)) {\n                toAppend = throwingMethod.getMethodName();\n            }\n            else if (ThrowableFormatOptions.LINE_NUMBER.equalsIgnoreCase(rawOption)) {\n                toAppend = String.valueOf(throwingMethod.getLineNumber());\n            }\n            else if (ThrowableFormatOptions.MESSAGE.equalsIgnoreCase(rawOption)) {\n                toAppend = t.getMessage();\n            }\n            else if (ThrowableFormatOptions.LOCALIZED_MESSAGE.equalsIgnoreCase(rawOption)) {\n                toAppend = t.getLocalizedMessage();\n            }\n            else if (ThrowableFormatOptions.FILE_NAME.equalsIgnoreCase(rawOption)) {\n                toAppend = throwingMethod.getFileName();\n            }\n\n            len = buffer.length();\n            if (len > 0 && !Character.isWhitespace(buffer.charAt(len - 1))) {\n                buffer.append(' ');\n            }\n            buffer.append(toAppend);\n\n            if (Strings.isNotBlank(suffix)) {\n                buffer.append(' ');\n                buffer.append(suffix);\n            }\n        }\n    }\n\n    private void formatOption(final Throwable throwable, final String suffix, final StringBuilder buffer) {\n        final int len = buffer.length();\n        if (len > 0 && !Character.isWhitespace(buffer.charAt(len - 1))) {\n            buffer.append(' ');\n        }\n        if (!options.allLines() || nonStandardLineSeparator || Strings.isNotBlank(suffix)) {\n            final StringWriter w = new StringWriter();\n            throwable.printStackTrace(new PrintWriter(w));\n\n            final String[] array = w.toString().split(Strings.LINE_SEPARATOR);\n            final int limit = options.minLines(array.length) - 1;\n            final boolean suffixNotBlank = Strings.isNotBlank(suffix);\n            for (int i = 0; i <= limit; ++i) {\n                buffer.append(array[i]);\n                if (suffixNotBlank) {\n                    buffer.append(' ');\n                    buffer.append(suffix);\n                }\n                if (i < limit) {\n                    buffer.append(options.getSeparator());\n                }\n            }\n        } else {\n            throwable.printStackTrace(new PrintWriter(new StringBuilderWriter(buffer)));\n        }\n    }\n\n    /**\n     * This converter obviously handles throwables.\n     *\n     * @return true.\n     */\n    @Override\n    public boolean handlesThrowable() {\n        return true;\n    }\n\n    protected String getSuffix(final AgentLogData event) {\n        if (formatters.isEmpty()) {\n            return Strings.EMPTY;\n        }\n\n        //noinspection ForLoopReplaceableByForEach\n        final StringBuilder toAppendTo = new StringBuilder();\n        for (int i = 0, size = formatters.size(); i <  size; i++) {\n            formatters.get(i).format(event, toAppendTo);\n        }\n\n        return toAppendTo.toString();\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/encoder/log/pattern/NamePatternConverter.java",
    "content": "/*\n * Copyright (c) 2022, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.report.encoder.log.pattern;\n\nimport org.apache.logging.log4j.core.pattern.NameAbbreviator;\n\npublic abstract class NamePatternConverter extends LogDataPatternConverter {\n    private final NameAbbreviator abbreviator;\n\n    /**\n     * Constructor.\n     *\n     * @param name    name of converter.\n     * @param style   style name for associated output.\n     * @param options options, may be null, first element will be interpreted as an abbreviation pattern.\n     */\n    protected NamePatternConverter(final String name, final String style, final String[] options) {\n        super(name, style);\n\n        if (options != null && options.length > 0) {\n            abbreviator = NameAbbreviator.getAbbreviator(options[0]);\n        } else {\n            abbreviator = NameAbbreviator.getDefaultAbbreviator();\n        }\n    }\n\n    /**\n     * Abbreviate name in string buffer.\n     *\n     * @param original string containing name.\n     * @param destination the StringBuilder to write to\n     */\n    protected final void abbreviate(final String original, final StringBuilder destination) {\n        abbreviator.abbreviate(original, destination);\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/encoder/log/pattern/NoOpPatternConverter.java",
    "content": "/*\n * Copyright (c) 2022, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.report.encoder.log.pattern;\n\nimport com.megaease.easeagent.plugin.api.otlp.common.AgentLogData;\n\npublic class NoOpPatternConverter extends LogDataPatternConverter {\n    public final static NoOpPatternConverter INSTANCE = new NoOpPatternConverter(\"\", \"\");\n\n    /**\n     * Create a new pattern converter.\n     *\n     * @param name  name for pattern converter.\n     * @param style CSS style for formatted output.\n     */\n    protected NoOpPatternConverter(String name, String style) {\n        super(name, style);\n    }\n\n    @Override\n    public void format(AgentLogData event, StringBuilder toAppendTo) {\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/encoder/log/pattern/SimpleMessageConverter.java",
    "content": "/*\n * Copyright (c) 2022, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.report.encoder.log.pattern;\n\nimport com.megaease.easeagent.plugin.api.otlp.common.AgentLogData;\n\npublic class SimpleMessageConverter extends LogDataPatternConverter {\n    public static final SimpleMessageConverter INSTANCE = new SimpleMessageConverter();\n    /**\n     * Create a new pattern converter.\n     */\n    protected SimpleMessageConverter() {\n        super(\"msg\", \"msg\");\n    }\n\n    @Override\n    public void format(AgentLogData event, StringBuilder toAppendTo) {\n        toAppendTo.append(event.getBody().asString());\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/encoder/metric/MetricJsonEncoder.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.report.encoder.metric;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.google.auto.service.AutoService;\nimport com.megaease.easeagent.config.report.ReportConfigConst;\nimport com.megaease.easeagent.plugin.api.config.Config;\nimport com.megaease.easeagent.plugin.report.ByteWrapper;\nimport com.megaease.easeagent.plugin.report.EncodedData;\nimport com.megaease.easeagent.plugin.report.Encoder;\nimport com.megaease.easeagent.plugin.report.encoder.JsonEncoder;\n\nimport java.util.Map;\n\n@AutoService(Encoder.class)\npublic class MetricJsonEncoder extends JsonEncoder<Map<String, Object>> {\n    public static final String ENCODER_NAME = ReportConfigConst.METRIC_JSON_ENCODER_NAME;\n\n    static final String ENCODED_TMP = \"__agent_encoded__\";\n\n    private final ObjectMapper objectMapper = new ObjectMapper();\n\n    @Override\n    public String name() {\n        return ENCODER_NAME;\n    }\n\n    @Override\n    public void init(Config config) {\n        // ignored\n    }\n\n    @Override\n    public int sizeInBytes(Map<String, Object> input) {\n        EncodedData d;\n        if (input.get(ENCODED_TMP) != null) {\n            d = (EncodedData)input.get(ENCODED_TMP);\n        } else {\n            d = encode(input);\n            input.put(ENCODED_TMP, d);\n        }\n        return d.size();\n    }\n\n    @Override\n    public EncodedData encode(Map<String, Object> input) {\n        try {\n            byte[] data;\n            if (input.get(ENCODED_TMP) != null) {\n                data = (byte[])input.get(ENCODED_TMP);\n            } else {\n                data = this.objectMapper.writeValueAsBytes(input);\n                input.put(ENCODED_TMP, data);\n            }\n            return new ByteWrapper(data);\n        } catch (JsonProcessingException e) {\n            // ignored\n        }\n        return new ByteWrapper(new byte[0]);\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/encoder/span/AbstractAgentV2SpanEndpointWriter.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.report.encoder.span;\n\nimport com.megaease.easeagent.plugin.report.tracing.Endpoint;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport zipkin2.internal.JsonEscaper;\nimport zipkin2.internal.WriteBuffer;\n\npublic abstract class AbstractAgentV2SpanEndpointWriter implements WriteBuffer.Writer<ReportSpan> {\n\n    static final String SERVICE_NAME_FIELD_NAME = \"\\\"serviceName\\\":\\\"\";\n    static final String IPV4_FIELD_NAME = \"\\\"ipv4\\\":\\\"\";\n    static final String IPV6_FIELD_NAME = \"\\\"ipv6\\\":\\\"\";\n    static final String PORT_FIELD_NAME = \"\\\"port\\\":\";\n\n    protected int endpointSizeInBytes(Endpoint value, boolean writeEmptyServiceName) {\n        int sizeInBytes = 1;\n        String serviceName = value.serviceName();\n        if (serviceName == null && writeEmptyServiceName) {\n            serviceName = \"\";\n        }\n\n        if (serviceName != null) {\n            sizeInBytes += SERVICE_NAME_FIELD_NAME.length() + 1;\n            sizeInBytes += JsonEscaper.jsonEscapedSizeInBytes(serviceName);\n        }\n\n        if (value.ipv4() != null) {\n            if (sizeInBytes != 1) {\n                ++sizeInBytes;\n            }\n\n            sizeInBytes += IPV4_FIELD_NAME.length() + 1;\n            sizeInBytes += value.ipv4().length();\n        }\n\n        if (value.ipv6() != null) {\n            if (sizeInBytes != 1) {\n                ++sizeInBytes;\n            }\n\n            sizeInBytes += IPV6_FIELD_NAME.length() + 1;\n            sizeInBytes += value.ipv6().length();\n        }\n\n        int port = value.port();\n        if (port != 0) {\n            if (sizeInBytes != 1) {\n                ++sizeInBytes;\n            }\n\n            sizeInBytes += PORT_FIELD_NAME.length();\n            sizeInBytes += WriteBuffer.asciiSizeInBytes(port);\n        }\n\n        sizeInBytes += 1;\n        return sizeInBytes;\n    }\n\n    protected void writeEndpoint(Endpoint value, WriteBuffer b, boolean writeEmptyServiceName) {\n\n        b.writeByte('{');\n        boolean wroteField = false;\n        String serviceName = value.serviceName();\n        if (serviceName == null && writeEmptyServiceName) {\n            serviceName = \"\";\n        }\n\n        if (serviceName != null) {\n            b.writeAscii(SERVICE_NAME_FIELD_NAME);\n            b.writeUtf8(JsonEscaper.jsonEscape(serviceName));\n            b.writeByte('\\\"');\n            wroteField = true;\n        }\n\n        if (value.ipv4() != null) {\n            if (wroteField) {\n                b.writeByte(',');\n            }\n            b.writeAscii(IPV4_FIELD_NAME);\n            b.writeAscii(value.ipv4());\n            b.writeByte('\\\"');\n            wroteField = true;\n        }\n\n        if (value.ipv6() != null) {\n            if (wroteField) {\n                b.writeByte(',');\n            }\n            b.writeAscii(IPV6_FIELD_NAME);\n            b.writeAscii(value.ipv6());\n            b.writeByte('\\\"');\n            wroteField = true;\n        }\n\n        int port = value.port();\n        if (port != 0) {\n            if (wroteField) {\n                b.writeByte(',');\n            }\n            b.writeAscii(PORT_FIELD_NAME);\n            b.writeAscii(port);\n        }\n\n        b.writeByte('}');\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/encoder/span/AgentV2SpanAnnotationsWriter.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.report.encoder.span;\n\nimport com.megaease.easeagent.plugin.report.tracing.Annotation;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport zipkin2.internal.JsonEscaper;\nimport zipkin2.internal.WriteBuffer;\n\npublic class AgentV2SpanAnnotationsWriter implements WriteBuffer.Writer<ReportSpan> {\n    static final String ANNOTATION_FIELD_NAME = \",\\\"annotations\\\":[\";\n    static final String TIMESTAMP_FIELD_NAME = \"{\\\"timestamp\\\":\";\n    static final String VALUE_FIELD_NAME = \",\\\"value\\\":\\\"\";\n    static final String ENDPOINT_FIELD_NAME = \",\\\"endpoint\\\":\";\n\n    int annotationSizeInBytes(long timestamp, String value, int endpointSizeInBytes) {\n        int sizeInBytes = 0;\n\n        sizeInBytes += TIMESTAMP_FIELD_NAME.length();\n        sizeInBytes = sizeInBytes + WriteBuffer.asciiSizeInBytes(timestamp);\n        sizeInBytes += VALUE_FIELD_NAME.length() + 1;\n        sizeInBytes += JsonEscaper.jsonEscapedSizeInBytes(value);\n        if (endpointSizeInBytes != 0) {\n            sizeInBytes += ENDPOINT_FIELD_NAME.length() + 1;\n            sizeInBytes += endpointSizeInBytes;\n        }\n        sizeInBytes++;\n        return sizeInBytes;\n    }\n\n    void writeAnnotation(long timestamp, String value, byte[] endpoint, WriteBuffer b) {\n        b.writeAscii(TIMESTAMP_FIELD_NAME);\n        b.writeAscii(timestamp);\n        b.writeAscii(VALUE_FIELD_NAME);\n        b.writeUtf8(JsonEscaper.jsonEscape(value));\n        b.writeByte(34); // \" for value field\n        if (endpoint != null) {\n            b.writeAscii(ENDPOINT_FIELD_NAME);\n            b.write(endpoint);\n            b.writeByte(34); // \" for value field\n        }\n\n        b.writeByte(125); // } for timestamp\n    }\n\n    @Override\n    public int sizeInBytes(ReportSpan value) {\n        int tagCount;\n        int sizeInBytes = 0;\n        if (!value.annotations().isEmpty()) {\n            sizeInBytes += ANNOTATION_FIELD_NAME.length() + 1;\n            tagCount = value.annotations().size();\n            if (tagCount > 1) {\n                sizeInBytes += tagCount - 1; // , for array item\n            }\n\n            for (int i = 0; i < tagCount; ++i) {\n                Annotation a = value.annotations().get(i);\n                sizeInBytes += annotationSizeInBytes(a.timestamp(), a.value(), 0);\n            }\n        }\n        return sizeInBytes;\n    }\n\n    @Override\n    public void write(ReportSpan value, WriteBuffer b) {\n        if (!value.annotations().isEmpty()) {\n            b.writeAscii(ANNOTATION_FIELD_NAME);\n            int i = 0;\n            int length = value.annotations().size();\n\n            while (i < length) {\n                Annotation a = value.annotations().get(i++);\n                writeAnnotation(a.timestamp(), a.value(), null, b);\n                if (i < length) {\n                    b.writeByte(44); //, for array item\n                }\n            }\n            b.writeByte(93); // ] for annotation field\n        }\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/encoder/span/AgentV2SpanBaseWriter.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.report.encoder.span;\n\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport zipkin2.internal.JsonEscaper;\nimport zipkin2.internal.WriteBuffer;\n\npublic class AgentV2SpanBaseWriter implements WriteBuffer.Writer<ReportSpan> {\n\n    static final String TRACE_ID_FIELD_NAME = \"\\\"traceId\\\":\\\"\";\n    static final String PARENT_ID_FIELD_NAME = \",\\\"parentId\\\":\\\"\";\n    static final String SPAN_ID_FIELD_NAME = \",\\\"id\\\":\\\"\";\n    static final String KIND_FIELD_NAME = \",\\\"kind\\\":\\\"\";\n    static final String NAME_FIELD_NAME = \",\\\"name\\\":\\\"\";\n    static final String TIMESTAMP_FIELD_NAME = \",\\\"timestamp\\\":\";\n    static final String DURATION_FIELD_NAME = \",\\\"duration\\\":\";\n    static final String DEBUG_FIELD_VALUE = \",\\\"debug\\\":true\";\n    static final String SHARED_FIELD_VALUE = \",\\\"shared\\\":true\";\n\n    @Override\n    public int sizeInBytes(ReportSpan value) {\n        int sizeInBytes = 0;\n\n        //traceId\n        sizeInBytes += TRACE_ID_FIELD_NAME.length() + 1; // 1 represent the last quote sign\n        sizeInBytes += value.traceId().length();\n\n        //parentId\n        if (value.parentId() != null) {\n            sizeInBytes += PARENT_ID_FIELD_NAME.length() + 1;\n            sizeInBytes += value.parentId().length();\n        }\n\n        // spanId\n        sizeInBytes += SPAN_ID_FIELD_NAME.length() + 1;\n        sizeInBytes += value.id().length();\n\n        // kind\n        if (value.kind() != null) {\n            sizeInBytes += KIND_FIELD_NAME.length() + 1;\n            sizeInBytes += value.kind().length();\n        }\n\n        // name\n        if (value.name() != null) {\n            sizeInBytes += NAME_FIELD_NAME.length() + 1;\n            sizeInBytes += JsonEscaper.jsonEscapedSizeInBytes(value.name());\n        }\n\n        // timestamp\n        if (value.timestamp() != 0L) {\n            sizeInBytes += TIMESTAMP_FIELD_NAME.length();\n            sizeInBytes += WriteBuffer.asciiSizeInBytes(value.timestamp());\n        }\n\n        //duration\n        if (value.duration() != 0L) {\n            sizeInBytes += DURATION_FIELD_NAME.length();\n            sizeInBytes += WriteBuffer.asciiSizeInBytes(value.duration());\n        }\n\n\n        if (value.debug()) {\n            sizeInBytes += DEBUG_FIELD_VALUE.length();\n        }\n\n        if (value.shared()) {\n            sizeInBytes += SHARED_FIELD_VALUE.length();\n        }\n        return sizeInBytes;\n    }\n\n    @Override\n    public void write(ReportSpan value, WriteBuffer b) {\n        b.writeAscii(TRACE_ID_FIELD_NAME);\n        b.writeAscii(value.traceId());\n        b.writeByte('\\\"');\n\n        if (value.parentId() != null) {\n            b.writeAscii(PARENT_ID_FIELD_NAME);\n            b.writeAscii(value.parentId());\n            b.writeByte('\\\"');\n        }\n\n        b.writeAscii(SPAN_ID_FIELD_NAME);\n        b.writeAscii(value.id());\n        b.writeByte(34);\n        if (value.kind() != null) {\n            b.writeAscii(KIND_FIELD_NAME);\n            b.writeAscii(value.kind());\n            b.writeByte('\\\"');\n        }\n\n        if (value.name() != null) {\n            b.writeAscii(NAME_FIELD_NAME);\n            b.writeUtf8(JsonEscaper.jsonEscape(value.name()));\n            b.writeByte('\\\"');\n        }\n\n        if (value.timestamp() != 0L) {\n            b.writeAscii(TIMESTAMP_FIELD_NAME);\n            b.writeAscii(value.timestamp());\n        }\n\n        if (value.duration() != 0L) {\n            b.writeAscii(DURATION_FIELD_NAME);\n            b.writeAscii(value.duration());\n        }\n\n        if (Boolean.TRUE.equals(value.debug())) {\n            b.writeAscii(DEBUG_FIELD_VALUE);\n        }\n\n        if (Boolean.TRUE.equals(value.shared())) {\n            b.writeAscii(SHARED_FIELD_VALUE);\n        }\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/encoder/span/AgentV2SpanGlobalWriter.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.report.encoder.span;\n\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport com.megaease.easeagent.report.util.TextUtils;\nimport org.apache.commons.lang3.mutable.MutableInt;\nimport zipkin2.internal.JsonEscaper;\nimport zipkin2.internal.WriteBuffer;\n\npublic class AgentV2SpanGlobalWriter implements WriteBuffer.Writer<ReportSpan> {\n\n    final String type;\n    final GlobalExtrasSupplier extras;\n\n    static final String TYPE_FIELD_NAME = \",\\\"type\\\":\\\"\";\n    static final String SERVICE_FIELD_NAME = \",\\\"service\\\":\\\"\";\n    static final String SYSTEM_FIELD_NAME = \",\\\"system\\\":\\\"\";\n\n    public AgentV2SpanGlobalWriter(String type, GlobalExtrasSupplier extras) {\n        this.type = type;\n        this.extras = extras;\n    }\n\n    @Override\n    public int sizeInBytes(ReportSpan value) {\n        final MutableInt mutableInt = new MutableInt(0);\n        if (TextUtils.hasText(type)) {\n            mutableInt.add(TYPE_FIELD_NAME.length() + 1);\n            mutableInt.add(JsonEscaper.jsonEscapedSizeInBytes(type));\n        }\n\n        String tmpService = this.extras.service();\n        if (TextUtils.hasText(tmpService)) {\n            mutableInt.add(SERVICE_FIELD_NAME.length() + 1);\n            mutableInt.add(JsonEscaper.jsonEscapedSizeInBytes(tmpService));\n        }\n\n        String tmpSystem = this.extras.system();\n        if (TextUtils.hasText(tmpSystem)) {\n            mutableInt.add(SYSTEM_FIELD_NAME.length() + 1);\n            mutableInt.add(JsonEscaper.jsonEscapedSizeInBytes(tmpSystem));\n        }\n        return mutableInt.intValue();\n    }\n\n    @Override\n    public void write(ReportSpan value, WriteBuffer buffer) {\n        if (TextUtils.hasText(type)) {\n            buffer.writeAscii(TYPE_FIELD_NAME);\n            buffer.writeUtf8(JsonEscaper.jsonEscape(type));\n            buffer.writeByte(34);\n        }\n        String tmpService = this.extras.service();\n        if (TextUtils.hasText(tmpService)) {\n            buffer.writeAscii(SERVICE_FIELD_NAME);\n            buffer.writeUtf8(JsonEscaper.jsonEscape(tmpService));\n            buffer.writeByte(34);\n        }\n        String tmpSystem = this.extras.system();\n        if (TextUtils.hasText(tmpSystem)) {\n            buffer.writeAscii(SYSTEM_FIELD_NAME);\n            buffer.writeUtf8(JsonEscaper.jsonEscape(tmpSystem));\n            buffer.writeByte(34);\n        }\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/encoder/span/AgentV2SpanLocalEndpointWriter.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.report.encoder.span;\n\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport zipkin2.internal.WriteBuffer;\n\npublic class AgentV2SpanLocalEndpointWriter\n    extends AbstractAgentV2SpanEndpointWriter implements WriteBuffer.Writer<ReportSpan> {\n\n    static final String LOCAL_ENDPOINT_FIELD_NAME = \",\\\"localEndpoint\\\":\";\n\n    @Override\n    public int sizeInBytes(ReportSpan value) {\n        if (value.localEndpoint() == null) {\n            return 0;\n        }\n        int size = LOCAL_ENDPOINT_FIELD_NAME.length();\n        size += this.endpointSizeInBytes(value.localEndpoint(), true);\n        return size;\n    }\n\n    @Override\n    public void write(ReportSpan value, WriteBuffer buffer) {\n        if (value.localEndpoint() == null) {\n            return;\n        }\n        buffer.writeAscii(LOCAL_ENDPOINT_FIELD_NAME);\n        this.writeEndpoint(value.localEndpoint(), buffer, true);\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/encoder/span/AgentV2SpanRemoteEndpointWriter.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.report.encoder.span;\n\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport zipkin2.internal.WriteBuffer;\n\npublic class AgentV2SpanRemoteEndpointWriter extends AbstractAgentV2SpanEndpointWriter\n    implements WriteBuffer.Writer<ReportSpan> {\n    static final String REMOTE_ENDPOINT_FIELD_NAME = \",\\\"remoteEndpoint\\\":\";\n\n    @Override\n    public int sizeInBytes(ReportSpan value) {\n        if (value.remoteEndpoint() == null) {\n            return 0;\n        }\n        int size = REMOTE_ENDPOINT_FIELD_NAME.length();\n        size += this.endpointSizeInBytes(value.remoteEndpoint(), false);\n        return size;\n    }\n\n    @Override\n    public void write(ReportSpan value, WriteBuffer buffer) {\n        if (value.remoteEndpoint() == null) {\n            return;\n        }\n        buffer.writeAscii(REMOTE_ENDPOINT_FIELD_NAME);\n        this.writeEndpoint(value.remoteEndpoint(), buffer, false);\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/encoder/span/AgentV2SpanTagsWriter.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.report.encoder.span;\n\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport zipkin2.internal.JsonEscaper;\nimport zipkin2.internal.WriteBuffer;\n\nimport java.util.Iterator;\nimport java.util.Map;\n\npublic class AgentV2SpanTagsWriter implements WriteBuffer.Writer<ReportSpan> {\n    @Override\n    public int sizeInBytes(ReportSpan value) {\n        int sizeInBytes = 0;\n        if (!value.tags().isEmpty()) {\n            sizeInBytes += 10;\n            Iterator<Map.Entry<String, String>> i = value.tags().entrySet().iterator();\n            while (i.hasNext()) {\n                Map.Entry<String, String> entry = i.next();\n                sizeInBytes += 5;\n                sizeInBytes += JsonEscaper.jsonEscapedSizeInBytes(entry.getKey());\n                sizeInBytes += JsonEscaper.jsonEscapedSizeInBytes(entry.getValue());\n                if (i.hasNext()) {\n                    sizeInBytes += 1;\n                }\n            }\n        }\n        return sizeInBytes;\n    }\n\n    @Override\n    public void write(ReportSpan value, WriteBuffer b) {\n        if (!value.tags().isEmpty()) {\n            b.writeAscii(\",\\\"tags\\\":{\");\n            Iterator<Map.Entry<String, String>> i = value.tags().entrySet().iterator();\n            while (i.hasNext()) {\n                Map.Entry<String, String> entry = i.next();\n\n                b.writeByte('\\\"');\n                b.writeUtf8(JsonEscaper.jsonEscape(entry.getKey()));\n                b.writeAscii(\"\\\":\\\"\");\n                b.writeUtf8(JsonEscaper.jsonEscape(entry.getValue()));\n                b.writeByte('\\\"');\n                if (i.hasNext()) {\n                    b.writeByte(',');\n                }\n            }\n            b.writeByte('}');\n        }\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/encoder/span/AgentV2SpanWriter.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.report.encoder.span;\n\nimport com.google.common.collect.ImmutableList;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport org.apache.commons.lang3.mutable.MutableInt;\nimport zipkin2.internal.WriteBuffer;\n\nimport java.util.Collection;\n\npublic class AgentV2SpanWriter implements WriteBuffer.Writer<ReportSpan> {\n\n    public final Collection<WriteBuffer.Writer<ReportSpan>> writerList;\n\n    public AgentV2SpanWriter(GlobalExtrasSupplier extrasSupplier) {\n        writerList = ImmutableList.<WriteBuffer.Writer<ReportSpan>>builder()\n                .add(new AgentV2SpanBaseWriter())\n                .add(new AgentV2SpanLocalEndpointWriter())\n                .add(new AgentV2SpanRemoteEndpointWriter())\n                .add(new AgentV2SpanAnnotationsWriter())\n                .add(new AgentV2SpanTagsWriter())\n                .add(new AgentV2SpanGlobalWriter(\"log-tracing\", extrasSupplier))\n                .build();\n    }\n\n\n    public int sizeInBytes(ReportSpan value) {\n        final MutableInt size = new MutableInt(1);\n        writerList.forEach(w -> size.add(w.sizeInBytes(value)));\n        size.add(1);\n        return size.intValue();\n    }\n\n    @Override\n    public void write(ReportSpan value, WriteBuffer buffer) {\n        buffer.writeByte(123);\n        writerList.forEach(w -> w.write(value, buffer));\n        buffer.writeByte(125);\n    }\n\n    public String toString() {\n        return \"Span\";\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/encoder/span/GlobalExtrasSupplier.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.report.encoder.span;\n\npublic interface GlobalExtrasSupplier {\n    String service();\n\n    String system();\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/encoder/span/SpanJsonEncoder.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.report.encoder.span;\n\nimport com.google.auto.service.AutoService;\nimport com.megaease.easeagent.config.report.ReportConfigConst;\nimport com.megaease.easeagent.plugin.api.config.Config;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.report.ByteWrapper;\nimport com.megaease.easeagent.plugin.report.EncodedData;\nimport com.megaease.easeagent.plugin.report.Encoder;\nimport com.megaease.easeagent.plugin.report.encoder.JsonEncoder;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport com.megaease.easeagent.report.GlobalExtractor;\nimport zipkin2.internal.JsonCodec;\n\n@AutoService(Encoder.class)\n@SuppressWarnings(\"unused\")\npublic class SpanJsonEncoder extends JsonEncoder<ReportSpan> {\n    public static final String ENCODER_NAME = ReportConfigConst.SPAN_JSON_ENCODER_NAME;\n    AgentV2SpanWriter writer;\n\n    @Override\n    public void init(Config config) {\n        GlobalExtrasSupplier extrasSupplier = GlobalExtractor.getInstance(EaseAgent.getConfig());\n        writer = new AgentV2SpanWriter(extrasSupplier);\n    }\n\n    @Override\n    public String name() {\n        return ENCODER_NAME;\n    }\n\n    @Override\n    public int sizeInBytes(ReportSpan input) {\n        return writer.sizeInBytes(input);\n    }\n\n    @Override\n    public EncodedData encode(ReportSpan span) {\n        return new ByteWrapper(JsonCodec.write(writer, span));\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/encoder/span/okhttp/HttpSpanJsonEncoder.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.report.encoder.span.okhttp;\n\nimport com.google.auto.service.AutoService;\nimport com.megaease.easeagent.config.report.ReportConfigConst;\nimport com.megaease.easeagent.plugin.api.config.Config;\nimport com.megaease.easeagent.plugin.report.EncodedData;\nimport com.megaease.easeagent.plugin.report.Encoder;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport com.megaease.easeagent.report.encoder.span.SpanJsonEncoder;\n\nimport java.util.List;\n\n@AutoService(Encoder.class)\npublic class HttpSpanJsonEncoder implements Encoder<ReportSpan> {\n    public static final String ENCODER_NAME = ReportConfigConst.HTTP_SPAN_JSON_ENCODER_NAME;\n    SpanJsonEncoder encoder;\n\n    @Override\n    public void init(Config config) {\n        this.encoder = new SpanJsonEncoder();\n        this.encoder.init(config);\n    }\n\n    @Override\n    public int sizeInBytes(ReportSpan input) {\n        return this.encoder.sizeInBytes(input);\n    }\n\n    @Override\n    public EncodedData encode(ReportSpan input) {\n        return new OkHttpJsonRequestBody(this.encoder.encode(input).getData());\n    }\n\n    @Override\n    public String name() {\n        return ENCODER_NAME;\n    }\n\n    @Override\n    public EncodedData encodeList(List<EncodedData> encodedItems) {\n        EncodedData body = this.encoder.encodeList(encodedItems);\n        return new OkHttpJsonRequestBody(body.getData());\n    }\n\n    @Override\n    public int appendSizeInBytes(int newMsgSize) {\n        return this.encoder.appendSizeInBytes(newMsgSize);\n    }\n\n    @Override\n    public int packageSizeInBytes(List<Integer> sizes) {\n        return this.encoder.packageSizeInBytes(sizes);\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/encoder/span/okhttp/OkHttpJsonRequestBody.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.report.encoder.span.okhttp;\n\nimport com.megaease.easeagent.plugin.report.EncodedData;\nimport okhttp3.MediaType;\nimport okhttp3.RequestBody;\nimport okio.BufferedSink;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.IOException;\n\npublic class OkHttpJsonRequestBody extends RequestBody implements EncodedData {\n    static final MediaType CONTENT_TYPE = MediaType.parse(\"application/json\");\n\n    private final byte[] data;\n    private final int contentLength;\n\n    @Override\n    public int size() {\n        return this.contentLength;\n    }\n\n    @Override\n    public byte[] getData() {\n        return new byte[0];\n    }\n\n    public OkHttpJsonRequestBody(byte[] data) {\n        this.data = data;\n        this.contentLength = data.length;\n    }\n\n    @Nullable\n    @Override\n    public MediaType contentType() {\n        return CONTENT_TYPE;\n    }\n\n    @Override\n    public void writeTo(@NotNull BufferedSink sink) throws IOException {\n        sink.write(data);\n    }\n\n    @Override public long contentLength() {\n        return contentLength;\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/metric/MetricItem.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.report.metric;\n\npublic class MetricItem {\n    private final String key;\n    private final String content;\n\n    public MetricItem(String key, String content) {\n        this.key = key;\n        this.content = content;\n    }\n\n    public String getKey() {\n        return key;\n    }\n\n    public String getContent() {\n        return content;\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/metric/MetricProps.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.report.metric;\n\nimport com.megaease.easeagent.config.ConfigUtils;\nimport com.megaease.easeagent.config.Configs;\nimport com.megaease.easeagent.plugin.api.config.Config;\nimport com.megaease.easeagent.plugin.api.config.Const;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.utils.NoNull;\nimport com.megaease.easeagent.plugin.utils.common.StringUtils;\nimport com.megaease.easeagent.report.sender.AgentLoggerSender;\nimport com.megaease.easeagent.report.sender.metric.MetricKafkaSender;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static com.megaease.easeagent.config.report.ReportConfigAdapter.getDefaultAppender;\nimport static com.megaease.easeagent.config.report.ReportConfigConst.*;\n\npublic interface MetricProps {\n    String getName();\n\n    String getSenderPrefix();\n\n    String getSenderName();\n\n    String getTopic();\n\n    int getInterval();\n\n    boolean isEnabled();\n\n    Configs asReportConfig();\n\n    static MetricProps newDefault(IPluginConfig config, Config reportConfig) {\n        return new Default(reportConfig, config);\n    }\n\n    static MetricProps newDefault(Config reportConfig, String prefix) {\n        return new Default(reportConfig, prefix);\n    }\n\n    class Default implements MetricProps {\n        private volatile String senderName;\n        private final boolean enabled;\n\n        // for kafka sender\n        private final String topic;\n        private final String name;\n\n        private int interval;\n        private final Config config;\n        private final String senderPrefix;\n        private final String asyncPrefix;\n        private final Map<String, String> pluginConfigMap;\n\n        public Default(Config reportConfig, IPluginConfig pluginConfig) {\n            this.config = reportConfig;\n            this.name = pluginConfig.namespace();\n            this.senderPrefix = generatePrefix();\n            this.asyncPrefix = getAsyncPrefix(this.senderPrefix);\n\n            this.enabled = reportConfig.getBoolean(OUTPUT_SERVERS_ENABLE)\n                && reportConfig.getBoolean(METRIC_SENDER_ENABLED)\n                && reportConfig.getBoolean(join(this.senderPrefix, ENABLED_KEY));\n\n            // low priority: global level\n            Map<String, String> pCfg = ConfigUtils.extractByPrefix(reportConfig, REPORT);\n            pCfg.putAll(ConfigUtils.extractAndConvertPrefix(pCfg, METRIC_SENDER, senderPrefix));\n            pCfg.putAll(ConfigUtils.extractAndConvertPrefix(pCfg, METRIC_ENCODER, getEncoderKey(senderPrefix)));\n\n            this.senderName = NoNull.of(pCfg.get(join(senderPrefix, APPEND_TYPE_KEY)),\n                getDefaultAppender(reportConfig.getConfigs()));\n\n            this.topic = NoNull.of(pCfg.get(join(senderPrefix, TOPIC_KEY)), Const.METRIC_DEFAULT_TOPIC);\n\n            if (pCfg.get(join(asyncPrefix, INTERVAL_KEY)) != null) {\n                try {\n                    this.interval = Integer.parseInt(pCfg.get(join(asyncPrefix, INTERVAL_KEY)));\n                } catch (NumberFormatException e) {\n                    this.interval = Const.METRIC_DEFAULT_INTERVAL;\n                }\n            } else {\n                this.interval = Const.METRIC_DEFAULT_INTERVAL;\n            }\n            checkSenderName();\n            pCfg.put(join(senderPrefix, APPEND_TYPE_KEY), this.senderName);\n            pCfg.put(join(senderPrefix, LOG_APPENDER_KEY), this.name);\n            pCfg.put(join(asyncPrefix, INTERVAL_KEY), Integer.toString(this.interval));\n\n            this.pluginConfigMap = pCfg;\n        }\n\n        public Default(Config reportConfig, String prefix) {\n            this.config = reportConfig;\n            this.senderPrefix = prefix;\n            this.asyncPrefix = getAsyncPrefix(prefix);\n            this.name = this.config.getString(join(this.senderPrefix, LOG_APPENDER_KEY));\n            this.enabled = this.config.getBoolean(join(this.senderPrefix, ENABLED_KEY));\n            this.senderName = this.config.getString(join(this.senderPrefix, APPEND_TYPE_KEY));\n            this.topic = this.config.getString(join(this.senderPrefix, TOPIC_KEY));\n            this.interval = this.config.getInt(join(this.asyncPrefix, INTERVAL_KEY));\n\n            checkSenderName();\n            this.pluginConfigMap = new HashMap<>(this.config.getConfigs());\n        }\n\n        private void checkSenderName() {\n            if (\"kafka\".equals(this.senderName)) {\n                this.senderName = MetricKafkaSender.SENDER_NAME;\n            }\n\n            String bootstrapServers = this.config.getString(BOOTSTRAP_SERVERS);\n            if (StringUtils.isEmpty(bootstrapServers) && this.senderName.equals(MetricKafkaSender.SENDER_NAME)) {\n                this.senderName = AgentLoggerSender.SENDER_NAME;\n            }\n        }\n\n        @Override\n        public String getName() {\n            return this.name;\n        }\n\n        @Override\n        public String getSenderPrefix() {\n            return this.senderPrefix;\n        }\n\n        @Override\n        public String getSenderName() {\n            return this.senderName;\n        }\n\n        @Override\n        public String getTopic() {\n            return this.topic;\n        }\n\n        @Override\n        public boolean isEnabled() {\n            return this.enabled;\n        }\n\n        public Configs asReportConfig() {\n            // merge plugin and global config\n            Map<String, String> cfg = this.config.getConfigs();\n            cfg.putAll(this.pluginConfigMap);\n            return new Configs(cfg);\n        }\n\n        @Override\n        public int getInterval() {\n            return this.interval;\n        }\n\n        @Override\n        public int hashCode() {\n            return this.pluginConfigMap.hashCode();\n        }\n\n        @Override\n        public boolean equals(Object o) {\n            if (!(o instanceof Default)) {\n                return false;\n            }\n            Default other = (Default) o;\n            return this.pluginConfigMap.equals(other.pluginConfigMap);\n        }\n\n        private String generatePrefix() {\n            return \"reporter.metric.\" + this.name + \".sender\";\n        }\n\n        private static String getEncoderKey(String cfgPrefix) {\n            return StringUtils.replaceSuffix(cfgPrefix, ENCODER_KEY);\n        }\n\n        private static String getAsyncPrefix(String cfgPrefix) {\n            return StringUtils.replaceSuffix(cfgPrefix, ASYNC_KEY);\n        }\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/metric/MetricReporterFactoryImpl.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.report.metric;\n\nimport com.megaease.easeagent.plugin.api.Reporter;\nimport com.megaease.easeagent.plugin.api.config.ChangeItem;\nimport com.megaease.easeagent.plugin.api.config.Config;\nimport com.megaease.easeagent.plugin.api.config.ConfigChangeListener;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.report.ByteWrapper;\nimport com.megaease.easeagent.plugin.report.EncodedData;\nimport com.megaease.easeagent.plugin.report.metric.MetricReporterFactory;\nimport com.megaease.easeagent.report.plugin.ReporterRegistry;\nimport com.megaease.easeagent.report.sender.SenderWithEncoder;\nimport com.megaease.easeagent.report.util.Utils;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.io.IOException;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\n\n@Slf4j\npublic class MetricReporterFactoryImpl implements MetricReporterFactory, ConfigChangeListener {\n    private final ConcurrentHashMap<String, DefaultMetricReporter> reporters;\n    private final Config reportConfig;\n\n    public MetricReporterFactoryImpl(Config reportConfig) {\n        this.reporters = new ConcurrentHashMap<>();\n        this.reportConfig = reportConfig;\n        this.reportConfig.addChangeListener(this);\n    }\n\n    public static MetricReporterFactory create(Config reportConfig) {\n        return new MetricReporterFactoryImpl(reportConfig);\n    }\n\n    @Override\n    public Reporter reporter(IPluginConfig pluginConfig) {\n        DefaultMetricReporter reporter = reporters.get(pluginConfig.namespace());\n        if (reporter != null) {\n            return reporter;\n        }\n        synchronized (reporters) {\n            reporter = reporters.get(pluginConfig.namespace());\n            if (reporter != null) {\n                return reporter;\n            }\n            reporter = new DefaultMetricReporter(pluginConfig, this.reportConfig);\n            reporters.put(pluginConfig.namespace(), reporter);\n            return reporter;\n        }\n    }\n\n    @Override\n    public void onChange(List<ChangeItem> list) {\n        Map<String, String> changes = filterChanges(list);\n        if (changes.isEmpty()) {\n            return;\n        }\n        this.reportConfig.updateConfigs(changes);\n    }\n\n    private Map<String, String> filterChanges(List<ChangeItem> list) {\n        Map<String, String> cfg = new HashMap<>();\n        list.forEach(one -> cfg.put(one.getFullName(), one.getNewValue()));\n\n        return cfg;\n    }\n\n    public static class DefaultMetricReporter implements Reporter, ConfigChangeListener {\n        private MetricProps metricProps;\n        private SenderWithEncoder sender;\n        private final IPluginConfig pluginConfig;\n        private final Config reportConfig;\n        private final Config metricConfig;\n\n        public DefaultMetricReporter(IPluginConfig pluginConfig, Config reportConfig) {\n            this.pluginConfig = pluginConfig;\n\n            this.reportConfig = reportConfig;\n            this.reportConfig.addChangeListener(this);\n\n            this.metricProps = Utils.extractMetricProps(this.pluginConfig, reportConfig);\n            this.metricConfig = this.metricProps.asReportConfig();\n\n            this.sender = ReporterRegistry.getSender(this.metricProps.getSenderPrefix(), this.metricConfig);\n        }\n\n        public void report(String context) {\n            try {\n                sender.send(new ByteWrapper(context.getBytes())).execute();\n            } catch (IOException e) {\n                log.warn(\"send error. {}\", e.getMessage());\n            }\n        }\n\n        @Override\n        public void report(EncodedData encodedData) {\n            try {\n                sender.send(encodedData).execute();\n            } catch (IOException e) {\n                log.warn(\"send error. {}\", e.getMessage());\n            }\n        }\n\n        public Config getMetricConfig() {\n            return this.metricConfig;\n        }\n\n        public MetricProps getMetricProps() {\n            return this.metricProps;\n        }\n\n        public SenderWithEncoder getSender() {\n            return this.sender;\n        }\n\n        @Override\n        public void onChange(List<ChangeItem> list) {\n            if (list.isEmpty()) {\n                return;\n            }\n\n            String senderName = this.metricProps.getSenderName();\n            this.metricProps = Utils.extractMetricProps(pluginConfig, this.reportConfig);\n            Config changedConfig = this.metricProps.asReportConfig();\n\n            this.metricConfig.updateConfigs(changedConfig.getConfigs());\n\n            if (!metricProps.getSenderName().equals(senderName)) {\n                this.sender = ReporterRegistry.getSender(this.metricProps.getSenderPrefix(), this.metricConfig);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/plugin/NoOpCall.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.report.plugin;\n\nimport com.megaease.easeagent.plugin.report.Call;\n\nimport java.io.IOException;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\n\npublic class NoOpCall<V> implements Call<V> {\n    private static final Map<Class<?>, NoOpCall> INSTANCE_MAP = new ConcurrentHashMap<>();\n\n    @SuppressWarnings(\"unchecked\")\n    public static <T> NoOpCall<T> getInstance(Class<?> clazz) {\n        NoOpCall<T> b = INSTANCE_MAP.get(clazz);\n        if (b != null) {\n            return (NoOpCall<T>)b;\n        }\n        b = new NoOpCall<>();\n        INSTANCE_MAP.put(clazz, b);\n        return b;\n    }\n\n    @Override\n    public V execute() throws IOException {\n        return null;\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/plugin/NoOpEncoder.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.report.plugin;\n\nimport com.megaease.easeagent.plugin.api.config.Config;\nimport com.megaease.easeagent.plugin.report.ByteWrapper;\nimport com.megaease.easeagent.plugin.report.EncodedData;\nimport com.megaease.easeagent.plugin.report.Encoder;\n\nimport java.nio.charset.StandardCharsets;\nimport java.util.List;\n\npublic class NoOpEncoder<S> implements Encoder<S> {\n    public static final NoOpEncoder<?> INSTANCE = new NoOpEncoder<>();\n\n    @Override\n    public void init(Config config) {\n        // ignored\n    }\n\n    @Override\n    public String name() {\n        return \"noop\";\n    }\n\n    @Override\n    public int sizeInBytes(S input) {\n        return input.toString().length();\n    }\n\n    @Override\n    public EncodedData encode(S input) {\n        return new ByteWrapper(input.toString().getBytes(StandardCharsets.US_ASCII));\n    }\n\n    @Override\n    public EncodedData encodeList(List<EncodedData> encodedItems) {\n        StringBuilder sb = new StringBuilder();\n        encodedItems.forEach(sb::append);\n        return new ByteWrapper(sb.toString().getBytes(StandardCharsets.US_ASCII));\n    }\n\n    @Override\n    public int appendSizeInBytes(int newMsgSize) {\n        return newMsgSize;\n    }\n\n    @Override\n    public int packageSizeInBytes(List<Integer> sizes) {\n        return sizes.stream().mapToInt(s -> s).sum();\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/plugin/ReporterLoader.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.report.plugin;\n\nimport com.megaease.easeagent.plugin.report.Encoder;\nimport com.megaease.easeagent.plugin.report.Sender;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.lang.reflect.Constructor;\nimport java.lang.reflect.InvocationTargetException;\nimport java.util.ArrayList;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.ServiceLoader;\nimport java.util.function.Supplier;\n\n@SuppressWarnings(\"rawtypes\")\npublic class ReporterLoader {\n    static Logger logger = LoggerFactory.getLogger(ReporterLoader.class);\n\n    private ReporterLoader() {}\n\n    public static void load() {\n        encoderLoad();\n        senderLoad();\n    }\n\n    public static void encoderLoad() {\n        for (Encoder<?> encoder : load(Encoder.class)) {\n            try {\n                Constructor<? extends Encoder> constructor = encoder.getClass().getConstructor();\n                Supplier<Encoder<?>> encoderSupplier = () -> {\n                    try {\n                        return constructor.newInstance();\n                    } catch (InvocationTargetException | IllegalAccessException | InstantiationException e) {\n                        logger.warn(\"unable to load sender: {}\", encoder.name());\n                        return null;\n                    }\n                };\n                ReporterRegistry.registryEncoder(encoder.name(), encoderSupplier);\n            } catch (NoSuchMethodException e) {\n                    logger.warn(\"Sender load fail:{}\", e.getMessage());\n            }\n        }\n    }\n\n    public static void senderLoad() {\n        for (Sender sender : load(Sender.class)) {\n            try {\n                Constructor<? extends Sender> constructor = sender.getClass().getConstructor();\n                Supplier<Sender> senderSupplier = () -> {\n                    try {\n                        return constructor.newInstance();\n                    } catch (InvocationTargetException | IllegalAccessException | InstantiationException e) {\n                        logger.warn(\"unable to load sender: {}\", sender.name());\n                        return null;\n                    }\n                };\n                ReporterRegistry.registrySender(sender.name(), senderSupplier);\n            } catch (NoSuchMethodException e) {\n                logger.warn(\"Sender load fail:{}\", e.getMessage());\n            }\n        }\n    }\n\n    private static <T> List<T> load(Class<T> serviceClass) {\n        List<T> result = new ArrayList<>();\n        java.util.ServiceLoader<T> services = ServiceLoader.load(serviceClass);\n        for (Iterator<T> it = services.iterator(); it.hasNext(); ) {\n            try {\n                result.add(it.next());\n            } catch (UnsupportedClassVersionError e) {\n                logger.info(\"Unable to load class: {}\", e.getMessage());\n                logger.info(\"Please check the plugin compile Java version configuration,\"\n                    + \" and it should not latter than current JVM runtime\");\n            }\n        }\n        return result;\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/plugin/ReporterRegistry.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.report.plugin;\n\nimport com.megaease.easeagent.plugin.api.config.Config;\nimport com.megaease.easeagent.plugin.report.Encoder;\nimport com.megaease.easeagent.plugin.report.Sender;\nimport com.megaease.easeagent.report.sender.NoOpSender;\nimport com.megaease.easeagent.report.sender.SenderConfigDecorator;\nimport com.megaease.easeagent.report.sender.SenderWithEncoder;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.function.Supplier;\n\nimport static com.megaease.easeagent.config.report.ReportConfigConst.APPEND_TYPE_KEY;\nimport static com.megaease.easeagent.config.report.ReportConfigConst.join;\n\npublic class ReporterRegistry {\n    static Logger logger = LoggerFactory.getLogger(ReporterRegistry.class);\n\n    static ConcurrentHashMap<String, Supplier<Encoder<?>>> encoders = new ConcurrentHashMap<>();\n    static ConcurrentHashMap<String, Supplier<Sender>> senderSuppliers = new ConcurrentHashMap<>();\n\n    private ReporterRegistry() {}\n\n    public static void registryEncoder(String name, Supplier<Encoder<?>> encoder) {\n        Supplier<Encoder<?>> o = encoders.putIfAbsent(name, encoder);\n        if (o != null) {\n            String on = o.get().getClass().getSimpleName();\n            String cn = encoder.get().getClass().getSimpleName();\n            logger.error(\"Encoder name conflict:{}, between {} and {}\", name, on, cn);\n        }\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    public static <T> Encoder<T> getEncoder(String name) {\n        if (encoders.get(name) == null) {\n            logger.error(\"Encoder name \\\"{}\\\" is not exists!\", name);\n            return (Encoder<T>) NoOpEncoder.INSTANCE;\n        }\n        Encoder<T> encoder = (Encoder<T>)encoders.get(name).get();\n\n        if (encoder == null) {\n            return (Encoder<T>)NoOpEncoder.INSTANCE;\n        }\n\n        return encoder;\n    }\n\n    public static void registrySender(String name, Supplier<Sender> sender) {\n        Supplier<Sender> o = senderSuppliers.putIfAbsent(name, sender);\n        if (o != null) {\n            String on = o.get().getClass().getSimpleName();\n            String cn = sender.get().getClass().getSimpleName();\n            logger.error(\"Sender name conflict:{}, between {} and {}\", name, on, cn);\n        }\n    }\n\n    public static SenderWithEncoder getSender(String prefix, Config config) {\n        String name = config.getString(join(prefix, APPEND_TYPE_KEY));\n        if (name == null) {\n            logger.warn(\"Can not find sender name for:{}\", join(prefix, APPEND_TYPE_KEY));\n        }\n        SenderWithEncoder sender = new SenderConfigDecorator(prefix, getSender(name), config);\n        sender.init(config, prefix);\n        return sender;\n    }\n\n    private static Sender getSender(String name) {\n        Supplier<Sender> supplier = senderSuppliers.get(name);\n        if (supplier == null) {\n            return NoOpSender.INSTANCE;\n        }\n        return supplier.get();\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/sender/AgentKafkaSender.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.report.sender;\n\nimport com.google.auto.service.AutoService;\nimport com.megaease.easeagent.config.ConfigUtils;\nimport com.megaease.easeagent.plugin.api.config.Config;\nimport com.megaease.easeagent.plugin.report.Call;\nimport com.megaease.easeagent.plugin.report.EncodedData;\nimport com.megaease.easeagent.plugin.report.Sender;\nimport com.megaease.easeagent.plugin.utils.common.StringUtils;\nimport com.megaease.easeagent.report.plugin.NoOpCall;\nimport zipkin2.codec.Encoding;\nimport zipkin2.reporter.kafka11.KafkaSender;\nimport zipkin2.reporter.kafka11.SDKKafkaSender;\n\nimport java.io.IOException;\nimport java.util.Map;\n\nimport static com.megaease.easeagent.config.report.ReportConfigConst.*;\n\n@AutoService(Sender.class)\npublic class AgentKafkaSender implements Sender {\n    public static final String SENDER_NAME = KAFKA_SENDER_NAME;\n    private boolean enabled;\n    private Config config;\n\n    SDKKafkaSender sender;\n    Map<String, String> ssl;\n    String prefix;\n    String topicKey;\n    String topic;\n    String maxByteKey;\n\n    @Override\n    public String name() {\n        return SENDER_NAME;\n    }\n\n    @Override\n    public void init(Config config, String prefix) {\n        this.config = config;\n        this.prefix = prefix;\n        this.topicKey = join(this.prefix, TOPIC_KEY);\n        String outputServer = config.getString(BOOTSTRAP_SERVERS);\n        if (StringUtils.isEmpty(outputServer)) {\n            this.enabled = false;\n            return;\n        } else {\n            enabled = checkEnable(config);\n        }\n        this.topic = config.getString(this.topicKey);\n\n        this.maxByteKey = StringUtils.replaceSuffix(this.prefix, join(ASYNC_KEY, ASYNC_MSG_MAX_BYTES_KEY));\n        int msgMaxBytes = config.getInt(this.maxByteKey);\n        this.ssl = ConfigUtils.extractByPrefix(config, OUTPUT_SERVERS_SSL);\n\n        if (!enabled) {\n            return;\n        }\n        this.sender = SDKKafkaSender.wrap(KafkaSender.newBuilder()\n            .bootstrapServers(outputServer)\n            .topic(this.topic)\n            .overrides(ssl)\n            .encoding(Encoding.JSON)\n            .messageMaxBytes(msgMaxBytes)\n            .build());\n    }\n\n    @Override\n    public Call<Void> send(EncodedData encodedData) {\n        if (!enabled) {\n            return new NoOpCall<>();\n        }\n        zipkin2.Call<Void> call = this.sender.sendSpans(encodedData.getData());\n        return new ZipkinCallWrapper<>(call);\n    }\n\n    @Override\n    public boolean isAvailable() {\n        return (this.sender != null) && (!this.sender.isClose());\n    }\n\n    @Override\n    public void updateConfigs(Map<String, String> changes) {\n        String name = changes.get(join(prefix, APPEND_TYPE_KEY));\n        if (StringUtils.isNotEmpty(name) && !SENDER_NAME.equals(name)) {\n            try {\n                this.close();\n                return;\n            } catch (IOException e) {\n                // ignored\n            }\n        }\n        boolean refresh = false;\n        for (String key : changes.keySet()) {\n            if (key.startsWith(OUTPUT_SERVER_V2)\n                || key.startsWith(this.topicKey)) {\n                refresh = true;\n                break;\n            }\n        }\n\n        if (refresh) {\n            try {\n                if(this.sender!=null){\n                    this.sender.close();\n                }\n            } catch (IOException e) {\n                // ignored\n            }\n            this.config.updateConfigsNotNotify(changes);\n            this.init(this.config, this.prefix);\n        }\n    }\n\n    @Override\n    public void close() throws IOException {\n        if (this.sender != null) {\n            this.sender.close();\n        }\n    }\n\n    private boolean checkEnable(Config config) {\n        boolean check = config.getBoolean(join(this.prefix, ENABLED_KEY), true);\n        if (check) {\n            check = config.getBoolean(OUTPUT_SERVERS_ENABLE);\n        } else {\n            return false;\n        }\n        return check;\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/sender/AgentLoggerSender.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.report.sender;\n\nimport com.google.auto.service.AutoService;\nimport com.megaease.easeagent.config.report.ReportConfigConst;\nimport com.megaease.easeagent.log4j2.Logger;\nimport com.megaease.easeagent.log4j2.LoggerFactory;\nimport com.megaease.easeagent.plugin.api.config.Config;\nimport com.megaease.easeagent.plugin.report.Call;\nimport com.megaease.easeagent.plugin.report.Callback;\nimport com.megaease.easeagent.plugin.report.EncodedData;\nimport com.megaease.easeagent.plugin.report.Sender;\n\nimport java.io.IOException;\nimport java.util.Map;\n\n/**\n * Send span data to agent log\n * It will be printed to console, when the logger configuration to append to console\n */\n@AutoService(Sender.class)\npublic class AgentLoggerSender implements Sender {\n    public static final String SENDER_NAME = ReportConfigConst.CONSOLE_SENDER_NAME;\n    private static final Logger LOGGER = LoggerFactory.getLogger(AgentLoggerSender.class);\n    private String prefix;\n\n    @Override\n    public String name() {\n        return SENDER_NAME;\n    }\n\n    @Override\n    public void init(Config config, String prefix) {\n        // ignored\n        this.prefix = prefix;\n    }\n\n    @Override\n    public Call<Void> send(EncodedData encodedData) {\n        return new ConsoleCall(encodedData.getData());\n    }\n\n    @Override\n    public boolean isAvailable() {\n        return true;\n    }\n\n    @Override\n    public void updateConfigs(Map<String, String> changes) {\n        // ignored\n    }\n\n    @Override\n    public void close() throws IOException {\n        // ignored\n    }\n\n    static class ConsoleCall implements Call<Void> {\n        private final byte[] msg;\n\n        ConsoleCall(byte[] msg) {\n            this.msg = msg;\n        }\n\n        @Override\n        public Void execute() throws IOException {\n            LOGGER.info(\"{}\", new String(msg));\n            return null;\n        }\n\n        @Override\n        public void enqueue(Callback<Void> cb) {\n            LOGGER.debug(\"{}\", new String(msg));\n        }\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/sender/NoOpSender.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.report.sender;\n\nimport com.google.auto.service.AutoService;\nimport com.megaease.easeagent.config.report.ReportConfigConst;\nimport com.megaease.easeagent.plugin.api.config.Config;\nimport com.megaease.easeagent.plugin.report.Call;\nimport com.megaease.easeagent.plugin.report.EncodedData;\nimport com.megaease.easeagent.plugin.report.Sender;\nimport com.megaease.easeagent.report.plugin.NoOpCall;\n\nimport java.io.IOException;\nimport java.util.Map;\n\n@AutoService(Sender.class)\npublic class NoOpSender implements Sender {\n    public static final NoOpSender INSTANCE = new NoOpSender();\n\n    @Override\n    public String name() {\n        return ReportConfigConst.NOOP_SENDER_NAME;\n    }\n\n    @Override\n    public void init(Config config, String prefix) {\n        // ignored\n    }\n\n    @Override\n    public Call<Void> send(EncodedData encodedData) {\n        return NoOpCall.getInstance(NoOpSender.class);\n    }\n\n    @Override\n    public boolean isAvailable() {\n        return true;\n    }\n\n    @Override\n    public void updateConfigs(Map<String, String> changes) {\n        // ignored\n    }\n\n    @Override\n    public void close() throws IOException {\n        // ignored\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/sender/SenderConfigDecorator.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.report.sender;\n\nimport com.megaease.easeagent.config.Configs;\nimport com.megaease.easeagent.plugin.api.config.ChangeItem;\nimport com.megaease.easeagent.plugin.api.config.Config;\nimport com.megaease.easeagent.plugin.api.config.ConfigChangeListener;\nimport com.megaease.easeagent.plugin.report.*;\nimport com.megaease.easeagent.report.plugin.ReporterRegistry;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.io.IOException;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.TreeMap;\n\nimport static com.megaease.easeagent.config.ConfigUtils.extractByPrefix;\nimport static com.megaease.easeagent.config.report.ReportConfigConst.*;\n\n@Slf4j\npublic class SenderConfigDecorator\n    implements SenderWithEncoder, ConfigChangeListener {\n\n    protected Sender sender;\n    String prefix;\n    Config senderConfig;\n    Config packerConfig;\n    String encoderKey;\n    Encoder<?> packer;\n\n    public SenderConfigDecorator(String prefix, Sender sender, Config config) {\n        this.sender = sender;\n        this.prefix = prefix;\n        this.encoderKey = getEncoderKey(prefix);\n        config.addChangeListener(this);\n        this.senderConfig = new Configs(extractSenderConfig(this.prefix, config));\n        this.packerConfig = new Configs(extractSenderConfig(encoderKey, config));\n    }\n\n    @Override\n    public String name() {\n        return sender.name();\n    }\n\n    @Override\n    public String getPrefix() {\n        return this.prefix;\n    }\n\n    @Override\n    public void init(Config config, String prefix) {\n        this.packer = ReporterRegistry.getEncoder(config.getString(this.encoderKey));\n        this.packer.init(this.packerConfig);\n        this.sender.init(this.senderConfig, prefix);\n    }\n\n    @Override\n    public Call<Void> send(EncodedData encodedData) {\n        return this.sender.send(encodedData);\n    }\n\n    @Override\n    public Call<Void> send(List<EncodedData> encodedData) {\n        EncodedData data = this.packer.encodeList(encodedData);\n        if (log.isDebugEnabled()) {\n            log.debug(new String(data.getData()));\n        }\n        return send(data);\n    }\n\n    @Override\n    public boolean isAvailable() {\n        return sender.isAvailable();\n    }\n\n    @Override\n    public void updateConfigs(Map<String, String> changes) {\n        String name = changes.get(join(this.prefix, APPEND_TYPE_KEY));\n        if (name == null || name.equals(name())) {\n            this.sender.updateConfigs(changes);\n        } else {\n            try {\n                this.sender.close();\n            } catch (IOException e) {\n                log.warn(\"Sender update fail, can not close sender:{}\", this.sender.name());\n            }\n        }\n    }\n\n    // checkEncoder update\n    protected void updateEncoder(Map<String, String> changes) {\n        String name = changes.get(this.encoderKey);\n        if (name == null || name.equals(this.packer.name())) {\n            return;\n        }\n        this.packer = ReporterRegistry.getEncoder(packerConfig.getString(this.encoderKey));\n        this.packer.init(packerConfig);\n    }\n\n    @Override\n    public void close() throws IOException {\n        sender.close();\n    }\n\n    @Override\n    public void onChange(List<ChangeItem> list) {\n        Map<String, String> changes = filterChanges(list);\n        if (changes.isEmpty()) {\n            return;\n        }\n        Map<String, String> senderChanges = new TreeMap<>();\n        Map<String, String> packerChanges = new TreeMap<>();\n\n        changes.forEach((key, value) -> {\n            if (key.startsWith(encoderKey)) {\n                packerChanges.put(key, value);\n            } else {\n                senderChanges.put(key, value);\n            }\n        });\n\n        if (!packerChanges.isEmpty()) {\n            this.packerConfig.updateConfigs(packerChanges);\n            this.updateEncoder(packerChanges);\n        }\n\n        if (!senderChanges.isEmpty()) {\n            this.senderConfig.updateConfigs(senderChanges);\n            this.updateConfigs(senderChanges);\n        }\n    }\n\n    private static String getEncoderKey(String cfgPrefix) {\n        int idx = cfgPrefix.lastIndexOf('.');\n        if (idx == 0) {\n            return ENCODER_KEY;\n        } else {\n            return cfgPrefix.substring(0, idx + 1) + ENCODER_KEY;\n        }\n    }\n\n    private static Map<String, String> extractSenderConfig(String cfgPrefix, Config config) {\n        Map<String, String> extract = extractByPrefix(config, cfgPrefix);\n        Map<String, String> cfg = new HashMap<>(extract);\n\n        // outputServer config\n        cfg.putAll(extractByPrefix(config, REPORT));\n\n        return cfg;\n    }\n\n    private Map<String, String> filterChanges(List<ChangeItem> list) {\n        Map<String, String> cfg = new HashMap<>();\n        list.stream()\n            .filter(one -> {\n                String name = one.getFullName();\n                return name.startsWith(REPORT)\n                    || name.startsWith(encoderKey)\n                    || name.startsWith(prefix);\n            }).forEach(one -> cfg.put(one.getFullName(), one.getNewValue()));\n\n        return cfg;\n    }\n\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public <T> Encoder<T> getEncoder() {\n        return (Encoder<T>)this.packer;\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/sender/SenderWithEncoder.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.report.sender;\n\nimport com.megaease.easeagent.plugin.report.Call;\nimport com.megaease.easeagent.plugin.report.EncodedData;\nimport com.megaease.easeagent.plugin.report.Encoder;\nimport com.megaease.easeagent.plugin.report.Sender;\n\nimport java.util.List;\n\npublic interface SenderWithEncoder extends Sender {\n    <T> Encoder<T> getEncoder();\n\n    /**\n     * Sends a list of encoded data to a transport such as http or Kafka.\n     *\n     * @param encodedData list of encoded data, such as encoded spans.\n     * @throws IllegalStateException if {@link #close() close} was called.\n     */\n    Call<Void> send(List<EncodedData> encodedData);\n\n\n    /**\n     * return sender prefix, eg.tracing sender: reporter.tracing.sender\n     * @return  sender prefix\n     */\n    String getPrefix();\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/sender/ZipkinCallWrapper.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.report.sender;\n\nimport com.megaease.easeagent.plugin.report.Call;\nimport com.megaease.easeagent.plugin.report.Callback;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.io.IOException;\n\n@Slf4j\npublic class ZipkinCallWrapper<V> implements Call<V> {\n    private final zipkin2.Call<V> call;\n\n    public ZipkinCallWrapper(zipkin2.Call<V> call) {\n        this.call = call;\n    }\n\n    @Override\n    public V execute() throws IOException {\n        try {\n            return call.execute();\n        } catch (Exception e) {\n            log.warn(\"Call exception: {}\", e.getMessage());\n            throw new IOException();\n        }\n    }\n\n    @Override\n    public void enqueue(Callback<V> cb) {\n        zipkin2.Callback<V> zCb = new ZipkinCallbackWrapper<>(cb);\n        this.call.enqueue(zCb);\n    }\n\n    static class ZipkinCallbackWrapper<V> implements zipkin2.Callback<V> {\n        final Callback<V> delegate;\n\n        ZipkinCallbackWrapper(Callback<V> cb) {\n            this.delegate = cb;\n        }\n\n        @Override\n        public void onSuccess(V value) {\n            this.delegate.onSuccess(value);\n        }\n\n        @Override\n        public void onError(Throwable t) {\n            this.delegate.onError(t);\n        }\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/sender/metric/KeySender.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.report.sender.metric;\n\nimport com.megaease.easeagent.report.metric.MetricProps;\nimport com.megaease.easeagent.report.sender.metric.log4j.AppenderManager;\nimport com.megaease.easeagent.report.sender.metric.log4j.LoggerFactory;\nimport com.megaease.easeagent.report.sender.metric.log4j.RefreshableAppender;\nimport org.apache.logging.log4j.core.Logger;\n\npublic class KeySender {\n    private static final String CONSOLE_APPEND = \"console\";\n    private final String key;\n    private final AppenderManager appenderManager;\n    private final MetricProps metricProps;\n    private Logger logger;\n    private org.slf4j.Logger consoleLogger;\n    private boolean isConsole = false;\n\n    public KeySender(String key, AppenderManager appenderManager, MetricProps metricProps) {\n        this.key = key;\n        this.appenderManager = appenderManager;\n        this.metricProps = metricProps;\n    }\n\n    public void send(String content) {\n        this.lazyInitLogger();\n        if (this.isConsole) {\n            this.consoleLogger.info(content);\n        } else {\n            this.logger.info(content);\n        }\n    }\n\n    private void lazyInitLogger() {\n        if (logger != null) {\n            return;\n        }\n\n        String loggerName = prepareAppenderAndLogger();\n        if (metricProps.getSenderName().equals(CONSOLE_APPEND)) {\n            this.isConsole = true;\n            this.consoleLogger = org.slf4j.LoggerFactory.getLogger(loggerName);\n        } else {\n            logger = LoggerFactory.getLoggerContext().getLogger(loggerName);\n        }\n    }\n\n    private String prepareAppenderAndLogger() {\n        RefreshableAppender build = RefreshableAppender.builder()\n                .names(this.key)\n                .metricProps(this.metricProps)\n                .appenderManager(this.appenderManager)\n                .build();\n        return build.getLogger();\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/sender/metric/MetricKafkaSender.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.report.sender.metric;\n\nimport com.google.auto.service.AutoService;\nimport com.megaease.easeagent.config.GlobalConfigs;\nimport com.megaease.easeagent.config.report.ReportConfigConst;\nimport com.megaease.easeagent.plugin.api.config.Config;\nimport com.megaease.easeagent.plugin.report.Call;\nimport com.megaease.easeagent.plugin.report.EncodedData;\nimport com.megaease.easeagent.plugin.report.Sender;\nimport com.megaease.easeagent.report.OutputProperties;\nimport com.megaease.easeagent.report.metric.MetricProps;\nimport com.megaease.easeagent.report.plugin.NoOpCall;\nimport com.megaease.easeagent.report.sender.metric.log4j.AppenderManager;\nimport com.megaease.easeagent.report.sender.metric.log4j.LoggerFactory;\nimport com.megaease.easeagent.report.sender.metric.log4j.RefreshableAppender;\nimport com.megaease.easeagent.report.util.Utils;\nimport org.apache.logging.log4j.core.Logger;\n\nimport java.io.IOException;\nimport java.util.Map;\n\n@AutoService(Sender.class)\npublic class MetricKafkaSender implements Sender {\n    public static final String SENDER_NAME = ReportConfigConst.METRIC_KAFKA_SENDER_NAME;\n    private static volatile AppenderManager appenderManager;\n\n    private OutputProperties outputProperties;\n    private MetricProps props;\n    private Logger logger;\n\n    private String prefix;\n\n    @Override\n    public String name() {\n        return SENDER_NAME;\n    }\n\n    @Override\n    public void init(Config config, String prefix) {\n        this.prefix = prefix;\n        this.outputProperties = Utils.extractOutputProperties(config);\n        this.props = MetricProps.newDefault(config, prefix);\n        initAppenderManager();\n    }\n\n    @Override\n    public Call<Void> send(EncodedData encodedData) {\n        lazyInitLogger();\n        String msg = new String(encodedData.getData());\n        logger.info(msg);\n        return new NoOpCall<>();\n    }\n\n    @Override\n    public boolean isAvailable() {\n        return true;\n    }\n\n    @Override\n    public void updateConfigs(Map<String, String> changes) {\n        if (Utils.isOutputPropertiesChange(changes)\n            && this.outputProperties.updateConfig(changes)) {\n            appenderManager.refresh();\n        }\n        // check topic\n        Map<String, String> cfg = this.props.asReportConfig().getConfigs();\n        cfg.putAll(changes);\n        MetricProps nProps = MetricProps.newDefault(new GlobalConfigs(cfg), this.prefix);\n        if (!nProps.getTopic().equals(this.props.getTopic())) {\n            try {\n                this.close();\n            } catch (IOException e) {\n                // ignored\n            }\n            this.props = nProps;\n            this.logger = null;\n            lazyInitLogger();\n        }\n        // check enabled\n    }\n\n    @Override\n    public void close() throws IOException {\n        appenderManager.stop(this.props.getTopic());\n    }\n\n    private void initAppenderManager() {\n        if (appenderManager != null) {\n            return;\n        }\n        synchronized (MetricKafkaSender.class) {\n            if (appenderManager != null) {\n                return;\n            }\n            appenderManager = AppenderManager.create(this.outputProperties);\n        }\n    }\n\n    private void lazyInitLogger() {\n        if (logger != null) {\n            return;\n        }\n        String loggerName = prepareAppenderAndLogger();\n        logger = LoggerFactory.getLoggerContext().getLogger(loggerName);\n    }\n\n    private String prepareAppenderAndLogger() {\n        RefreshableAppender build = RefreshableAppender.builder()\n            .names(this.props.getName())\n            .metricProps(this.props)\n            .appenderManager(appenderManager)\n            .build();\n        return build.getLogger();\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/sender/metric/log4j/AppenderManager.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.report.sender.metric.log4j;\n\nimport com.google.common.collect.ImmutableList;\nimport com.megaease.easeagent.log4j2.Logger;\nimport com.megaease.easeagent.report.OutputProperties;\nimport org.apache.commons.lang3.RandomStringUtils;\nimport org.apache.commons.lang3.StringUtils;\nimport org.apache.kafka.clients.CommonClientConfigs;\nimport org.apache.kafka.clients.producer.ProducerConfig;\nimport org.apache.kafka.common.config.SslConfigs;\nimport org.apache.kafka.common.security.auth.SecurityProtocol;\nimport org.apache.logging.log4j.core.Appender;\nimport org.apache.logging.log4j.core.LoggerContext;\nimport org.apache.logging.log4j.core.appender.NullAppender;\nimport org.apache.logging.log4j.core.appender.mom.kafka.KafkaAppender;\nimport org.apache.logging.log4j.core.config.Property;\nimport org.apache.logging.log4j.core.layout.PatternLayout;\n\nimport java.nio.charset.StandardCharsets;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.function.Function;\n\n/**\n * Manage kafka's log4j appender according topics\n *\n * @author Kun Zhao\n * @version v1.0.2\n * @since V1.0.2\n */\npublic interface AppenderManager {\n    Appender appender(String topic);\n\n    void stop(String topic);\n\n    void refresh();\n\n    static AppenderManager create(OutputProperties outputProperties) {\n        return new DefaultKafkaAppenderManager(outputProperties);\n    }\n\n    static AppenderManager create(Function<String, Appender> provider) {\n        return new DefaultKafkaAppenderManager(null, provider);\n    }\n\n    final class DefaultKafkaAppenderManager implements AppenderManager {\n\n        public static final Logger LOGGER = com.megaease.easeagent.log4j2.LoggerFactory.getLogger(DefaultKafkaAppenderManager.class);\n\n        private Map<String, Appender> appenderMap = new ConcurrentHashMap<>();\n        private final OutputProperties outputProperties;\n        final LoggerContext context = LoggerFactory.getLoggerContext();\n        Function<String, Appender> provider;\n\n        private DefaultKafkaAppenderManager(OutputProperties outputProperties) {\n            this.outputProperties = outputProperties;\n            ClassLoader initClassLoader = Thread.currentThread().getContextClassLoader();\n            LOGGER.info(\"bind classloader:{} to AppenderManager\", initClassLoader);\n            this.provider = topic -> {\n                ClassLoader old = Thread.currentThread().getContextClassLoader();\n                Thread.currentThread().setContextClassLoader(initClassLoader);\n                try {\n                    return this.newAppender(this.outputProperties, topic);\n                } finally {\n                    Thread.currentThread().setContextClassLoader(old);\n                }\n            };\n        }\n\n        private DefaultKafkaAppenderManager(OutputProperties outputProperties, Function<String, Appender> provider) {\n            this.outputProperties = outputProperties;\n            this.provider = provider;\n        }\n\n        @Override\n        public Appender appender(String topic) {\n            return appenderMap.computeIfAbsent(topic, this.provider);\n        }\n\n        @Override\n        public void stop(String topic) {\n            Appender appender = appenderMap.remove(topic);\n            if (appender != null) {\n                appender.stop();\n            }\n        }\n\n        private Appender newAppender(OutputProperties outputProperties, String topic) {\n            if (!Boolean.TRUE.equals(outputProperties.isEnabled())) {\n                String s = RandomStringUtils.randomAscii(8);\n                return NullAppender.createAppender(topic + \"_kafka_disabled_\" + s);\n            }\n            if (StringUtils.isEmpty(outputProperties.getServers())) {\n                return null;\n            }\n            try {\n                String s = RandomStringUtils.randomAscii(8);\n\n                List<Property> propertyList = new ArrayList<>();\n                propertyList.add(Property.createProperty(\"bootstrap.servers\", outputProperties.getServers()));\n                propertyList.add(Property.createProperty(\"timeout.ms\", outputProperties.getTimeout()));\n                propertyList.add(Property.createProperty(\"acks\", \"0\"));\n                Property.createProperty(ProducerConfig.CLIENT_ID_CONFIG, \"producer_\" + topic + s);\n                if (SecurityProtocol.SSL.name.equals(outputProperties.getSecurityProtocol())) {\n                    propertyList.add(Property.createProperty(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, outputProperties.getSecurityProtocol()));\n                    propertyList.add(Property.createProperty(SslConfigs.SSL_KEYSTORE_TYPE_CONFIG, outputProperties.getSSLKeyStoreType()));\n                    propertyList.add(Property.createProperty(SslConfigs.SSL_KEYSTORE_KEY_CONFIG, outputProperties.getKeyStoreKey()));\n                    propertyList.add(Property.createProperty(SslConfigs.SSL_KEYSTORE_CERTIFICATE_CHAIN_CONFIG, outputProperties.getKeyStoreCertChain()));\n                    propertyList.add(Property.createProperty(SslConfigs.SSL_TRUSTSTORE_CERTIFICATES_CONFIG, outputProperties.getTrustCertificate()));\n                    propertyList.add(Property.createProperty(SslConfigs.SSL_TRUSTSTORE_TYPE_CONFIG, outputProperties.getTrustCertificateType()));\n                    propertyList.add(Property.createProperty(SslConfigs.SSL_ENDPOINT_IDENTIFICATION_ALGORITHM_CONFIG, outputProperties.getEndpointAlgorithm()));\n                }\n\n                Property[] properties = new Property[propertyList.size()];\n                propertyList.toArray(properties);\n                Appender appender = KafkaAppender.newBuilder()\n                    .setTopic(topic)\n                    .setSyncSend(false)\n                    .setName(topic + \"_kafka_\" + s)\n                    .setPropertyArray(properties)\n                    .setLayout(PatternLayout.newBuilder()\n                        .withCharset(StandardCharsets.UTF_8)\n                        .withConfiguration(context.getConfiguration())\n                        .withPattern(\"%m%n\").build())\n                    .setConfiguration(context.getConfiguration())\n                    .build();\n                appender.start();\n                return appender;\n            } catch (Exception e) {\n                LOGGER.warn(\"can't not create topic :\" + topic + \" kafka appender , error :\", e.getMessage(), e);\n            }\n            return null;\n        }\n\n        @Override\n        public void refresh() {\n            Map<String, Appender> clearMap = this.appenderMap;\n            this.appenderMap = new ConcurrentHashMap<>();\n            ImmutableList<Appender> appenderList = ImmutableList.copyOf(clearMap.values());\n            for (Appender a : appenderList) {\n                try {\n                    a.stop();\n                } catch (Exception e) {\n                    //\n                }\n            }\n            clearMap.clear();\n        }\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/sender/metric/log4j/LoggerFactory.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.report.sender.metric.log4j;\n\nimport org.apache.logging.log4j.core.LoggerContext;\n\npublic class LoggerFactory {\n    private LoggerFactory() {}\n\n    // Independent logger context as an anchor for loggers in the metrics\n    private static final LoggerContext loggerContext = new LoggerContext(\"ROOT\");\n\n    public static LoggerContext getLoggerContext() {\n        return loggerContext;\n    }\n\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/sender/metric/log4j/MetricRefreshableAppender.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.report.sender.metric.log4j;\n\nimport com.megaease.easeagent.report.metric.MetricProps;\nimport org.apache.logging.log4j.core.Appender;\nimport org.apache.logging.log4j.core.LogEvent;\nimport org.apache.logging.log4j.core.appender.AbstractAppender;\nimport org.apache.logging.log4j.core.appender.ConsoleAppender;\nimport org.apache.logging.log4j.core.appender.mom.kafka.KafkaAppender;\nimport org.apache.logging.log4j.core.config.Configuration;\nimport org.apache.logging.log4j.core.layout.PatternLayout;\n\nimport java.util.Optional;\nimport java.util.function.Consumer;\n\n/**\n * {@code MetricRefreshableAppender} is a lazy kafka appender  and can\n * be refreshed when kafka topic or bootstrap servers' address changed.\n * It has a {@link ConsoleAppender} and mock appender, when appender's\n * type is changed, it will not close kafka appender or create new\n * console appender. The default {@link ConsoleAppender} will be reused.\n * <p/>\n * We introduce this class for avoiding rebuild {@link KafkaAppender}\n * frequently when configuration changed the. KafkaAppender should be\n * rebuild only when the bootstrap server's address or topic changed.\n *\n * @author Kun Zhao\n * @version v1.0.1\n * @since v1.0.1\n */\npublic class MetricRefreshableAppender extends AbstractAppender implements TestableAppender {\n\n    private final MetricProps metricProps;\n    private final Configuration configuration;\n    Consumer<LogEvent> logEventConsumer;\n    private Appender console;\n    private Appender mock;\n    private final AppenderManager appenderManager;\n\n    MetricRefreshableAppender(final String name,\n                              final MetricProps metricProps,\n                              final Configuration configuration,\n                              final AppenderManager appenderManager\n    ) {\n        super(name, null, null, true, null);\n        this.metricProps = metricProps;\n        this.appenderManager = appenderManager;\n        this.configuration = configuration;\n        this.getConsoleAppender();\n    }\n\n\n    @Override\n    public void append(LogEvent event) {\n        if (!metricProps.isEnabled()) {\n            return;\n        }\n        Optional.ofNullable(getAppender()).ifPresent(a -> a.append(event));\n    }\n\n    private Appender getAppender() {\n        switch (metricProps.getSenderName()) {\n            case \"mock\":\n                return getMockAppender();\n            default:\n                return getKafkaAppender(metricProps.getTopic());\n        }\n    }\n\n    private Appender getKafkaAppender(String topic) {\n        return Optional.ofNullable(appenderManager.appender(topic)).orElse(getConsoleAppender());\n    }\n\n    private Appender getConsoleAppender() {\n        if (console != null) {\n            return console;\n        }\n\n        console = ConsoleAppender.newBuilder()\n                .setConfiguration(configuration)\n                .setLayout(this.getLayout())\n                .setName(this.getName() + \"_console\")\n                .build();\n        console.start();\n        return console;\n    }\n\n\n    @Override\n    public void setTestAppender(Consumer<LogEvent> consumer) {\n        this.logEventConsumer = consumer;\n    }\n\n    private Appender getMockAppender() {\n        if (mock != null) {\n            return mock;\n        }\n        mock = new AbstractAppender(this.getName() + \"_mock\", null, PatternLayout.createDefaultLayout(), true, null) {\n            @Override\n            public void append(LogEvent event) {\n                Optional.ofNullable(logEventConsumer).ifPresent(l -> l.accept(event));\n            }\n        };\n        return mock;\n    }\n\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/sender/metric/log4j/RefreshableAppender.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.report.sender.metric.log4j;\n\nimport com.megaease.easeagent.log4j2.Logger;\nimport com.megaease.easeagent.report.metric.MetricProps;\nimport com.megaease.easeagent.report.util.TextUtils;\nimport org.apache.logging.log4j.Level;\nimport org.apache.logging.log4j.core.LogEvent;\nimport org.apache.logging.log4j.core.LoggerContext;\nimport org.apache.logging.log4j.core.async.AsyncLoggerConfig;\nimport org.apache.logging.log4j.core.async.AsyncLoggerConfigDisruptor;\nimport org.apache.logging.log4j.core.config.AppenderRef;\nimport org.apache.logging.log4j.core.config.LoggerConfig;\n\nimport java.util.function.Consumer;\n\n/**\n * The {@link org.apache.logging.log4j.core.Appender} is log4j object which is\n * responsible for delivering LogEvents to their destination.\n * RefreshableAppender is dedicated to maintaining the\n * {@link org.apache.logging.log4j.core.Appender} for hot updates.\n * RefreshableAppender provides implementations that include update\n * {@link org.apache.logging.log4j.core.Appender} Appender's type, Kafka\n * bootstrap server address, Kafka topic, etc dynamically.\n *\n * @author wanglei\n * @version v1.0.1\n * @since 2020/2/23 09:38\n */\npublic interface RefreshableAppender extends TestableAppender {\n\n    static Builder builder() {\n        return new Builder();\n    }\n\n    /**\n     * Fetching logger name corresponded to current\n     * {@code org.apache.logging.log4j.core.Appender}\n     *\n     * @return a name of logger\n     */\n    String getLogger();\n\n\n    class DefaultRefreshableAppender implements RefreshableAppender {\n\n        private static final Logger LOGGER = com.megaease.easeagent.log4j2.LoggerFactory.getLogger(DefaultRefreshableAppender.class);\n\n        private final String loggerName;\n        private final String appenderName;\n        protected final MetricRefreshableAppender delegate;\n        private final AppenderManager appenderManager;\n\n        DefaultRefreshableAppender(\n            String appender,\n            String loggerName,\n            AppenderManager appenderManager,\n            MetricProps metricProps) {\n            this.loggerName = loggerName;\n            this.appenderName = appender;\n            this.appenderManager = appenderManager;\n            LoggerContext context = LoggerFactory.getLoggerContext();\n            // start disruptor synchronized thread\n            startAsyncDisruptor(context);\n            AppenderRef[] appenderRefs = forAppenderRefs();\n            LoggerConfig logger = createLogger(loggerName, context, appenderRefs);\n            delegate = newDelegate(context, metricProps);\n            if (delegate != null) {\n                //shouldn't be null always\n                logger.addAppender(delegate, Level.INFO, null);\n            }\n            context.getConfiguration().addLogger(loggerName, logger);\n            context.updateLoggers();\n        }\n\n        private void startAsyncDisruptor(LoggerContext context) {\n            AsyncLoggerConfigDisruptor asyncLoggerConfigDelegate = (AsyncLoggerConfigDisruptor) context.getConfiguration().getAsyncLoggerConfigDelegate();\n            if (!asyncLoggerConfigDelegate.isStarted() && !asyncLoggerConfigDelegate.isStarting()) {\n                asyncLoggerConfigDelegate.start();\n            }\n        }\n\n        private AppenderRef[] forAppenderRefs() {\n            return new AppenderRef[]{AppenderRef.createAppenderRef(appenderName, Level.INFO, null)};\n        }\n\n        private LoggerConfig createLogger(String loggerName, LoggerContext ctx, AppenderRef[] refs) {\n            return AsyncLoggerConfig.createLogger(false, Level.INFO, loggerName,\n                \"true\", refs, null, ctx.getConfiguration(), null);\n        }\n\n        private MetricRefreshableAppender newDelegate(LoggerContext context, MetricProps metricProps) {\n            try {\n                MetricRefreshableAppender metricRefreshableAppender = new MetricRefreshableAppender(this.appenderName,\n                    metricProps,\n                    context.getConfiguration(),\n                    appenderManager);\n                metricRefreshableAppender.start();\n                return metricRefreshableAppender;\n            } catch (Exception e) {\n                LOGGER.warn(\"new refreshable appender failed + [\" + e.getMessage() + \"]\");\n            }\n            return null;\n        }\n\n        @Override\n        public String getLogger() {\n            return loggerName;\n        }\n\n        @Override\n        public void setTestAppender(Consumer<LogEvent> logEventConsumer) {\n            this.delegate.setTestAppender(logEventConsumer);\n        }\n    }\n\n    class Builder {\n        private String appender;\n        private String loggerName;\n        private AppenderManager appenderManager;\n        private MetricProps metricProps;\n\n        public Builder names(String prefix) {\n            if (TextUtils.isEmpty(prefix)) {\n                prefix = \"DefaultPrefix\";\n            }\n            this.appender = prefix.concat(\"MetricAppender\");\n            this.loggerName = prefix;\n            return this;\n        }\n\n\n        public Builder appenderManager(AppenderManager manager) {\n            this.appenderManager = manager;\n            return this;\n        }\n\n        public Builder metricProps(MetricProps metricProps) {\n            this.metricProps = metricProps;\n            return this;\n        }\n\n        public RefreshableAppender build() {\n            if (TextUtils.isEmpty(appender) || TextUtils.isEmpty(loggerName) || appenderManager == null) {\n                throw new IllegalArgumentException(\"appender, loggerName must be a unique name, kafkaAppenderManager can't be null\");\n            }\n            return new DefaultRefreshableAppender(\n                appender,\n                loggerName,\n                appenderManager, metricProps);\n        }\n    }\n\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/sender/metric/log4j/TestableAppender.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.report.sender.metric.log4j;\n\nimport org.apache.logging.log4j.core.LogEvent;\n\nimport java.util.function.Consumer;\n\npublic interface TestableAppender {\n    /**\n     * Add a LogEvent consumer which was wrapped as a appender to logger, <strong>test only</strong>\n     *\n     * @param logEventConsumer a Consumer for consumer {@link LogEvent}\n     */\n    void setTestAppender(Consumer<LogEvent> logEventConsumer);\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/sender/okhttp/ByteRequestBody.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.report.sender.okhttp;\n\nimport okhttp3.MediaType;\nimport okhttp3.RequestBody;\nimport okio.BufferedSink;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.IOException;\n\n/**\n * should be replaced by HttpSpanJsonEncoder to return OkHttpJsonRequestBody\n */\npublic class ByteRequestBody extends RequestBody {\n    static final MediaType CONTENT_TYPE = MediaType.parse(\"application/json\");\n\n    private final byte[] data;\n    private final int contentLength;\n\n    public ByteRequestBody(byte[] data) {\n        this.data = data;\n        this.contentLength = data.length;\n    }\n\n    @Nullable\n    @Override\n    public MediaType contentType() {\n        return CONTENT_TYPE;\n    }\n\n    @Override\n    public void writeTo(@NotNull BufferedSink sink) throws IOException {\n        sink.write(data);\n    }\n\n    @Override public long contentLength() {\n        return contentLength;\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/sender/okhttp/HttpCall.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.report.sender.okhttp;\n\nimport com.megaease.easeagent.plugin.report.Call;\nimport com.megaease.easeagent.plugin.report.Callback;\nimport okhttp3.Response;\n\nimport javax.annotation.Nonnull;\nimport java.io.IOException;\n\n// from zipkin-reporter-java\nfinal class HttpCall implements Call<Void> {\n\n    final okhttp3.Call call;\n\n    HttpCall(okhttp3.Call call) {\n        this.call = call;\n    }\n\n    @Override\n    public Void execute() throws IOException {\n        try (Response response = call.execute()) {\n            parseResponse(response);\n        }\n        return null;\n    }\n\n    @Override\n    public void enqueue(Callback<Void> delegate) {\n        call.enqueue(new V2CallbackAdapter<>(delegate));\n    }\n\n    static void parseResponse(Response response) throws IOException {\n        if (response.isSuccessful()) {\n            return;\n        }\n        throw new IOException(\"response failed: \" + response);\n    }\n\n    static class V2CallbackAdapter<V> implements okhttp3.Callback {\n        final Callback<V> delegate;\n\n        V2CallbackAdapter(Callback<V> delegate) {\n            this.delegate = delegate;\n        }\n\n        @Override\n        public void onFailure(@Nonnull okhttp3.Call call, @Nonnull IOException e) {\n            delegate.onError(e);\n        }\n\n        /**\n         * Note: this runs on the {@link okhttp3.OkHttpClient#dispatcher() dispatcher} thread!\n         */\n        @Override\n        public void onResponse(@Nonnull okhttp3.Call call, @Nonnull Response response) {\n            try {\n                parseResponse(response);\n                delegate.onSuccess(null);\n            } catch (Throwable e) {\n                delegate.onError(e);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/sender/okhttp/HttpSender.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.report.sender.okhttp;\n\nimport com.google.auto.service.AutoService;\nimport com.megaease.easeagent.plugin.api.config.Config;\nimport com.megaease.easeagent.plugin.async.AgentThreadFactory;\nimport com.megaease.easeagent.plugin.report.Call;\nimport com.megaease.easeagent.plugin.report.EncodedData;\nimport com.megaease.easeagent.plugin.report.Sender;\nimport com.megaease.easeagent.plugin.utils.NoNull;\nimport com.megaease.easeagent.plugin.utils.common.StringUtils;\nimport com.megaease.easeagent.report.plugin.NoOpCall;\nimport lombok.extern.slf4j.Slf4j;\nimport okhttp3.*;\nimport okhttp3.tls.Certificates;\nimport okhttp3.tls.HandshakeCertificates;\nimport okhttp3.tls.HeldCertificate;\nimport okio.Buffer;\nimport okio.BufferedSink;\nimport okio.GzipSink;\nimport okio.Okio;\n\nimport java.io.IOException;\nimport java.security.cert.X509Certificate;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.SynchronousQueue;\nimport java.util.concurrent.ThreadPoolExecutor;\nimport java.util.concurrent.TimeUnit;\n\nimport static com.megaease.easeagent.config.report.ReportConfigConst.*;\nimport static java.util.concurrent.TimeUnit.MILLISECONDS;\n\n@Slf4j\n@AutoService(Sender.class)\npublic class HttpSender implements Sender {\n\n    public static final String SENDER_NAME = ZIPKIN_SENDER_NAME;\n\n    private static final String AUTH_HEADER = \"Authorization\";\n\n\n    private static final String ENABLED_KEY = \"enabled\";\n    private static final String URL_KEY = \"url\";\n    private static final String USERNAME_KEY = \"username\";\n    private static final String PASSWORD_KEY = \"password\";\n    private static final String GZIP_KEY = \"compress\";\n    private static final String MAX_REQUESTS_KEY = \"maxRequests\";\n\n    private static final String SERVER_USER_NAME_KEY = join(OUTPUT_SERVER_V2, USERNAME_KEY);\n    private static final String SERVER_PASSWORD_KEY = join(OUTPUT_SERVER_V2, PASSWORD_KEY);\n    private static final String SERVER_GZIP_KEY = join(OUTPUT_SERVER_V2, GZIP_KEY);\n\n    private static final String TLS_ENABLE = join(OUTPUT_SERVER_V2, \"tls.enable\");\n\n    // private key should be pkcs8 format\n    private static final String TLS_KEY = join(OUTPUT_SERVER_V2, \"tls.key\");\n    private static final String TLS_CERT = join(OUTPUT_SERVER_V2, \"tls.cert\");\n    private static final String TLS_CA_CERT = join(OUTPUT_SERVER_V2, \"tls.ca_cert\");\n\n    private String senderEnabledKey;\n    private String urlKey;\n    private String usernameKey;\n    private String passwordKey;\n    private String gzipKey;\n    private String maxRequestsKey;\n\n    private static final int MIN_TIMEOUT = 30_000;\n\n    private Config config;\n\n    private String url;\n    private HttpUrl httpUrl;\n    private String username;\n    private String password;\n\n    private boolean enabled;\n    private boolean gzip;\n    private boolean isAuth;\n\n    private int timeout;\n    private int maxRequests;\n\n    private String credential;\n    private OkHttpClient client;\n\n    private Boolean tlsEnable;\n    private String tlsKey;\n    private String tlsCert;\n    private String tlsCaCert;\n\n    private String prefix;\n\n    // URL-USER-PASSWORD as unique key shared a client\n    static ConcurrentHashMap<String, OkHttpClient> clientMap = new ConcurrentHashMap<>();\n\n    @Override\n    public String name() {\n        return SENDER_NAME;\n    }\n\n    @Override\n    public void init(Config config, String prefix) {\n        this.prefix = prefix;\n        extractConfig(config);\n        this.config = config;\n        initClient();\n    }\n\n    private void updatePrefix(String prefix) {\n        senderEnabledKey = join(prefix, ENABLED_KEY);\n        urlKey = join(prefix, URL_KEY);\n        usernameKey = join(prefix, USERNAME_KEY);\n        passwordKey = join(prefix, PASSWORD_KEY);\n        gzipKey = join(prefix, GZIP_KEY);\n        maxRequestsKey = join(prefix, MAX_REQUESTS_KEY);\n    }\n\n    private void extractConfig(Config config) {\n        updatePrefix(this.prefix);\n        this.url = getUrl(config);\n        this.username = StringUtils.noEmptyOf(config.getString(usernameKey), config.getString(SERVER_USER_NAME_KEY));\n        this.password = StringUtils.noEmptyOf(config.getString(passwordKey), config.getString(SERVER_PASSWORD_KEY));\n\n        this.tlsEnable = config.getBoolean(TLS_ENABLE);\n        this.tlsKey = config.getString(TLS_KEY);\n        this.tlsCert = config.getString(TLS_CERT);\n        this.tlsCaCert = config.getString(TLS_CA_CERT);\n\n        this.gzip = NoNull.of(config.getBooleanNullForUnset(gzipKey), NoNull.of(config.getBooleanNullForUnset(SERVER_GZIP_KEY), true));\n\n        this.timeout = NoNull.of(config.getInt(OUTPUT_SERVERS_TIMEOUT), MIN_TIMEOUT);\n        if (this.timeout < MIN_TIMEOUT) {\n            this.timeout = MIN_TIMEOUT;\n        }\n        this.enabled = NoNull.of(config.getBooleanNullForUnset(senderEnabledKey), true);\n        this.maxRequests = NoNull.of(config.getInt(maxRequestsKey), 65);\n\n        if (StringUtils.isEmpty(url) || Boolean.FALSE.equals(config.getBoolean(OUTPUT_SERVERS_ENABLE))) {\n            this.enabled = false;\n        } else {\n            this.httpUrl = HttpUrl.parse(this.url);\n            if (this.httpUrl == null) {\n                log.error(\"Invalid Url:{}\", this.url);\n                this.enabled = false;\n            }\n        }\n\n        this.isAuth = !StringUtils.isEmpty(username) && !StringUtils.isEmpty(password);\n        if (isAuth) {\n            this.credential = Credentials.basic(username, password);\n        }\n    }\n\n    private String getUrl(Config config) {\n        // url\n        String outputServer = config.getString(BOOTSTRAP_SERVERS);\n        String cUrl = NoNull.of(config.getString(urlKey), \"\");\n        if (!StringUtils.isEmpty(outputServer) && !cUrl.startsWith(\"http\")) {\n            cUrl = outputServer + cUrl;\n        }\n        return cUrl;\n    }\n\n    @Override\n    public Call<Void> send(EncodedData encodedData) {\n        if (!enabled) {\n            return NoOpCall.getInstance(Void.class);\n        }\n        Request request;\n\n        try {\n            if (encodedData instanceof RequestBody) {\n                request = newRequest((RequestBody) encodedData);\n            } else {\n                request = newRequest(new ByteRequestBody(encodedData.getData()));\n            }\n        } catch (IOException e) {\n            // log rate-limit\n            if (log.isDebugEnabled()) {\n                log.debug(\"tracing send fail!\");\n            }\n            return NoOpCall.getInstance(Void.class);\n        }\n\n        return new HttpCall(client.newCall(request));\n    }\n\n    @Override\n    public boolean isAvailable() {\n        return this.enabled;\n    }\n\n    @Override\n    public void updateConfigs(Map<String, String> changes) {\n        this.config.updateConfigsNotNotify(changes);\n\n        String newUserName = StringUtils.noEmptyOf(config.getString(usernameKey), config.getString(SERVER_USER_NAME_KEY));\n        String newPwd = StringUtils.noEmptyOf(config.getString(passwordKey), config.getString(SERVER_PASSWORD_KEY));\n        // check new client\n        boolean renewClient = !getUrl(this.config).equals(this.url) || !org.apache.commons.lang3.StringUtils.equals(newUserName, this.username) || !org.apache.commons.lang3.StringUtils.equals(newPwd, this.password) || !org.apache.commons.lang3.StringUtils.equals(this.config.getString(TLS_CA_CERT), this.tlsCaCert) || !org.apache.commons.lang3.StringUtils.equals(this.config.getString(TLS_CERT), this.tlsCert) || !org.apache.commons.lang3.StringUtils.equals(this.config.getString(TLS_KEY), this.tlsKey);\n\n        if (renewClient) {\n            clearClient();\n            extractConfig(this.config);\n            newClient();\n        }\n    }\n\n    @Override\n    public void close() throws IOException {\n        clearClient();\n    }\n\n    /**\n     * Waits up to a second for in-flight requests to finish before cancelling them\n     */\n    private void clearClient() {\n        OkHttpClient dClient = clientMap.remove(getClientKey());\n        if (dClient == null) {\n            return;\n        }\n        Dispatcher dispatcher = dClient.dispatcher();\n        dispatcher.executorService().shutdown();\n        try {\n            if (!dispatcher.executorService().awaitTermination(1, TimeUnit.SECONDS)) {\n                dispatcher.cancelAll();\n            }\n        } catch (InterruptedException e) {\n            Thread.currentThread().interrupt();\n        }\n    }\n\n    // different url for different business, so create separate clients with different dispatcher\n    private String getClientKey() {\n        return this.url + \":\" + this.username + \":\" + this.password;\n    }\n\n    private void newClient() {\n        String clientKey = getClientKey();\n        OkHttpClient newClient = clientMap.get(clientKey);\n        if (newClient != null) {\n            client = newClient;\n            return;\n        }\n        OkHttpClient.Builder builder = new OkHttpClient.Builder();\n\n        // timeout\n        builder.connectTimeout(timeout, MILLISECONDS);\n        builder.readTimeout(timeout, MILLISECONDS);\n        builder.writeTimeout(timeout, MILLISECONDS);\n\n        // auth\n        if (this.isAuth) {\n            appendBasicAuth(builder, this.credential);\n        }\n        // tls\n        if (Boolean.TRUE.equals(this.tlsEnable)) {\n            appendTLS(builder, this.tlsCaCert, this.tlsCert, this.tlsKey);\n        }\n        synchronized (HttpSender.class) {\n            if (clientMap.get(clientKey) != null) {\n                client = clientMap.get(clientKey);\n            } else {\n                builder.dispatcher(newDispatcher(maxRequests));\n                newClient = builder.build();\n                clientMap.putIfAbsent(clientKey, newClient);\n                client = newClient;\n            }\n        }\n    }\n\n    public static void appendBasicAuth(OkHttpClient.Builder builder, String basicUser, String basicPassword) {\n        builder.addInterceptor(chain -> {\n            Request request = chain.request();\n            Request authRequest = request.newBuilder().header(AUTH_HEADER, Credentials.basic(basicUser, basicPassword)).build();\n            return chain.proceed(authRequest);\n        });\n    }\n\n    public static void appendBasicAuth(OkHttpClient.Builder builder, String basicCredential) {\n        builder.addInterceptor(chain -> {\n            Request request = chain.request();\n            Request authRequest = request.newBuilder().header(AUTH_HEADER, basicCredential).build();\n            return chain.proceed(authRequest);\n        });\n    }\n\n    public static void appendTLS(OkHttpClient.Builder builder, String tlsCaCert, String tlsCert, String tlsKey) {\n        X509Certificate clientX509Certificate = Certificates.decodeCertificatePem(tlsCert);\n        HeldCertificate clientCertificateKey = HeldCertificate.decode(tlsCert + tlsKey);\n        HandshakeCertificates.Builder handshakeCertificatesBuilder = new HandshakeCertificates.Builder();\n        handshakeCertificatesBuilder.addPlatformTrustedCertificates();\n        if (org.apache.commons.lang3.StringUtils.isNotBlank(tlsCaCert)) {\n            X509Certificate rootX509Certificate = Certificates.decodeCertificatePem(tlsCaCert);\n            handshakeCertificatesBuilder.addTrustedCertificate(rootX509Certificate);\n        }\n        handshakeCertificatesBuilder.heldCertificate(clientCertificateKey, clientX509Certificate);\n        HandshakeCertificates clientCertificates = handshakeCertificatesBuilder.build();\n        builder.sslSocketFactory(clientCertificates.sslSocketFactory(), clientCertificates.trustManager());\n    }\n\n    private void initClient() {\n        if (client != null) {\n            return;\n        }\n        newClient();\n    }\n\n    // borrow form zipkin-reporter\n    private Request newRequest(RequestBody body) throws IOException {\n        Request.Builder request = new Request.Builder().url(httpUrl);\n        // Amplification can occur when the Zipkin endpoint is accessed through a proxy, and the proxy is instrumented.\n        // This prevents that in proxies, such as Envoy, that understand B3 single format,\n        request.addHeader(\"b3\", \"0\");\n        if (this.isAuth) {\n            request.header(AUTH_HEADER, credential);\n        }\n        if (this.gzip) {\n            request.addHeader(\"Content-Encoding\", \"gzip\");\n            Buffer gzipped = new Buffer();\n            BufferedSink gzipSink = Okio.buffer(new GzipSink(gzipped));\n            body.writeTo(gzipSink);\n            gzipSink.close();\n            body = new BufferRequestBody(body.contentType(), gzipped);\n        }\n        request.post(body);\n        return request.build();\n    }\n\n    static Dispatcher newDispatcher(int maxRequests) {\n        // bound the executor so that we get consistent performance\n        ThreadPoolExecutor dispatchExecutor = new ThreadPoolExecutor(0, maxRequests, 60, TimeUnit.SECONDS,\n            // Using a synchronous queue means messages will send immediately until we hit max\n            // in-flight requests. Once max requests are hit, send will block the caller, which is\n            // the AsyncReporter flush thread. This is ok, as the AsyncReporter has a buffer of\n            // unsent spans for this purpose.\n            new SynchronousQueue<>(), OkHttpSenderThreadFactory.INSTANCE);\n\n        Dispatcher dispatcher = new Dispatcher(dispatchExecutor);\n        dispatcher.setMaxRequests(maxRequests);\n        dispatcher.setMaxRequestsPerHost(maxRequests);\n        return dispatcher;\n    }\n\n    static class OkHttpSenderThreadFactory extends AgentThreadFactory {\n        public static final OkHttpSenderThreadFactory INSTANCE = new OkHttpSenderThreadFactory();\n\n        @Override\n        public Thread newThread(Runnable r) {\n            return new Thread(r, \"AgentHttpSenderDispatcher-\" + createCount.getAndIncrement());\n        }\n    }\n\n    // from zipkin-reporter-java\n    static final class BufferRequestBody extends RequestBody {\n        final MediaType contentType;\n        final Buffer body;\n\n        BufferRequestBody(MediaType contentType, Buffer body) {\n            this.contentType = contentType;\n            this.body = body;\n        }\n\n        @Override\n        public long contentLength() {\n            return body.size();\n        }\n\n        @Override\n        public MediaType contentType() {\n            return contentType;\n        }\n\n        @Override\n        public void writeTo(BufferedSink sink) throws IOException {\n            sink.write(body, body.size());\n        }\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/trace/Platform.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.report.trace;\n\npublic class Platform {\n    static final ThreadLocal<char[]> SHORT_STRING_BUFFER = new ThreadLocal<>();\n    /** Maximum character length constraint of most names, IP literals and IDs. */\n    public static final int SHORT_STRING_LENGTH = 256;\n\n    /**\n     * Returns a {@link ThreadLocal} reused {@code char[]} for use when decoding bytes into hex, IP\n     * literals, or {@link #SHORT_STRING_LENGTH short strings}. The buffer must never be leaked\n     * outside the method. Most will {@link String#String(char[], int, int) copy it into a string}.\n     */\n    public static char[] shortStringBuffer() {\n        char[] shortStringBuffer = SHORT_STRING_BUFFER.get();\n        if (shortStringBuffer == null) {\n            shortStringBuffer = new char[SHORT_STRING_LENGTH];\n            SHORT_STRING_BUFFER.set(shortStringBuffer);\n        }\n        return shortStringBuffer;\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/trace/RefreshableReporter.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.report.trace;\n\nimport com.megaease.easeagent.plugin.api.config.Config;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.utils.common.StringUtils;\nimport com.megaease.easeagent.report.async.trace.TraceAsyncProps;\nimport com.megaease.easeagent.report.sender.SenderWithEncoder;\nimport com.megaease.easeagent.report.async.trace.SDKAsyncReporter;\nimport com.megaease.easeagent.report.async.AsyncProps;\nimport com.megaease.easeagent.report.plugin.ReporterRegistry;\nimport zipkin2.reporter.Reporter;\n\nimport java.util.Map;\nimport java.util.concurrent.TimeUnit;\n\nimport static com.megaease.easeagent.config.report.ReportConfigConst.TRACE_SENDER;\nimport static com.megaease.easeagent.config.report.ReportConfigConst.TRACE_SENDER_NAME;\n\n/**\n * RefreshableReporter is a reporter wrapper, which enhances the AgentAsyncReporter with refreshable function\n *\n * @param <S> always zipkin2.reporter\n */\npublic class RefreshableReporter<S> implements Reporter<S> {\n    private final SDKAsyncReporter<S> asyncReporter;\n    private AsyncProps traceProperties;\n    private Config reportConfig;\n\n    public RefreshableReporter(SDKAsyncReporter<S> reporter,\n                               Config reportConfig) {\n        this.asyncReporter = reporter;\n        this.traceProperties = new TraceAsyncProps(reportConfig);\n        this.reportConfig = reportConfig;\n    }\n\n    /**\n     * report delegate span report to asyncReporter\n     *\n     * @param span a span need to be reported\n     */\n    @Override\n    public void report(S span) {\n        this.asyncReporter.report(span);\n    }\n\n    public synchronized void refresh(Map<String, String> cfg) {\n        String name = cfg.get(TRACE_SENDER_NAME);\n        SenderWithEncoder sender = asyncReporter.getSender();\n        if (sender != null) {\n            if (StringUtils.isNotEmpty(name) && !sender.name().equals(name)) {\n                try {\n                    sender.close();\n                } catch (Exception ignored) {\n                    // ignored\n                }\n                sender = ReporterRegistry.getSender(TRACE_SENDER, this.reportConfig);\n                asyncReporter.setSender(sender);\n            }\n        } else {\n            sender = ReporterRegistry.getSender(TRACE_SENDER, this.reportConfig);\n            asyncReporter.setSender(sender);\n        }\n\n        traceProperties = new TraceAsyncProps(this.reportConfig);\n        asyncReporter.closeFlushThread();\n        asyncReporter.setPending(traceProperties.getQueuedMaxItems(), traceProperties.getQueuedMaxSize());\n        asyncReporter.setMessageTimeoutNanos(messageTimeout(traceProperties.getMessageTimeout()));\n        asyncReporter.startFlushThread(); // start thread\n    }\n\n    protected long messageTimeout(long timeout) {\n        if (timeout < 0) {\n            timeout = 1000L;\n        }\n        return TimeUnit.MILLISECONDS.toNanos(timeout);\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/trace/ReportSpanBuilder.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.report.trace;\n\nimport com.megaease.easeagent.plugin.report.tracing.Annotation;\nimport com.megaease.easeagent.plugin.report.tracing.Endpoint;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpanImpl;\nimport zipkin2.Span;\nimport zipkin2.Span.Kind;\n\nimport java.util.*;\nimport java.util.logging.Logger;\nimport java.util.stream.Collectors;\n\nimport static brave.internal.codec.HexCodec.toLowerHex;\nimport static brave.internal.codec.HexCodec.writeHexLong;\nimport static java.lang.String.format;\nimport static java.util.logging.Level.FINEST;\n\n@SuppressWarnings(\"unused\")\npublic class ReportSpanBuilder extends ReportSpanImpl.Builder {\n    static final Endpoint EMPTY_ENDPOINT = endpoint(zipkin2.Endpoint.newBuilder().build());\n\n    public static ReportSpanBuilder newBuilder() {\n        return new ReportSpanBuilder();\n    }\n\n    public ReportSpanBuilder clear() {\n        traceId = null;\n        parentId = null;\n        id = null;\n        kind = null;\n        name = null;\n        timestamp = 0L;\n        duration = 0L;\n        localEndpoint = null;\n        remoteEndpoint = null;\n        if (annotations != null) annotations.clear();\n        if (tags != null) tags.clear();\n        shared = false;\n        debug = false;\n        return this;\n    }\n\n    /**\n     * Used to merge multiple incomplete spans representing the same operation on the same host. Do\n     * not use this to merge spans that occur on different hosts.\n     */\n    public ReportSpanBuilder merge(Span source) {\n        if (traceId == null) traceId = source.traceId();\n        if (id == null) id = source.id();\n        if (parentId == null) parentId = source.parentId();\n        if (kind == null) kind = source.kind().name();\n        if (name == null) name = source.name();\n        if (timestamp == 0L) timestamp = source.timestampAsLong();\n        if (duration == 0L) duration = source.durationAsLong();\n        if (localEndpoint == null) {\n            localEndpoint = endpoint(source.localEndpoint());\n        } else if (source.localEndpoint() != null) {\n            mergeEndpoint(localEndpoint, source.localEndpoint());\n        }\n        if (remoteEndpoint == null) {\n            remoteEndpoint = endpoint(source.remoteEndpoint());\n        } else if (source.remoteEndpoint() != null) {\n            mergeEndpoint(remoteEndpoint, source.remoteEndpoint());\n        }\n        if (!source.annotations().isEmpty()) {\n            if (annotations == null) {\n                annotations = new ArrayList<>(source.annotations().size());\n            }\n            annotations.addAll(annotations(source.annotations()));\n        }\n        if (!source.tags().isEmpty()) {\n            if (tags == null) tags = new TreeMap<>();\n            tags.putAll(source.tags());\n        }\n        shared = source.debug();\n        debug = source.debug();\n\n        return this;\n    }\n\n    Annotation annotation(zipkin2.Annotation sa) {\n        return new Annotation(sa.timestamp(), sa.value());\n    }\n\n    Collection<Annotation> annotations(Collection<zipkin2.Annotation> sa) {\n        return sa.stream().map(this::annotation).collect(Collectors.toList());\n    }\n\n    public static Endpoint endpoint(zipkin2.Endpoint endpoint) {\n        Endpoint e = new Endpoint();\n        e.setPort(endpoint.portAsInt());\n        e.setServiceName(endpoint.serviceName());\n        e.setIpv4(endpoint.ipv4());\n        e.setIpv6(endpoint.ipv6());\n        return e;\n    }\n\n    public static void mergeEndpoint(Endpoint e, zipkin2.Endpoint source) {\n        if (e.serviceName() == null) {\n            e.setServiceName(source.serviceName());\n        }\n        if (e.ipv4() == null) {\n            e.setIpv4(source.ipv4());\n        }\n        if (e.ipv6() == null) {\n            e.setIpv6(source.ipv6());\n        }\n        if (e.port() == 0) {\n            e.setPort(source.port());\n        }\n    }\n\n    public String kind() {\n        return kind;\n    }\n\n    public Endpoint localEndpoint() {\n        return localEndpoint;\n    }\n\n    /**\n     * @throws IllegalArgumentException if not lower-hex format\n     * @see ReportSpan#id()\n     */\n    public ReportSpanBuilder traceId(String traceId) {\n        this.traceId = normalizeTraceId(traceId);\n        return this;\n    }\n\n    /**\n     * Encodes 64 or 128 bits from the input into a hex trace ID.\n     *\n     * @param high Upper 64bits of the trace ID. Zero means the trace ID is 64-bit.\n     * @param low Lower 64bits of the trace ID.\n     * @throws IllegalArgumentException if both values are zero\n     */\n    public ReportSpanBuilder traceId(long high, long low) {\n        if (high == 0L && low == 0L) throw new IllegalArgumentException(\"empty trace ID\");\n        char[] data = Platform.shortStringBuffer();\n        int pos = 0;\n        if (high != 0L) {\n            writeHexLong(data, pos, high);\n            pos += 16;\n        }\n        writeHexLong(data, pos, low);\n        this.traceId = new String(data, 0, high != 0L ? 32 : 16);\n        return this;\n    }\n\n    /**\n     * Encodes 64 bits from the input into a hex parent ID. Unsets the {@link ReportSpan#parentId()} if\n     * the input is 0.\n     *\n     * @see ReportSpan#parentId()\n     */\n    public ReportSpanBuilder parentId(long parentId) {\n        this.parentId = parentId != 0L ? toLowerHex(parentId) : null;\n        return this;\n    }\n\n    /**\n     * @throws IllegalArgumentException if not lower-hex format\n     * @see ReportSpan#parentId()\n     */\n    public ReportSpanBuilder parentId(String parentId) {\n        if (parentId == null) {\n            this.parentId = null;\n            return this;\n        }\n        int length = parentId.length();\n        if (length == 0) throw new IllegalArgumentException(\"parentId is empty\");\n        if (length > 16) throw new IllegalArgumentException(\"parentId.length > 16\");\n        if (validateHexAndReturnZeroPrefix(parentId) == length) {\n            this.parentId = null;\n        } else {\n            this.parentId = length < 16 ? padLeft(parentId, 16) : parentId;\n        }\n        return this;\n    }\n\n    /**\n     * Encodes 64 bits from the input into a hex span ID.\n     *\n     * @throws IllegalArgumentException if the input is zero\n     * @see ReportSpan#id()\n     */\n    public ReportSpanBuilder id(long id) {\n        if (id == 0L) throw new IllegalArgumentException(\"empty id\");\n        this.id = toLowerHex(id);\n        return this;\n    }\n\n    /**\n     * @throws IllegalArgumentException if not lower-hex format\n     * @see ReportSpan#id()\n     */\n    public ReportSpanBuilder id(String id) {\n        if (id == null) throw new NullPointerException(\"id == null\");\n        int length = id.length();\n        if (length == 0) throw new IllegalArgumentException(\"id is empty\");\n        if (length > 16) throw new IllegalArgumentException(\"id.length > 16\");\n        if (validateHexAndReturnZeroPrefix(id) == 16) {\n            throw new IllegalArgumentException(\"id is all zeros\");\n        }\n        this.id = length < 16 ? padLeft(id, 16) : id;\n        return this;\n    }\n\n    public ReportSpanBuilder kind(Kind kind) {\n        this.kind = kind.name();\n        return this;\n    }\n\n    public ReportSpanBuilder name(String name) {\n        this.name = name == null || name.isEmpty() ? null : name.toLowerCase(Locale.ROOT);\n        return this;\n    }\n\n    /** @see ReportSpan#timestamp() */\n    public ReportSpanBuilder timestamp(long timestamp) {\n        if (timestamp < 0L) timestamp = 0L;\n        this.timestamp = timestamp;\n        return this;\n    }\n\n    /** @see ReportSpan#timestamp() */\n    public ReportSpanBuilder timestamp(Long timestamp) {\n        if (timestamp == null || timestamp < 0L) timestamp = 0L;\n        this.timestamp = timestamp;\n        return this;\n    }\n\n    /** @see ReportSpan#duration() */\n    public ReportSpanBuilder duration(long duration) {\n        if (duration < 0L) duration = 0L;\n        this.duration = duration;\n        return this;\n    }\n\n    /** @see ReportSpan#duration() */\n    public ReportSpanBuilder duration(Long duration) {\n        if (duration == null || duration < 0L) duration = 0L;\n        this.duration = duration;\n        return this;\n    }\n\n    /** @see ReportSpan#localEndpoint() */\n    public ReportSpanBuilder localEndpoint(Endpoint localEndpoint) {\n        if (EMPTY_ENDPOINT.equals(localEndpoint)) {\n            localEndpoint = null;\n        }\n        this.localEndpoint = localEndpoint;\n        return this;\n    }\n\n    /** @see ReportSpan#remoteEndpoint() */\n    public ReportSpanBuilder remoteEndpoint(Endpoint remoteEndpoint) {\n        if (EMPTY_ENDPOINT.equals(remoteEndpoint)) remoteEndpoint = null;\n        this.remoteEndpoint = remoteEndpoint;\n        return this;\n    }\n\n    /** @see ReportSpan#annotations() */\n    public ReportSpanBuilder addAnnotation(long timestamp, String value) {\n        if (annotations == null) annotations = new ArrayList<>(2);\n        annotations.add(new Annotation(timestamp, value));\n        return this;\n    }\n\n    /** @see ReportSpan#annotations() */\n    public ReportSpanBuilder clearAnnotations() {\n        if (annotations == null) return this;\n        annotations.clear();\n        return this;\n    }\n\n    /** @see ReportSpan#tags() */\n    public ReportSpanBuilder putTag(String key, String value) {\n        if (tags == null) tags = new TreeMap<>();\n        if (key == null) throw new NullPointerException(\"key == null\");\n        if (value == null) throw new NullPointerException(\"value of \" + key + \" == null\");\n        this.tags.put(key, value);\n        return this;\n    }\n\n    /** @see ReportSpan#tags() */\n    public ReportSpanBuilder clearTags() {\n        if (tags == null) return this;\n        tags.clear();\n        return this;\n    }\n\n    /** @see ReportSpan#debug() */\n    public ReportSpanBuilder debug(boolean debug) {\n        this.debug = debug;\n        return this;\n    }\n\n    /** @see ReportSpan#debug */\n    public ReportSpanBuilder debug(Boolean debug) {\n        if (debug != null) {\n            return debug((boolean) debug);\n        } else {\n            return debug(false);\n        }\n    }\n\n    /** @see ReportSpan#shared */\n    public ReportSpanBuilder shared(boolean shared) {\n        this.shared = shared;\n        return this;\n    }\n\n    /** @see ReportSpan#shared */\n    public ReportSpanBuilder shared(Boolean shared) {\n        if (shared != null) {\n            return shared((boolean) shared);\n        } else {\n            return shared(false);\n        }\n    }\n\n    public ReportSpan build() {\n        String missing = \"\";\n        if (traceId == null) {\n            missing += \" traceId\";\n        }\n        if (id == null) {\n            missing += \" id\";\n        }\n        if (!\"\".equals(missing)) {\n            throw new IllegalStateException(\"Missing :\" + missing);\n        }\n        if (id.equals(parentId)) { // edge case, so don't require a logger field\n            Logger logger = Logger.getLogger(ReportSpan.class.getName());\n            if (logger.isLoggable(FINEST)) {\n                logger.fine(format(\"undoing circular dependency: traceId=%s, spanId=%s\", traceId, id));\n            }\n            parentId = null;\n        }\n        annotations = sortedList(annotations);\n        // shared is for the server side, unset it if accidentally set on the client side\n        if (this.shared && kind.equals(Kind.CLIENT.name())) {\n            Logger logger = Logger.getLogger(ReportSpan.class.getName());\n            if (logger.isLoggable(FINEST)) {\n                logger.fine(format(\"removing shared flag on client: traceId=%s, spanId=%s\", traceId, id));\n            }\n            shared(null);\n        }\n        return new ReportSpanImpl(this);\n    }\n\n    ReportSpanBuilder() {\n    }\n\n    /**\n     * Returns a valid lower-hex trace ID, padded left as needed to 16 or 32 characters.\n     *\n     * @throws IllegalArgumentException if over-sized or not lower-hex\n     */\n    public static String normalizeTraceId(String traceId) {\n        if (traceId == null) throw new NullPointerException(\"traceId == null\");\n        int length = traceId.length();\n        if (length == 0) throw new IllegalArgumentException(\"traceId is empty\");\n        if (length > 32) throw new IllegalArgumentException(\"traceId.length > 32\");\n        int zeros = validateHexAndReturnZeroPrefix(traceId);\n        if (zeros == length) throw new IllegalArgumentException(\"traceId is all zeros\");\n        if (length == 32 || length == 16) {\n            if (length == 32 && zeros >= 16) return traceId.substring(16);\n            return traceId;\n        } else if (length < 16) {\n            return padLeft(traceId, 16);\n        } else {\n            return padLeft(traceId, 32);\n        }\n    }\n\n    static int validateHexAndReturnZeroPrefix(String id) {\n        int zeros = 0;\n        boolean inZeroPrefix = id.charAt(0) == '0';\n        for (int i = 0, length = id.length(); i < length; i++) {\n            char c = id.charAt(i);\n            if ((c < '0' || c > '9') && (c < 'a' || c > 'f')) {\n                throw new IllegalArgumentException(id + \" should be lower-hex encoded with no prefix\");\n            }\n            if (c != '0') {\n                inZeroPrefix = false;\n            } else if (inZeroPrefix) {\n                zeros++;\n            }\n        }\n        return zeros;\n    }\n\n    static final String THIRTY_TWO_ZEROS;\n    static {\n        char[] zeros = new char[32];\n        Arrays.fill(zeros, '0');\n        THIRTY_TWO_ZEROS = new String(zeros);\n    }\n\n    static String padLeft(String id, int desiredLength) {\n        int length = id.length();\n        int numZeros = desiredLength - length;\n\n        char[] data = Platform.shortStringBuffer();\n        THIRTY_TWO_ZEROS.getChars(0, numZeros, data, 0);\n        id.getChars(0, length, data, numZeros);\n\n        return new String(data, 0, desiredLength);\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    static <T extends Comparable<? super T>> List<T> sortedList(List<T> in) {\n        if (in == null || in.isEmpty()) {\n            return Collections.emptyList();\n        }\n\n        if (in.size() == 1) {\n            return Collections.singletonList(in.get(0));\n        }\n\n        Object[] array = in.toArray();\n        Arrays.sort(array);\n\n        // dedupe\n        int j = 0;\n        int i = 1;\n        while (i < array.length) {\n            if (!array[i].equals(array[j])) {\n                array[++j] = array[i];\n            }\n            i++;\n        }\n\n        List result = Arrays.asList(i == j + 1 ? array : Arrays.copyOf(array, j + 1));\n        return Collections.<T>unmodifiableList(result);\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/trace/TraceReport.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.report.trace;\n\nimport com.megaease.easeagent.config.AutoRefreshConfigItem;\nimport com.megaease.easeagent.config.report.ReportConfigConst;\nimport com.megaease.easeagent.plugin.api.config.ChangeItem;\nimport com.megaease.easeagent.plugin.api.config.Config;\nimport com.megaease.easeagent.plugin.api.config.ConfigChangeListener;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport com.megaease.easeagent.report.async.trace.SDKAsyncReporter;\nimport com.megaease.easeagent.report.async.AsyncProps;\nimport com.megaease.easeagent.report.async.trace.TraceAsyncProps;\nimport com.megaease.easeagent.report.encoder.span.GlobalExtrasSupplier;\nimport com.megaease.easeagent.report.plugin.ReporterRegistry;\nimport com.megaease.easeagent.report.sender.SenderWithEncoder;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\npublic class TraceReport {\n    private final RefreshableReporter<ReportSpan> spanRefreshableReporter;\n\n    public TraceReport(Config reportConfig) {\n        spanRefreshableReporter = initSpanRefreshableReporter(reportConfig);\n        reportConfig.addChangeListener(new InternalListener());\n    }\n\n    private RefreshableReporter<ReportSpan> initSpanRefreshableReporter(Config reportConfig) {\n        SenderWithEncoder sender = ReporterRegistry.getSender(ReportConfigConst.TRACE_SENDER, reportConfig);\n\n        AsyncProps traceProperties = new TraceAsyncProps(reportConfig);\n\n        GlobalExtrasSupplier extrasSupplier = new GlobalExtrasSupplier() {\n            Config gConfig = EaseAgent.getConfig();\n            final AutoRefreshConfigItem<String> serviceName = new AutoRefreshConfigItem<>(gConfig, ConfigConst.SERVICE_NAME, Config::getString);\n            final AutoRefreshConfigItem<String> systemName = new AutoRefreshConfigItem<>(gConfig, ConfigConst.SYSTEM_NAME, Config::getString);\n\n            @Override\n            public String service() {\n                return serviceName.getValue();\n            }\n\n            @Override\n            public String system() {\n                return systemName.getValue();\n            }\n        };\n\n        SDKAsyncReporter<ReportSpan> reporter = SDKAsyncReporter.\n            builderSDKAsyncReporter(sender, traceProperties, extrasSupplier);\n\n        reporter.startFlushThread();\n\n        return new RefreshableReporter<>(reporter, reportConfig);\n    }\n\n    public void report(ReportSpan span) {\n        this.spanRefreshableReporter.report(span);\n    }\n\n    private class InternalListener implements ConfigChangeListener {\n        @Override\n        public void onChange(List<ChangeItem> list) {\n            Map<String, String> cfg = filterChanges(list);\n\n            if (cfg.isEmpty()) {\n                return;\n            }\n            spanRefreshableReporter.refresh(cfg);\n        }\n\n        private Map<String, String> filterChanges(List<ChangeItem> list) {\n            Map<String, String> cfg = new HashMap<>();\n            list.forEach(one -> cfg.put(one.getFullName(), one.getNewValue()));\n            return cfg;\n        }\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/util/SpanUtils.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.report.util;\n\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport zipkin2.Span;\n\npublic class SpanUtils {\n    private SpanUtils() {}\n\n    public static boolean isValidSpan(Object next) {\n        if (next instanceof ReportSpan) {\n            ReportSpan s = (ReportSpan) next;\n            return s.timestamp() > 0;\n        } else if (next instanceof Span) {\n            Span s = (Span) next;\n            return s.timestamp() != null && s.timestamp() > 0;\n        }\n        return false;\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/util/TextUtils.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.report.util;\n\npublic class TextUtils {\n    private TextUtils() {}\n\n    public static boolean hasText(String content) {\n        return content != null && content.trim().length() > 0;\n    }\n\n    public static boolean isEmpty(String value) {\n        return !hasText(value);\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/megaease/easeagent/report/util/Utils.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.report.util;\n\nimport com.megaease.easeagent.plugin.api.config.Config;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.report.OutputProperties;\nimport com.megaease.easeagent.report.metric.MetricProps;\n\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Map;\n\nimport static com.megaease.easeagent.config.report.ReportConfigConst.*;\n\npublic class Utils {\n    private Utils() {}\n\n    public static boolean isOutputPropertiesChange(Map<String, String> changes) {\n        List<String> relatedNames = Arrays.asList(\n            OUTPUT_SERVER_V2\n        );\n        return changes.keySet().stream().anyMatch(relatedNames::contains);\n    }\n\n    public static OutputProperties extractOutputProperties(Config configs) {\n        return OutputProperties.newDefault(configs);\n    }\n\n    public static MetricProps extractMetricProps(IPluginConfig config, Config reportConfig) {\n        return MetricProps.newDefault(config, reportConfig);\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/zipkin2/reporter/TracerConverter.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage zipkin2.reporter;\n\nimport java.util.List;\n\npublic interface TracerConverter {\n\n    List<byte[]> converter(List<byte[]> nextMessage);\n\n\n}\n"
  },
  {
    "path": "report/src/main/java/zipkin2/reporter/brave/ConvertSpanReporter.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage zipkin2.reporter.brave;\nimport brave.Span.Kind;\nimport brave.Tag;\nimport brave.handler.MutableSpan;\nimport brave.handler.MutableSpan.AnnotationConsumer;\nimport brave.handler.MutableSpan.TagConsumer;\nimport brave.handler.SpanHandler;\nimport java.util.LinkedHashMap;\nimport java.util.Map;\nimport java.util.logging.Logger;\n\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport com.megaease.easeagent.report.trace.ReportSpanBuilder;\nimport zipkin2.Endpoint;\nimport zipkin2.Span;\nimport zipkin2.reporter.Reporter;\n\nfinal class ConvertSpanReporter implements Reporter<MutableSpan> {\n    static final Logger logger = Logger.getLogger(ConvertSpanReporter.class.getName());\n    static final Map<Kind, Span.Kind> BRAVE_TO_ZIPKIN_KIND = generateKindMap();\n\n    final Reporter<ReportSpan> delegate;\n    final Tag<Throwable> errorTag;\n\n    ConvertSpanReporter(Reporter<ReportSpan> delegate, Tag<Throwable> errorTag) {\n        this.delegate = delegate;\n        this.errorTag = errorTag;\n    }\n\n    @Override public void report(MutableSpan span) {\n        maybeAddErrorTag(span);\n        ReportSpan converted = convert(span);\n        delegate.report(converted);\n    }\n\n    static ReportSpan convert(MutableSpan span) {\n        ReportSpanBuilder result = ReportSpanBuilder.newBuilder()\n            .traceId(span.traceId())\n            .parentId(span.parentId())\n            .id(span.id())\n            .name(span.name());\n\n        long start = span.startTimestamp();\n        long finish = span.finishTimestamp();\n        result.timestamp(start);\n        if (start != 0 && finish != 0L) {\n            result.duration(Math.max(finish - start, 1));\n        }\n\n        // use ordinal comparison to defend against version skew\n        Kind kind = span.kind();\n        if (kind != null) {\n            result.kind(BRAVE_TO_ZIPKIN_KIND.get(kind));\n        }\n\n        String localServiceName = span.localServiceName();\n        String localIp = span.localIp();\n        if (localServiceName != null || localIp != null) {\n            zipkin2.Endpoint e = Endpoint.newBuilder()\n                .serviceName(localServiceName)\n                .ip(localIp)\n                .port(span.localPort())\n                .build();\n            result.localEndpoint(ReportSpanBuilder.endpoint(e));\n        }\n\n        String remoteServiceName = span.remoteServiceName();\n        String remoteIp = span.remoteIp();\n        if (remoteServiceName != null || remoteIp != null) {\n            zipkin2.Endpoint e = Endpoint.newBuilder()\n                .serviceName(remoteServiceName)\n                .ip(remoteIp)\n                .port(span.remotePort())\n                .build();\n            result.remoteEndpoint(ReportSpanBuilder.endpoint(e));\n        }\n\n        span.forEachTag(Consumer.INSTANCE, result);\n        span.forEachAnnotation(Consumer.INSTANCE, result);\n\n        if (span.shared()) result.shared(true);\n        if (span.debug()) result.debug(true);\n        return result.build();\n    }\n\n    void maybeAddErrorTag(MutableSpan span) {\n        // span.tag(key) iterates: check if we need to first!\n        if (span.error() == null) return;\n        if (span.tag(\"error\") == null) errorTag.tag(span.error(), null, span);\n    }\n\n    @Override public String toString() {\n        return delegate.toString();\n    }\n\n    /**\n     * Overridden to avoid duplicates when added via {@link brave.Tracing.Builder#addSpanHandler(SpanHandler)}\n     */\n    @Override public final boolean equals(Object o) {\n        if (o == this) return true;\n        if (!(o instanceof ConvertSpanReporter)) return false;\n        return delegate.equals(((ConvertSpanReporter) o).delegate);\n    }\n\n    /**\n     * Overridden to avoid duplicates when added via {@link brave.Tracing.Builder#addSpanHandler(SpanHandler)}\n     */\n    @Override\n    public final int hashCode() {\n        return delegate.hashCode();\n    }\n\n    enum Consumer implements TagConsumer<ReportSpanBuilder>, AnnotationConsumer<ReportSpanBuilder> {\n        INSTANCE;\n\n        @Override public void accept(ReportSpanBuilder target, String key, String value) {\n            target.putTag(key, value);\n        }\n\n        @Override public void accept(ReportSpanBuilder target, long timestamp, String value) {\n            target.addAnnotation(timestamp, value);\n        }\n    }\n\n    /**\n     * This keeps the code maintenance free in the rare case there is disparity between Brave and\n     * Zipkin kind values.\n     */\n    static Map<Kind, Span.Kind> generateKindMap() {\n        Map<Kind, Span.Kind> result = new LinkedHashMap<>();\n        // Note: Both Brave and Zipkin treat null kind as a local/in-process span\n        for (Kind kind : Kind.values()) {\n            try {\n                result.put(kind, Span.Kind.valueOf(kind.name()));\n            } catch (RuntimeException e) {\n                logger.warning(\"Could not map Brave kind \" + kind + \" to Zipkin\");\n            }\n        }\n        return result;\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/zipkin2/reporter/brave/ConvertZipkinSpanHandler.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage zipkin2.reporter.brave;\n\nimport brave.handler.SpanHandler;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport zipkin2.reporter.Reporter;\n\npublic class ConvertZipkinSpanHandler extends ZipkinSpanHandler {\n    public static final class Builder extends ZipkinSpanHandler.Builder {\n        final Reporter<ReportSpan> spanReporter;\n\n        Builder(Reporter<ReportSpan> spanReporter) {\n            this.spanReporter = spanReporter;\n        }\n\n        @Override\n        public Builder alwaysReportSpans(boolean alwaysReportSpans) {\n            this.alwaysReportSpans = alwaysReportSpans;\n            return this;\n        }\n\n        // SpanHandler not ZipkinSpanHandler as it can coerce to NOOP\n        public SpanHandler build() {\n            if (spanReporter == null) {\n                return SpanHandler.NOOP;\n            }\n            return new ConvertZipkinSpanHandler(this);\n        }\n\n    }\n\n    public static Builder builder(Reporter<ReportSpan> spanReporter) {\n        if (spanReporter == null) {\n            throw new NullPointerException(\"spanReporter == null\");\n        }\n        return new Builder(spanReporter);\n    }\n\n    ConvertZipkinSpanHandler(Builder builder) {\n        super(new ConvertSpanReporter(builder.spanReporter, builder.errorTag),\n            builder.errorTag, builder.alwaysReportSpans);\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/zipkin2/reporter/kafka11/SDKKafkaSender.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage zipkin2.reporter.kafka11;\n\nimport zipkin2.Call;\nimport zipkin2.CheckResult;\nimport zipkin2.codec.Encoding;\nimport zipkin2.reporter.Sender;\n\nimport java.io.IOException;\nimport java.util.List;\n\npublic class SDKKafkaSender extends Sender implements SDKSender {\n    private final KafkaSender kafkaSender;\n\n    public SDKKafkaSender(KafkaSender kafkaSender) {\n        this.kafkaSender = kafkaSender;\n    }\n\n    public static SDKKafkaSender wrap(KafkaSender sender) {\n        return new SDKKafkaSender(sender);\n    }\n\n    @Override\n    public boolean isClose() {\n        return kafkaSender.closeCalled;\n    }\n\n    @Override\n    public void close() throws IOException {\n        kafkaSender.close();\n    }\n\n    public Call<Void> sendSpans(List<byte[]> encodedSpans) {\n        if (kafkaSender.closeCalled) {\n            throw new IllegalStateException(\"closed\");\n        } else {\n            byte[] message = kafkaSender.encoder.encode(encodedSpans);\n            return kafkaSender.new KafkaCall(message);\n        }\n    }\n\n    public Call<Void> sendSpans(byte[] encodedSpans) {\n        if (kafkaSender.closeCalled) {\n            throw new IllegalStateException(\"closed\");\n        } else {\n            return kafkaSender.new KafkaCall(encodedSpans);\n        }\n    }\n\n    @Override\n    public Encoding encoding() {\n        return kafkaSender.encoding();\n    }\n\n    @Override\n    public int messageMaxBytes() {\n        return kafkaSender.messageMaxBytes();\n    }\n\n    @Override\n    public int messageSizeInBytes(List<byte[]> encodedSpans) {\n        return kafkaSender.messageSizeInBytes(encodedSpans);\n    }\n\n    @Override\n    public CheckResult check() {\n        return kafkaSender.check();\n    }\n\n    @Override\n    public int messageSizeInBytes(int encodedSizeInBytes) {\n        return kafkaSender.messageSizeInBytes(encodedSizeInBytes);\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/zipkin2/reporter/kafka11/SDKSender.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage zipkin2.reporter.kafka11;\n\npublic interface SDKSender {\n\n    boolean isClose();\n}\n"
  },
  {
    "path": "report/src/main/java/zipkin2/reporter/kafka11/SimpleSender.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage zipkin2.reporter.kafka11;\n\nimport com.megaease.easeagent.log4j2.Logger;\nimport com.megaease.easeagent.log4j2.LoggerFactory;\nimport zipkin2.Call;\nimport zipkin2.codec.Encoding;\nimport zipkin2.reporter.BytesMessageEncoder;\nimport zipkin2.reporter.Sender;\n\nimport java.util.List;\n\npublic class SimpleSender extends Sender implements SDKSender {\n    private static final Logger LOGGER = LoggerFactory.getLogger(SimpleSender.class);\n\n    @Override\n    public Encoding encoding() {\n        return Encoding.JSON;\n    }\n\n    @Override\n    public int messageMaxBytes() {\n        return Integer.MAX_VALUE;\n    }\n\n    @Override\n    public int messageSizeInBytes(List<byte[]> encodedSpans) {\n        return encoding().listSizeInBytes(encodedSpans);\n    }\n\n    @Override\n    public Call<Void> sendSpans(List<byte[]> encodedSpans) {\n        final byte[] bytes = BytesMessageEncoder.JSON.encode(encodedSpans);\n        LOGGER.info(\"{}\", new String(bytes));\n        return Call.create(null);\n    }\n\n    @Override\n    public boolean isClose() {\n        return false;\n    }\n}\n"
  },
  {
    "path": "report/src/test/java/com/megaease/easeagent/report/HttpSenderTest.java",
    "content": "/*\n * Copyright (c) 2021 MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.report;\n\nimport com.megaease.easeagent.report.sender.okhttp.HttpSender;\nimport lombok.SneakyThrows;\nimport okhttp3.*;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/**\n * This test class can connect to a test server, please update user pwd cert key\n */\npublic class HttpSenderTest {\n\n    @SneakyThrows\n//    @Test\n    public void perform() {\n\n        OkHttpClient.Builder builder = new OkHttpClient.Builder();\n        HttpSender.appendBasicAuth(builder, user, pwd);\n        HttpSender.appendTLS(builder, this.tlsCaCert, this.tlsCert, this.tlsKey);\n\n        String json = \"{\\\"id\\\":1,\\\"name\\\":\\\"John\\\"}\";\n\n        RequestBody body = RequestBody.create(json, MediaType.parse(\"application/json\"));\n\n        Request request = new Request.Builder()\n            .url(url)\n            .post(body)\n            .build();\n\n        OkHttpClient client = builder.build();\n        Call call = client.newCall(request);\n        Response response = call.execute();\n        System.out.println(response);\n        Assert.assertEquals(200, response.code());\n    }\n\n    private String url = \"\";\n    private String user = \"\";\n    private String pwd = \"\";\n    private String tlsKey = \"\";\n    private String tlsCert = \"\";\n    private String tlsCaCert = \"\";\n\n}\n"
  },
  {
    "path": "report/src/test/java/com/megaease/easeagent/report/async/zipkin/AgentByteBoundedQueueTest.java",
    "content": "package com.megaease.easeagent.report.async.zipkin;\n\nimport com.megaease.easeagent.report.plugin.NoOpEncoder;\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport java.util.UUID;\nimport java.util.concurrent.*;\nimport java.util.concurrent.atomic.AtomicInteger;\n\npublic class AgentByteBoundedQueueTest {\n\n    private AgentByteBoundedQueue<String> queue = new AgentByteBoundedQueue<>(10, 100);\n\n    @Test\n    public void offer() {\n        // 1. Test normal offer execution\n        queue.offer(\"abc\", 5);\n        Assert.assertEquals(\"getCount should be 1\", 1, queue.getCount());\n        // 2. Test situations that exceed the MaxSize\n        for (int i = 0; i < 9; i++) {\n            queue.offer(\"abc\", i);\n        }\n        Assert.assertFalse(\n            \"The last piece of data should not be inserted.\", queue.offer(\"last\", 1));\n        // 3. Test situations that exceed the MaxBytes\n        queue.clear();\n        queue.offer(\"abc\", 5);\n        Assert.assertFalse(\"Beyond maxBytes, data should not be inserted.\", queue.offer(\"last\", 100));\n    }\n\n    @Test\n    public void drainTo() {\n        //1. When the test team is listed as empty, it times out and returns 0.\n        AgentBufferNextMessage<String> consumer = AgentBufferNextMessage.create(new NoOpEncoder<>(), 100, TimeUnit.SECONDS.toNanos(1));\n        int result = queue.drainTo(consumer, TimeUnit.SECONDS.toNanos(1));\n        Assert.assertEquals(\"The queue is empty and the result should be 0 normally.\", 0, result);\n        //2. Normal performance of the test\n        for (int i = 0; i < 10; i++) {\n            queue.offer(\"test\" + i, 1);\n        }\n        result = queue.drainTo(consumer, TimeUnit.SECONDS.toNanos(1));\n        Assert.assertEquals(\"The normal result should be 10..\", 10, result);\n        Assert.assertEquals(\"The queue should be empty now.\", 0, queue.getCount());\n        Assert.assertEquals(\"The number of bytes of the remaining data in the queue should be 0\", 0, queue.getSizeInBytes());\n        //3. add 100 object and then consumer object\n        queue = new AgentByteBoundedQueue<>(10, 100);\n        for (int i = 0; i < 100; i++) {\n            queue.offer(\"test\" + i, 1);\n        }\n        result = queue.drainTo(consumer, TimeUnit.SECONDS.toNanos(1));\n        Assert.assertEquals(\"Teh normal result should be 10..\", 10, result);\n        Assert.assertEquals(\"The queue should be empty now.\", 0, queue.getCount());\n        Assert.assertEquals(\"The number of bytes of the remaining data in the queue should be 0\", 0, queue.getSizeInBytes());\n        Assert.assertEquals(\"The amount of data lost should be 90.\", 90, queue.getLoseCount());\n    }\n\n    @Test\n    public void multiThreadProductConsumerTest() throws InterruptedException {\n        AgentByteBoundedQueue<String> queue = new AgentByteBoundedQueue<>(100, 100);\n        int threadCount = 10;\n        final CountDownLatch latch = new CountDownLatch(threadCount);\n        ExecutorService executorService = Executors.newFixedThreadPool(threadCount);\n        SynchronousQueue<Object> synchronousQueue = new SynchronousQueue<>();\n        AtomicInteger successCount = new AtomicInteger();\n        AtomicInteger loseCount = new AtomicInteger();\n        AtomicInteger consumerCount = new AtomicInteger();\n        for (int i = 0; i < threadCount; i++) {\n            executorService.submit(() -> {\n                for (int j = 0; j < 100; j++) {\n                    if (queue.offer(UUID.randomUUID().toString(), 1)) {\n                        successCount.incrementAndGet();\n                    } else {\n                        loseCount.incrementAndGet();\n                    }\n                }\n                latch.countDown();\n            });\n        }\n\n        new Thread(() -> {\n            AgentBufferNextMessage<String> consumer = AgentBufferNextMessage.create(new NoOpEncoder<>(), 1000000, TimeUnit.SECONDS.toNanos(1));\n            consumerCount.addAndGet(queue.drainTo(consumer, TimeUnit.SECONDS.toNanos(1)));\n            try {\n                latch.await();\n            } catch (InterruptedException e) {\n                throw new RuntimeException(e);\n            }\n            consumerCount.addAndGet(queue.drainTo(consumer, TimeUnit.SECONDS.toNanos(1)));\n            synchronousQueue.offer(new Object());\n        }).start();\n        synchronousQueue.take();\n        Assert.assertEquals(\"The data of the queue should be 0\", 0, queue.getCount());\n        Assert.assertEquals(\"The number of bytes of data stored in the queue should be 0\", 0, queue.getSizeInBytes());\n        Assert.assertTrue(\"There is data loss in the queue. LoseCount should be greater than 0.\", queue.getLoseCount() > 0L);\n        Assert.assertEquals(\"The number of losses within the queue should be consistent with the value of the external record\", loseCount.intValue(), queue.getLoseCount());\n        Assert.assertEquals(\"The amount of consumption should be consistent with the number of successes.\", successCount.intValue(), consumerCount.intValue());\n    }\n\n\n    @Test\n    public void testDatNotLostOnDrainToFail() throws Exception {\n        AgentByteBoundedQueue<String> queue = new AgentByteBoundedQueue<>(1000, 1000);\n        int threadCount = 10;\n        final CountDownLatch latch = new CountDownLatch(threadCount);\n        ExecutorService executorService = Executors.newFixedThreadPool(threadCount);\n        SynchronousQueue<Object> synchronousQueue = new SynchronousQueue<>();\n        AtomicInteger successCount = new AtomicInteger();\n        AtomicInteger loseCount = new AtomicInteger();\n        AtomicInteger consumerCount = new AtomicInteger();\n        for (int i = 0; i < threadCount; i++) {\n            executorService.submit(() -> {\n                for (int j = 0; j < 100; j++) {\n                    if (queue.offer(UUID.randomUUID().toString(), 1)) {\n                        successCount.incrementAndGet();\n                    } else {\n                        loseCount.incrementAndGet();\n                    }\n                }\n                latch.countDown();\n            });\n        }\n\n        new Thread(() -> {\n            AgentBufferNextMessage<String> consumer = AgentBufferNextMessage.create(new NoOpEncoder<>(), 500, TimeUnit.SECONDS.toNanos(1));\n            consumerCount.addAndGet(queue.drainTo(consumer, TimeUnit.SECONDS.toNanos(1)));\n            try {\n                latch.await();\n            } catch (InterruptedException e) {\n                throw new RuntimeException(e);\n            }\n            consumerCount.addAndGet(queue.drainTo(consumer, TimeUnit.SECONDS.toNanos(1)));\n            synchronousQueue.offer(new Object());\n        }).start();\n        synchronousQueue.take();\n        Assert.assertEquals(\"The data of the queue should be 500\", 500, queue.getCount());\n        Assert.assertEquals(\"The number of bytes of data stored in the queue should be 0\", 500, queue.getSizeInBytes());\n        Assert.assertTrue(\"There is data loss in the queue. LoseCount should be greater than 0.\", queue.getLoseCount() == 0L);\n        Assert.assertEquals(\"The amount of consumption should be consistent with the number of successes.\", 500, consumerCount.intValue());\n    }\n\n\n    @Test\n    public void clear() {\n        for (int i = 0; i < 10; i++) {\n            queue.offer(\"test\" + i, 1);\n        }\n        Assert.assertEquals(\"The queue size should be 10\", 10, queue.getCount());\n        queue.clear();\n        Assert.assertEquals(\"The queue size should be 0\", 0, queue.getCount());\n        Assert.assertEquals(\"The number of bytes of data in the queue should be 0\", 0, queue.getSizeInBytes());\n    }\n\n}\n"
  },
  {
    "path": "report/src/test/java/com/megaease/easeagent/report/encoder/log/LogDataJsonEncoderTest.java",
    "content": "/*\n * Copyright (c) 2022, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.report.encoder.log;\n\nimport com.google.common.base.CharMatcher;\nimport com.megaease.easeagent.config.Configs;\nimport com.megaease.easeagent.plugin.api.otlp.common.AgentLogData;\nimport com.megaease.easeagent.plugin.api.otlp.common.AgentLogDataImpl;\nimport com.megaease.easeagent.plugin.report.EncodedData;\nimport com.megaease.easeagent.plugin.utils.common.JsonUtil;\nimport io.opentelemetry.sdk.logs.data.Severity;\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.slf4j.event.Level;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static com.megaease.easeagent.config.report.ReportConfigConst.ENCODER_KEY;\nimport static com.megaease.easeagent.config.report.ReportConfigConst.join;\nimport static com.megaease.easeagent.report.encoder.log.LogDataWriter.LOCATION;\nimport static com.megaease.easeagent.report.encoder.log.LogDataWriter.LOG_LEVEL;\n\npublic class LogDataJsonEncoderTest {\n    Logger log = LoggerFactory.getLogger(LogDataJsonEncoderTest.class);\n    LogDataJsonEncoder encoder;\n    Configs config;\n\n    AgentLogData data;\n\n    @Before\n    public void init() {\n        Map<String, String> cfg = new HashMap<>();\n        cfg.put(ENCODER_KEY, LogDataJsonEncoder.ENCODER_NAME);\n        cfg.put(join(ENCODER_KEY, \"timestamp\"), \"%d{UNIX_MILLIS}\");\n        cfg.put(join(ENCODER_KEY, \"logLevel\"), \"%-5level\");\n        cfg.put(join(ENCODER_KEY, \"threadId\"), \"%thread\");\n        cfg.put(join(ENCODER_KEY, \"location\"), \"%logger{3}\");\n        cfg.put(join(ENCODER_KEY, \"message\"), \"%msg\");\n        cfg.put(\"name\", \"demo-service\");\n        cfg.put(\"system\", \"demo-system\");\n        this.config = new Configs(cfg);\n\n        this.data =\n            AgentLogDataImpl.builder()\n                .epochMills(1648878722451L)\n                .logger(log.getName())\n                .severity(Severity.INFO)\n                .severityText(Level.INFO.toString())\n                .thread(Thread.currentThread())\n                .throwable(new NullPointerException(\"test\"))\n                .body(\"Hello\")\n                .build();\n\n        encoder = new LogDataJsonEncoder();\n        encoder.init(this.config);\n    }\n\n    @Test\n    public void test_encoder() {\n        // size = 208\n        int size = encoder.sizeInBytes(data);\n        Assert.assertEquals(203, size);\n        EncodedData encoded = encoder.encode(data);\n        Map<String, Object> jsonMap = JsonUtil.toMap(new String(encoded.getData()));\n        Assert.assertEquals(\"encoder.log.LogDataJsonEncoderTest\", jsonMap.get(LOCATION));\n        Assert.assertEquals(5, jsonMap.get(LOG_LEVEL).toString().length());\n    }\n\n    @Test\n    public void test_encoder_update() {\n        Map<String, String> changes = new HashMap<>();\n        String original = this.config.getString(join(ENCODER_KEY, \"location\"));\n        changes.put(join(ENCODER_KEY, \"location\"), \"%logger{2}\");\n        this.config.updateConfigs(changes);\n\n        int size = encoder.sizeInBytes(data);\n        Assert.assertEquals(195, size);\n        EncodedData encoded = encoder.encode(data);\n        Map<String, Object> jsonMap = JsonUtil.toMap(new String(encoded.getData()));\n        Assert.assertEquals(\"log.LogDataJsonEncoderTest\", jsonMap.get(LOCATION));\n\n        changes.put(join(ENCODER_KEY, \"location\"), original);\n        this.config.updateConfigs(changes);\n    }\n\n    @Test\n    public void test_throwable_encoder() {\n        Map<String, String> changes = new HashMap<>();\n\n        String key = join(ENCODER_KEY, \"message\");\n        String original = this.config.getString(key);\n        changes.put(key, \"%msg%n%xEx\");\n        this.config.updateConfigs(changes);\n\n        AgentLogData exceptionData = AgentLogDataImpl.builder()\n            .epochMills(1648878722451L)\n            .logger(log.getName())\n            .severity(Severity.INFO)\n            .severityText(Level.INFO.toString())\n            .thread(Thread.currentThread())\n            .throwable(new NullPointerException(\"test\"))\n            .body(\"Hello\")\n            .build();\n\n        EncodedData encoded = encoder.encode(exceptionData);\n        Map<String, Object> jsonMap = JsonUtil.toMap(new String(encoded.getData()));\n\n        Assert.assertTrue(jsonMap.get(\"message\").toString().contains(NullPointerException.class.getCanonicalName()));\n\n        changes.put(key, original);\n        this.config.updateConfigs(changes);\n    }\n\n    @Test\n    public void test_throwable_short_encoder() {\n        Map<String, String> changes = new HashMap<>();\n\n        String key = join(ENCODER_KEY, \"message\");\n        String original = this.config.getString(key);\n        changes.put(key, \"%msg%xEx{5,separator(|)}\");\n        this.config.updateConfigs(changes);\n\n        AgentLogData exceptionData = AgentLogDataImpl.builder()\n            .epochMills(1648878722451L)\n            .logger(log.getName())\n            .severity(Severity.INFO)\n            .severityText(Level.INFO.toString())\n            .thread(Thread.currentThread())\n            .throwable(new NullPointerException(\"test\"))\n            .body(\"Hello\")\n            .build();\n\n        // int size = encoder.sizeInBytes(exceptionData);\n        // Assert.assertEquals(668, size);\n        EncodedData encoded = encoder.encode(exceptionData);\n        Map<String, Object> jsonMap = JsonUtil.toMap(new String(encoded.getData()));\n\n        String message = jsonMap.get(\"message\").toString();\n        Assert.assertTrue(message.contains(NullPointerException.class.getCanonicalName()));\n        int count = CharMatcher.is('|').countIn(message);\n        Assert.assertEquals(4, count);\n\n        changes.put(key, original);\n        this.config.updateConfigs(changes);\n    }\n\n    @Test\n    public void test_mdc_selected() {\n        Map<String, String> changes = new HashMap<>();\n\n        String key = join(ENCODER_KEY, \"message\");\n        String original = this.config.getString(key);\n        changes.put(key, \"%X{name, number}-%msg%xEx{5,separator(|)}\");\n        this.config.updateConfigs(changes);\n\n        AgentLogDataImpl.Builder builder = AgentLogDataImpl.builder()\n            .epochMills(1648878722451L)\n            .logger(log.getName())\n            .severity(Severity.INFO)\n            .severityText(Level.INFO.toString())\n            .thread(Thread.currentThread())\n            .body(\"Hello\");\n\n        AgentLogData noMdcData = builder.build();\n        int size = encoder.sizeInBytes(noMdcData);\n        Assert.assertEquals(206, size);\n        EncodedData encoded = encoder.encode(noMdcData);\n        Map<String, Object> jsonMap = JsonUtil.toMap(new String(encoded.getData()));\n        String message = jsonMap.get(\"message\").toString();\n        Assert.assertTrue(message.startsWith(\"{}-Hello\"));\n\n        // test mdc\n        Map<String, String> ctxData = new HashMap<>();\n        ctxData.put(\"name\", \"easeagent\");\n        ctxData.put(\"number\", \"v2.2\");\n        builder.contextData(null, ctxData);\n        AgentLogData mdcData = builder.build();\n\n        size = encoder.sizeInBytes(mdcData);\n        Assert.assertEquals(233, size);\n        encoded = encoder.encode(mdcData);\n        jsonMap = JsonUtil.toMap(new String(encoded.getData()));\n        message = jsonMap.get(\"message\").toString();\n        Assert.assertTrue(message.startsWith(\"{name=easeagent, number=v2.2}-Hello\"));\n\n        changes.put(key, original);\n        this.config.updateConfigs(changes);\n    }\n\n    @Test\n    public void test_custom() {\n        Map<String, String> original = this.config.getConfigs();\n        Map<String, String> changes = new HashMap<>();\n        changes.put(\"encoder.custom\", \"%X{custom}\");\n\n        this.config.updateConfigs(changes);\n\n        AgentLogDataImpl.Builder builder = AgentLogDataImpl.builder()\n            .epochMills(1648878722451L)\n            .logger(log.getName())\n            .severity(Severity.INFO)\n            .severityText(Level.INFO.toString())\n            .thread(Thread.currentThread())\n            .body(\"Hello\");\n\n        // test mdc\n        Map<String, String> ctxData = new HashMap<>();\n        ctxData.put(\"custom\", \"easeagent\");\n        builder.contextData(null, ctxData);\n        AgentLogData mdcData = builder.build();\n\n        int size = encoder.sizeInBytes(mdcData);\n        Assert.assertEquals(224, size);\n        EncodedData encoded = encoder.encode(mdcData);\n        Map<String, Object> jsonMap = JsonUtil.toMap(new String(encoded.getData()));\n        String custom = jsonMap.get(\"custom\").toString();\n        Assert.assertEquals(\"easeagent\", custom);\n\n        this.config = new Configs(original);\n    }\n}\n"
  },
  {
    "path": "report/src/test/java/com/megaease/easeagent/report/encoder/log/LogDataWriterTest.java",
    "content": "/*\n * Copyright (c) 2022, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.report.encoder.log;\n\nimport com.megaease.easeagent.config.Configs;\nimport com.megaease.easeagent.plugin.api.config.Config;\nimport com.megaease.easeagent.plugin.api.config.IConfigFactory;\nimport com.megaease.easeagent.plugin.api.config.IPluginConfig;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\npublic class LogDataWriterTest {\n    Logger log = LoggerFactory.getLogger(LogDataWriterTest.class);\n    Configs config;\n    @Before\n    public void init() {\n        Map<String, String> cfg = new HashMap<>();\n        cfg.put(\"timestamp\", \"%d{UNIX_MILLIS}\");\n        cfg.put(\"logLevel\", \"%-5level\");\n        cfg.put(\"threadId\", \"%thread\");\n        cfg.put(\"location\", \"%logger{36}\");\n        cfg.put(\"message\", \"%msg\");\n        this.config = new Configs(cfg);\n    }\n\n    @Test\n    public void test_location_pattern() {\n        LogDataWriter writer = new LogDataWriter(config);\n    }\n}\n"
  },
  {
    "path": "report/src/test/java/com/megaease/easeagent/report/metric/MetricPropsTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.report.metric;\n\nimport com.megaease.easeagent.config.Configs;\nimport com.megaease.easeagent.config.GlobalConfigs;\nimport com.megaease.easeagent.config.PluginConfig;\nimport com.megaease.easeagent.plugin.utils.common.StringUtils;\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static com.megaease.easeagent.config.report.ReportConfigConst.*;\n\npublic class MetricPropsTest {\n    @Test\n    public void test_plugin_props() {\n        String testNamespace = \"test\";\n        // test console\n        HashMap<String, String> globalConfig = new HashMap<>();\n        globalConfig.put(\"interval\", \"30\");\n        globalConfig.put(\"topic\", \"application-meter\");\n        globalConfig.put(\"appendType\", \"console\");\n\n        HashMap<String, String> coverConfig = new HashMap<>();\n        coverConfig.put(\"interval\", \"30\");\n        coverConfig.put(\"topic\", \"test-meter\");\n        coverConfig.put(\"appendType\", \"kafka\");\n\n        PluginConfig pluginConfig = PluginConfig.build(\"observability\", \"metric\",\n            globalConfig, testNamespace, coverConfig, null);\n\n        Map<String, String> cfgMap = new HashMap<>();\n\n        cfgMap.put(\"reporter.outputServer.bootstrapServer\", \"http://127.0.0.1:8080/report\");\n        cfgMap.put(\"plugin.observability.global.metric.enabled\", \"true\");\n        cfgMap.put(\"plugin.observability.global.metric.interval\", \"30\");\n        cfgMap.put(\"plugin.observability.global.metric.topic\", \"application-meter\");\n        cfgMap.put(\"plugin.observability.global.metric.appendType\", CONSOLE_SENDER_NAME);\n\n        cfgMap.put(\"plugin.observability.\" + testNamespace + \".metric.topic\", \"test-meter\");\n        cfgMap.put(\"plugin.observability.\" + testNamespace + \".metric.appendType\", \"kafka\");\n        cfgMap.put(\"plugin.observability.\" + testNamespace + \".metric.interval\", \"30\");\n        cfgMap.put(\"plugin.observability.\" + testNamespace + \".metric.enabled\", \"true\");\n\n        cfgMap.put(\"reporter.outputServer.appendType\", \"http\");\n\n        MetricProps props = MetricProps.newDefault(pluginConfig, new GlobalConfigs(cfgMap));\n        Configs reportConfigs = props.asReportConfig();\n        String prefix = props.getSenderPrefix();\n\n        // test kafka sender / topic / interval\n        Assert.assertEquals(METRIC_KAFKA_SENDER_NAME, reportConfigs.getString(join(prefix, APPEND_TYPE_KEY)));\n\n        Assert.assertEquals(\"30\",\n            reportConfigs.getString(join(StringUtils.replaceSuffix(prefix, ASYNC_KEY), INTERVAL_KEY)));\n\n        Assert.assertEquals(\"test-meter\", reportConfigs.getString(join(prefix, TOPIC_KEY)));\n\n        // test console\n        cfgMap.remove(\"plugin.observability.\" + testNamespace + \".metric.appendType\");\n        props = MetricProps.newDefault(pluginConfig, new GlobalConfigs(cfgMap));\n        reportConfigs = props.asReportConfig();\n        Assert.assertEquals(CONSOLE_SENDER_NAME, reportConfigs.getString(join(prefix, APPEND_TYPE_KEY)));\n\n        // test http\n        cfgMap.remove(\"plugin.observability.global.metric.appendType\");\n        props = MetricProps.newDefault(pluginConfig, new GlobalConfigs(cfgMap));\n        reportConfigs = props.asReportConfig();\n        Assert.assertEquals(ZIPKIN_SENDER_NAME, reportConfigs.getString(join(prefix, APPEND_TYPE_KEY)));\n    }\n}\n"
  },
  {
    "path": "report/src/test/java/com/megaease/easeagent/report/metric/MetricReporterFactoryTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.report.metric;\n\nimport com.megaease.easeagent.config.Configs;\nimport com.megaease.easeagent.config.GlobalConfigs;\nimport com.megaease.easeagent.config.PluginConfig;\nimport com.megaease.easeagent.plugin.api.Reporter;\nimport com.megaease.easeagent.plugin.api.config.Config;\nimport com.megaease.easeagent.plugin.report.metric.MetricReporterFactory;\nimport com.megaease.easeagent.report.DefaultAgentReport;\nimport com.megaease.easeagent.report.sender.SenderWithEncoder;\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static com.megaease.easeagent.config.report.ReportConfigConst.*;\n\npublic class MetricReporterFactoryTest {\n    @Test\n    public void global_config_test() {\n        Map<String, String> cfgMap = new HashMap<>();\n        String testNamespace = \"test\";\n\n        cfgMap.put(\"reporter.outputServer.bootstrapServer\", \"http://127.0.0.1:8080/report\");\n        cfgMap.put(\"plugin.observability.global.metric.enabled\", \"true\");\n        cfgMap.put(\"plugin.observability.global.metric.interval\", \"30\");\n        cfgMap.put(\"plugin.observability.global.metric.topic\", \"application-meter\");\n        cfgMap.put(\"plugin.observability.global.metric.appendType\", \"kafka\");\n\n        cfgMap.put(\"plugin.observability.\" + testNamespace + \".metric.topic\", \"test-meter\");\n        cfgMap.put(\"plugin.observability.\" + testNamespace + \".metric.appendType\", \"console\");\n        cfgMap.put(\"plugin.observability.\" + testNamespace + \".metric.interval\", \"30\");\n        cfgMap.put(\"plugin.observability.\" + testNamespace + \".metric.enabled\", \"true\");\n\n        cfgMap.put(\"reporter.outputServer.appendType\", \"http\");\n\n        Configs config = new GlobalConfigs(cfgMap);\n\n        DefaultAgentReport agentReport = (DefaultAgentReport)DefaultAgentReport.create(config);\n        MetricReporterFactory metricReporterFactory = agentReport.metricReporter();\n\n        // --- generate plugin config\n        HashMap<String, String> globalConfig = new HashMap<>();\n        globalConfig.put(\"interval\", \"30\");\n        globalConfig.put(\"topic\", \"application-meter\");\n        globalConfig.put(\"appendType\", \"kafka\");\n\n        HashMap<String, String> coverConfig = new HashMap<>();\n        coverConfig.put(\"interval\", \"30\");\n        coverConfig.put(\"topic\", \"xxx-meter\");\n        coverConfig.put(\"appendType\", \"console\");\n\n        PluginConfig pluginConfig = PluginConfig.build(\"observability\", \"metric\",\n            globalConfig, testNamespace, coverConfig, null);\n\n        Reporter reporter = metricReporterFactory.reporter(pluginConfig);\n        MetricReporterFactoryImpl.DefaultMetricReporter dReporter = (MetricReporterFactoryImpl.DefaultMetricReporter) reporter;\n        String prefix = dReporter.getMetricProps().getSenderPrefix();\n\n        Config cfg = dReporter.getMetricConfig();\n        Assert.assertEquals(\"console\", cfg.getString(join(prefix, APPEND_TYPE_KEY)));\n        Assert.assertEquals(\"test-meter\", cfg.getString(join(prefix, TOPIC_KEY)));\n\n        // change topic\n        Map<String, String> changes = new HashMap<>();\n        changes.put(\"plugin.observability.global.metric.topic\", \"tom\");\n        changes.put(join(join(\"plugin.observability\", testNamespace), \"metric.topic\"), \"john\");\n\n        config.updateConfigs(changes);\n        // dReporter.onChange(pluginConfig, npCfg);\n        Assert.assertEquals(\"john\", cfg.getString(join(prefix, TOPIC_KEY)));\n\n        // change sender\n        changes.put(join(join(\"plugin.observability\", testNamespace), \"metric.appendType\"), \"http\");\n        config.updateConfigs(changes);\n        SenderWithEncoder sender = dReporter.getSender();\n        Assert.assertEquals(\"http\", sender.name());\n    }\n}\n"
  },
  {
    "path": "report/src/test/java/com/megaease/easeagent/report/sender/AgentKafkaSenderTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.report.sender;\n\nimport com.megaease.easeagent.config.Configs;\nimport com.megaease.easeagent.plugin.report.Call;\nimport com.megaease.easeagent.plugin.report.EncodedData;\nimport com.megaease.easeagent.report.plugin.NoOpCall;\nimport com.megaease.easeagent.report.utils.UtilsTest;\nimport org.junit.After;\nimport org.junit.Test;\nimport org.mockito.MockedStatic;\nimport zipkin2.codec.Encoding;\nimport zipkin2.reporter.kafka11.KafkaSender;\nimport zipkin2.reporter.kafka11.SDKKafkaSender;\n\nimport java.io.IOException;\nimport java.util.Map;\n\nimport static com.megaease.easeagent.config.report.ReportConfigConst.KAFKA_SENDER_NAME;\nimport static org.junit.Assert.*;\nimport static org.mockito.Mockito.*;\n\npublic class AgentKafkaSenderTest {\n    MockedStatic<KafkaSender> kafkaSenderMockedStatic;\n    MockedStatic<SDKKafkaSender> sdkKafkaSenderMockedStatic;\n\n\n    @After\n    public void after() {\n        if (kafkaSenderMockedStatic != null) {\n            kafkaSenderMockedStatic.close();\n            kafkaSenderMockedStatic = null;\n        }\n        if (sdkKafkaSenderMockedStatic != null) {\n            sdkKafkaSenderMockedStatic.close();\n            sdkKafkaSenderMockedStatic = null;\n        }\n    }\n\n    @Test\n    public void testName() throws IOException {\n        AgentKafkaSender agentKafkaSender = new AgentKafkaSender();\n        assertEquals(KAFKA_SENDER_NAME, agentKafkaSender.name());\n    }\n\n    @Test\n    public void test_outputServer_disabled() throws IOException, NoSuchFieldException, IllegalAccessException {\n        Configs configs = UtilsTest.readConfigs(\"sender/outputServer_disabled.json\");\n        AgentKafkaSender agentKafkaSender = new AgentKafkaSender();\n        agentKafkaSender.init(configs, \"reporter.log.access.sender\");\n        SDKKafkaSender sender = UtilsTest.getFiled(agentKafkaSender, \"sender\");\n        assertNull(sender);\n        Call result = agentKafkaSender.send(null);\n        assertTrue(result instanceof NoOpCall);\n        assertFalse(agentKafkaSender.isAvailable());\n        agentKafkaSender.close();\n    }\n\n    @Test\n    public void test_outputServer_enabled() throws IOException, NoSuchFieldException, IllegalAccessException {\n        Configs configs = UtilsTest.readConfigs(\"sender/outputServer_enabled.json\");\n        AgentKafkaSender agentKafkaSender = new AgentKafkaSender();\n\n        SDKKafkaSender sdkKafkaSender = mockKafkaSender();\n\n        agentKafkaSender.init(configs, \"reporter.log.access.sender\");\n        SDKKafkaSender sender = UtilsTest.getFiled(agentKafkaSender, \"sender\");\n        assertNotNull(sender);\n        assertSame(sdkKafkaSender, sender);\n        Call result = agentKafkaSender.send(EncodedData.EMPTY);\n        assertTrue(result instanceof ZipkinCallWrapper);\n        assertTrue(agentKafkaSender.isAvailable());\n        agentKafkaSender.close();\n    }\n\n    @Test\n    public void test_outputServer_updateConfigs() throws IOException, NoSuchFieldException, IllegalAccessException {\n        Configs configs = UtilsTest.readConfigs(\"sender/outputServer_disabled.json\");\n        AgentKafkaSender agentKafkaSender = new AgentKafkaSender();\n        agentKafkaSender.init(configs, \"reporter.log.access.sender\");\n        assertFalse(agentKafkaSender.isAvailable());\n\n        Map<String, String> map = UtilsTest.readMap(\"sender/outputServer_enabled.json\");\n        SDKKafkaSender sdkKafkaSender = mockKafkaSender();\n        agentKafkaSender.updateConfigs(map);\n        SDKKafkaSender sender = UtilsTest.getFiled(agentKafkaSender, \"sender\");\n        assertNotNull(sender);\n        assertSame(sdkKafkaSender, sender);\n    }\n\n    @Test\n    public void test_outputServer_empty_bootstrapServer_updateConfigs() throws IOException, NoSuchFieldException, IllegalAccessException {\n        Configs configs = UtilsTest.readConfigs(\"sender/outputServer_empty_bootstrapServer_disabled.json\");\n        AgentKafkaSender agentKafkaSender = new AgentKafkaSender();\n        agentKafkaSender.init(configs, \"reporter.log.access.sender\");\n        assertFalse(agentKafkaSender.isAvailable());\n\n        {\n            Map<String, String> map = UtilsTest.readMap(\"sender/outputServer_empty_bootstrapServer_enabled.json\");\n            agentKafkaSender.updateConfigs(map);\n            SDKKafkaSender sender = UtilsTest.getFiled(agentKafkaSender, \"sender\");\n            assertNull(sender);\n        }\n\n        {\n            Map<String, String> map = UtilsTest.readMap(\"sender/outputServer_enabled.json\");\n            SDKKafkaSender sdkKafkaSender = mockKafkaSender();\n            agentKafkaSender.updateConfigs(map);\n            SDKKafkaSender sender = UtilsTest.getFiled(agentKafkaSender, \"sender\");\n            assertNotNull(sender);\n            assertSame(sdkKafkaSender, sender);\n        }\n\n    }\n\n    private SDKKafkaSender mockKafkaSender() {\n        KafkaSender.Builder builder = mock(KafkaSender.Builder.class);\n        KafkaSender kafkaSender = mock(KafkaSender.class);\n        kafkaSenderMockedStatic = mockStatic(KafkaSender.class);\n        when(KafkaSender.newBuilder()).thenReturn(builder);\n        when(builder.build()).thenReturn(kafkaSender);\n        when(builder.bootstrapServers(anyString())).thenReturn(builder);\n        when(builder.topic(anyString())).thenReturn(builder);\n        when(builder.overrides(anyMap())).thenReturn(builder);\n        when(builder.encoding(Encoding.JSON)).thenReturn(builder);\n        when(builder.messageMaxBytes(anyInt())).thenReturn(builder);\n        when(builder.build()).thenReturn(kafkaSender);\n\n        SDKKafkaSender sdkKafkaSender = mock(SDKKafkaSender.class);\n        sdkKafkaSenderMockedStatic = mockStatic(SDKKafkaSender.class);\n        when(SDKKafkaSender.wrap(kafkaSender)).thenReturn(sdkKafkaSender);\n        return sdkKafkaSender;\n    }\n\n\n}\n"
  },
  {
    "path": "report/src/test/java/com/megaease/easeagent/report/sender/metric/log4j/AppenderManagerTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.report.sender.metric.log4j;\n\nimport com.megaease.easeagent.config.Configs;\nimport com.megaease.easeagent.report.OutputProperties;\nimport com.megaease.easeagent.report.utils.UtilsTest;\nimport org.apache.logging.log4j.core.Appender;\nimport org.apache.logging.log4j.core.Layout;\nimport org.apache.logging.log4j.core.appender.NullAppender;\nimport org.apache.logging.log4j.core.appender.mom.kafka.KafkaAppender;\nimport org.apache.logging.log4j.core.config.Configuration;\nimport org.apache.logging.log4j.core.config.Property;\nimport org.junit.Test;\nimport org.mockito.MockedStatic;\n\nimport java.io.IOException;\n\nimport static org.junit.Assert.*;\nimport static org.mockito.Mockito.*;\n\npublic class AppenderManagerTest {\n\n    @Test\n    public void create() throws IOException {\n        Configs configs = UtilsTest.readConfigs(\"sender/outputServer_disabled.json\");\n        OutputProperties outputProperties = OutputProperties.newDefault(configs);\n        AppenderManager appenderManager = AppenderManager.create(outputProperties);\n\n        {\n            Appender appender = appenderManager.appender(\"testTopic\");\n            assertTrue(appender instanceof NullAppender);\n        }\n\n        outputProperties.updateConfig(UtilsTest.readMap(\"sender/outputServer_empty_bootstrapServer_enabled.json\"));\n        appenderManager.refresh();\n\n        {\n            Appender appender = appenderManager.appender(\"testTopic\");\n            assertNull(appender);\n        }\n\n        outputProperties.updateConfig(UtilsTest.readMap(\"sender/outputServer_enabled.json\"));\n        appenderManager.refresh();\n\n        {\n            MockedStatic<KafkaAppender> kafkaAppenderMockedStatic = mockStatic(KafkaAppender.class);\n            KafkaAppender.Builder builder = mock(KafkaAppender.Builder.class);\n            when(KafkaAppender.newBuilder()).thenReturn(builder);\n            when(builder.setTopic(anyString())).thenReturn(builder);\n            when(builder.setSyncSend(anyBoolean())).thenReturn(builder);\n            when(builder.setName(anyString())).thenReturn(builder);\n            when(builder.setPropertyArray(any(Property[].class))).thenReturn(builder);\n            when(builder.setLayout(any(Layout.class))).thenReturn(builder);\n            when(builder.setConfiguration(any(Configuration.class))).thenReturn(builder);\n            KafkaAppender mockAppender = mock(KafkaAppender.class);\n            when(builder.build()).thenReturn(mockAppender);\n\n            Appender appender = appenderManager.appender(\"testTopic\");\n            assertSame(mockAppender, appender);\n            kafkaAppenderMockedStatic.close();\n        }\n\n    }\n}\n"
  },
  {
    "path": "report/src/test/java/com/megaease/easeagent/report/trace/TraceReportTest.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.report.trace;\n\nimport com.megaease.easeagent.config.Configs;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.config.report.ReportConfigAdapter;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport com.megaease.easeagent.report.plugin.ReporterLoader;\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.Test;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.concurrent.TimeUnit;\n\npublic class TraceReportTest {\n    @Before\n    public void before() {\n        ReporterLoader.load();\n    }\n\n    @Test\n    public void test1() throws InterruptedException {\n        final HashMap<String, String> source = new HashMap<>();\n        source.put(ConfigConst.SERVICE_NAME, \"test-service\");\n        source.put(ConfigConst.Observability.TRACE_ENABLED, \"true\");\n        source.put(ConfigConst.Observability.TRACE_OUTPUT_ENABLED, \"false\");\n        source.put(ConfigConst.Observability.TRACE_OUTPUT_TOPIC, \"log-tracing\");\n        source.put(ConfigConst.Observability.TRACE_OUTPUT_REPORT_THREAD, \"1\");\n        source.put(ConfigConst.Observability.TRACE_OUTPUT_MESSAGE_TIMEOUT, \"1000\");\n        source.put(ConfigConst.Observability.TRACE_OUTPUT_MESSAGE_MAX_BYTES, \"999900\");\n        source.put(ConfigConst.Observability.TRACE_OUTPUT_QUEUED_MAX_SIZE, \"1000000\");\n        source.put(ConfigConst.Observability.TRACE_OUTPUT_QUEUED_MAX_SPANS, \"1000\");\n        Map<String, String> cfg = ReportConfigAdapter.extractReporterConfig(new Configs(source));\n        final TraceReport report = new TraceReport(new Configs(cfg));\n        final ReportSpan build = ReportSpanBuilder.newBuilder()\n                .traceId(\"122332\")\n                .id(1L)\n                .timestamp(10000)\n                .build();\n        report.report(build);\n        TimeUnit.SECONDS.sleep(3);\n        Assert.assertTrue(true);\n    }\n\n    @Test\n    public void test2() throws InterruptedException {\n        final HashMap<String, String> source = new HashMap<>();\n        source.put(ConfigConst.SERVICE_NAME, \"test-service\");\n        source.put(ConfigConst.Observability.OUTPUT_SERVERS, \"127.0.0.1:9093\");\n        source.put(ConfigConst.Observability.OUTPUT_TIMEOUT, \"10000\");\n        source.put(ConfigConst.Observability.TRACE_ENABLED, \"true\");\n        source.put(ConfigConst.Observability.TRACE_OUTPUT_ENABLED, \"true\");\n        source.put(ConfigConst.Observability.TRACE_OUTPUT_TOPIC, \"log-tracing\");\n        source.put(ConfigConst.Observability.TRACE_OUTPUT_REPORT_THREAD, \"1\");\n        source.put(ConfigConst.Observability.TRACE_OUTPUT_MESSAGE_TIMEOUT, \"1000\");\n        source.put(ConfigConst.Observability.TRACE_OUTPUT_MESSAGE_MAX_BYTES, \"999900\");\n        source.put(ConfigConst.Observability.TRACE_OUTPUT_QUEUED_MAX_SIZE, \"1000000\");\n        source.put(ConfigConst.Observability.TRACE_OUTPUT_QUEUED_MAX_SPANS, \"1000\");\n        Map<String, String> cfg = ReportConfigAdapter.extractReporterConfig(new Configs(source));\n        final TraceReport report = new TraceReport(new Configs(cfg));\n        final ReportSpan build = ReportSpanBuilder.newBuilder()\n                .traceId(\"122332\")\n                .id(1L)\n                .timestamp(10000)\n                .build();\n        report.report(build);\n        TimeUnit.SECONDS.sleep(3);\n        Assert.assertTrue(true);\n    }\n}\n"
  },
  {
    "path": "report/src/test/java/com/megaease/easeagent/report/utils/UtilsTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.report.utils;\n\nimport com.megaease.easeagent.config.Configs;\nimport com.megaease.easeagent.plugin.utils.common.JsonUtil;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.lang.reflect.Field;\nimport java.util.Map;\n\npublic class UtilsTest {\n\n    public static Map<String, String> readMap(String path) throws IOException {\n        String config = UtilsTest.readFromResourcePath(path);\n        return (Map<String, String>) (Map) JsonUtil.toMap(config);\n    }\n\n    public static Configs readConfigs(String path) throws IOException {\n        return new Configs(readMap(path));\n    }\n\n    public static String readFromResourcePath(String resourcePath) throws IOException {\n        try (InputStream in = ClassLoader.getSystemResource(resourcePath).openStream()) {\n            int size = in.available();\n            byte[] bytes = new byte[size];\n            in.read(bytes, 0, size);\n            return new String(bytes);\n        }\n    }\n\n    public static <T> T getFiled(Object o, String fieldName) throws IllegalAccessException, NoSuchFieldException {\n        Field field = o.getClass().getDeclaredField(fieldName);\n        field.setAccessible(true);\n        Object result = field.get(o);\n        field.setAccessible(false);\n        return (T) result;\n    }\n}\n"
  },
  {
    "path": "report/src/test/resources/sender/outputServer_disabled.json",
    "content": "{\n    \"reporter.log.access.encoder\": \"AccessLogJsonEncoder\",\n    \"reporter.log.access.encoder.location\": \"%logger{36}\",\n    \"reporter.log.access.encoder.logLevel\": \"%-5level\",\n    \"reporter.log.access.encoder.message\": \"%msg%n%xEx{3}\",\n    \"reporter.log.access.encoder.threadId\": \"%thread\",\n    \"reporter.log.access.encoder.timestamp\": \"%d{UNIX_MILLIS}\",\n    \"reporter.log.access.output.messageMaxBytes\": \"999900\",\n    \"reporter.log.access.output.messageTimeout\": \"1000\",\n    \"reporter.log.access.output.queuedMaxSize\": \"1000000\",\n    \"reporter.log.access.output.queuedMaxSpans\": \"1000\",\n    \"reporter.log.access.output.reportThread\": \"1\",\n    \"reporter.log.access.sender.appendType\": \"kafka\",\n    \"reporter.log.access.sender.enabled\": \"true\",\n    \"reporter.log.access.sender.level\": \"INFO\",\n    \"reporter.log.access.sender.topic\": \"application-log\",\n    \"reporter.log.access.sender.url\": \"/application-log\",\n    \"reporter.log.output.messageMaxBytes\": \"999900\",\n    \"reporter.log.output.messageTimeout\": \"1000\",\n    \"reporter.log.output.queuedMaxSize\": \"1000000\",\n    \"reporter.log.output.queuedMaxSpans\": \"1000\",\n    \"reporter.log.output.reportThread\": \"1\",\n    \"reporter.outputServer.appendType\": \"kafka\",\n    \"reporter.outputServer.bootstrapServer\": \"127.0.0.1\",\n    \"reporter.outputServer.enabled\": \"false\",\n    \"reporter.outputServer.password\": \"\",\n    \"reporter.outputServer.timeout\": \"1000\",\n    \"reporter.outputServer.tls.ca_cert\": \"\",\n    \"reporter.outputServer.tls.cert\": \"\",\n    \"reporter.outputServer.tls.enable\": \"false\",\n    \"reporter.outputServer.tls.key\": \"\",\n    \"reporter.outputServer.username\": \"\"\n}\n"
  },
  {
    "path": "report/src/test/resources/sender/outputServer_empty_bootstrapServer_disabled.json",
    "content": "{\n    \"reporter.log.access.encoder\": \"AccessLogJsonEncoder\",\n    \"reporter.log.access.encoder.location\": \"%logger{36}\",\n    \"reporter.log.access.encoder.logLevel\": \"%-5level\",\n    \"reporter.log.access.encoder.message\": \"%msg%n%xEx{3}\",\n    \"reporter.log.access.encoder.threadId\": \"%thread\",\n    \"reporter.log.access.encoder.timestamp\": \"%d{UNIX_MILLIS}\",\n    \"reporter.log.access.output.messageMaxBytes\": \"999900\",\n    \"reporter.log.access.output.messageTimeout\": \"1000\",\n    \"reporter.log.access.output.queuedMaxSize\": \"1000000\",\n    \"reporter.log.access.output.queuedMaxSpans\": \"1000\",\n    \"reporter.log.access.output.reportThread\": \"1\",\n    \"reporter.log.access.sender.appendType\": \"kafka\",\n    \"reporter.log.access.sender.enabled\": \"true\",\n    \"reporter.log.access.sender.level\": \"INFO\",\n    \"reporter.log.access.sender.topic\": \"application-log\",\n    \"reporter.log.access.sender.url\": \"/application-log\",\n    \"reporter.log.output.messageMaxBytes\": \"999900\",\n    \"reporter.log.output.messageTimeout\": \"1000\",\n    \"reporter.log.output.queuedMaxSize\": \"1000000\",\n    \"reporter.log.output.queuedMaxSpans\": \"1000\",\n    \"reporter.log.output.reportThread\": \"1\",\n    \"reporter.outputServer.appendType\": \"kafka\",\n    \"reporter.outputServer.bootstrapServer\": \"\",\n    \"reporter.outputServer.enabled\": \"false\",\n    \"reporter.outputServer.password\": \"\",\n    \"reporter.outputServer.timeout\": \"1000\",\n    \"reporter.outputServer.tls.ca_cert\": \"\",\n    \"reporter.outputServer.tls.cert\": \"\",\n    \"reporter.outputServer.tls.enable\": \"false\",\n    \"reporter.outputServer.tls.key\": \"\",\n    \"reporter.outputServer.username\": \"\"\n}\n"
  },
  {
    "path": "report/src/test/resources/sender/outputServer_empty_bootstrapServer_enabled.json",
    "content": "{\n    \"reporter.log.access.encoder\": \"AccessLogJsonEncoder\",\n    \"reporter.log.access.encoder.location\": \"%logger{36}\",\n    \"reporter.log.access.encoder.logLevel\": \"%-5level\",\n    \"reporter.log.access.encoder.message\": \"%msg%n%xEx{3}\",\n    \"reporter.log.access.encoder.threadId\": \"%thread\",\n    \"reporter.log.access.encoder.timestamp\": \"%d{UNIX_MILLIS}\",\n    \"reporter.log.access.output.messageMaxBytes\": \"999900\",\n    \"reporter.log.access.output.messageTimeout\": \"1000\",\n    \"reporter.log.access.output.queuedMaxSize\": \"1000000\",\n    \"reporter.log.access.output.queuedMaxSpans\": \"1000\",\n    \"reporter.log.access.output.reportThread\": \"1\",\n    \"reporter.log.access.sender.appendType\": \"kafka\",\n    \"reporter.log.access.sender.enabled\": \"true\",\n    \"reporter.log.access.sender.level\": \"INFO\",\n    \"reporter.log.access.sender.topic\": \"application-log\",\n    \"reporter.log.access.sender.url\": \"/application-log\",\n    \"reporter.log.output.messageMaxBytes\": \"999900\",\n    \"reporter.log.output.messageTimeout\": \"1000\",\n    \"reporter.log.output.queuedMaxSize\": \"1000000\",\n    \"reporter.log.output.queuedMaxSpans\": \"1000\",\n    \"reporter.log.output.reportThread\": \"1\",\n    \"reporter.outputServer.appendType\": \"kafka\",\n    \"reporter.outputServer.bootstrapServer\": \"\",\n    \"reporter.outputServer.enabled\": \"true\",\n    \"reporter.outputServer.password\": \"\",\n    \"reporter.outputServer.timeout\": \"1000\",\n    \"reporter.outputServer.tls.ca_cert\": \"\",\n    \"reporter.outputServer.tls.cert\": \"\",\n    \"reporter.outputServer.tls.enable\": \"false\",\n    \"reporter.outputServer.tls.key\": \"\",\n    \"reporter.outputServer.username\": \"\"\n}\n"
  },
  {
    "path": "report/src/test/resources/sender/outputServer_enabled.json",
    "content": "{\n    \"reporter.log.access.encoder\": \"AccessLogJsonEncoder\",\n    \"reporter.log.access.encoder.location\": \"%logger{36}\",\n    \"reporter.log.access.encoder.logLevel\": \"%-5level\",\n    \"reporter.log.access.encoder.message\": \"%msg%n%xEx{3}\",\n    \"reporter.log.access.encoder.threadId\": \"%thread\",\n    \"reporter.log.access.encoder.timestamp\": \"%d{UNIX_MILLIS}\",\n    \"reporter.log.access.output.messageMaxBytes\": \"999900\",\n    \"reporter.log.access.output.messageTimeout\": \"1000\",\n    \"reporter.log.access.output.queuedMaxSize\": \"1000000\",\n    \"reporter.log.access.output.queuedMaxSpans\": \"1000\",\n    \"reporter.log.access.output.reportThread\": \"1\",\n    \"reporter.log.access.sender.appendType\": \"kafka\",\n    \"reporter.log.access.sender.enabled\": \"true\",\n    \"reporter.log.access.sender.level\": \"INFO\",\n    \"reporter.log.access.sender.topic\": \"application-log\",\n    \"reporter.log.access.sender.url\": \"/application-log\",\n    \"reporter.log.output.messageMaxBytes\": \"999900\",\n    \"reporter.log.output.messageTimeout\": \"1000\",\n    \"reporter.log.output.queuedMaxSize\": \"1000000\",\n    \"reporter.log.output.queuedMaxSpans\": \"1000\",\n    \"reporter.log.output.reportThread\": \"1\",\n    \"reporter.outputServer.appendType\": \"kafka\",\n    \"reporter.outputServer.bootstrapServer\": \"127.0.0.1\",\n    \"reporter.outputServer.enabled\": \"true\",\n    \"reporter.outputServer.password\": \"\",\n    \"reporter.outputServer.timeout\": \"1000\",\n    \"reporter.outputServer.tls.ca_cert\": \"\",\n    \"reporter.outputServer.tls.cert\": \"\",\n    \"reporter.outputServer.tls.enable\": \"false\",\n    \"reporter.outputServer.tls.key\": \"\",\n    \"reporter.outputServer.username\": \"\"\n}\n"
  },
  {
    "path": "resources/rootfs/Dockerfile",
    "content": "FROM megaease/easeimg-javabuild:latest AS builder\n\nARG      REPOSITORY1\nARG      REPOSITORY2\nARG      REPOSITORY3\nARG      MIRROR1\nARG      MIRROR2\nARG      MIRROR3\nARG      SERVER1\nARG      SERVER2\nARG      SERVER3\n\nCOPY     ./ /easeagent/\nWORKDIR  /easeagent\n\nRUN     /bin/rewrite-settings.sh &&  cd /easeagent/ && mvn clean package && cd build/target/ && jar xf easeagent.jar easeagent-log4j2.xml\n\nFROM alpine:latest\nRUN apk --no-cache add curl wget\n\n\nCOPY --from=builder /easeagent/build/target/easeagent-dep.jar   /easeagent-volume/easeagent.jar\nCOPY --from=builder /easeagent/build/target/easeagent-log4j2.xml   /easeagent-volume/easeagent-log4j2.xml\n"
  },
  {
    "path": "resources/scripts/Jenkinsfile",
    "content": "package resources.scripts\n\n@Library(value='JenkinsHelper', changelog=false) _\n\nupdateJavaBuild()"
  },
  {
    "path": "resources/scripts/build-image.sh",
    "content": "#!/bin/bash\n\nset -e\n\npushd $(dirname $0) > /dev/null\nSCRIPTPATH=$(pwd -P)\npopd > /dev/null\n\nbuild_image() {\n    local img_name=$1\n    local img_ver=$2\n    local docker_context_path=$3\n    local docker_file=$4\n\n    docker build -t \"${img_name}:${img_ver}\" \\\n           -f \"${docker_file}\" \\\n           --build-arg REPOSITORY1 \\\n           --build-arg REPOSITORY2 \\\n           --build-arg REPOSITORY3 \\\n           --build-arg MIRROR1 \\\n           --build-arg MIRROR2 \\\n           --build-arg MIRROR3 \\\n           --build-arg SERVER1 \\\n           --build-arg SERVER2 \\\n           --build-arg SERVER3 \\\n           \"${docker_context_path}\"\n}\n\nshow_usage() {\n   printf \"usage: build_image.sh [-i IMAGE_NAME] [-v IMAGE_VERSION] -d ROOT_RESPOSITORY_DIR [-f DOCKER_FILE] \\n\"\n   printf \"\\t-i IMAGE_NAME the name of building image, default is megaease/easeagent\\n\"\n   printf \"\\t-v IMAGE_VERSION the version of building image, default is 0.1.0-alpine\\n\"\n   printf \"\\t-d ROOT_RESPOSITORY_DIR the root directory of repository\\n\"\n   printf \"\\t-f DOCKER_FILE the location of Dockerfile, if it's ommited, default location is ROOT_RESPOSITORY_DIR/resources/rootfs/Dockerfile\\n\"\n   printf \"\"\n}\n\nif [ \"${IMAGE_NAME:-x}\" == \"x\" ];\nthen\n   IMAGE_NAME=megaease/easeservice-alert\nfi\n\nif [ \"${IMAGE_VERSION:-alpine-0.0.1}\" != \"alpine-0.0.1\" ];\nthen\n   IMAGE_VERSION=\"alpine-${IMAGE_VERSION}\"\nfi\n\nIMAGE_VERSION=${IMAGE_VERSION:-alpine-0.0.1}\n\n\nwhile getopts 'hi:v:d:f:' flag; do\n  case \"${flag}\" in\n    h) show_usage; exit ;;\n    i) IMAGE_NAME=${OPTARG};;\n    v) IMAGE_VERSION=${OPTARG};;\n    d) REPOSITORY_DIR=${OPTARG};;\n    f) DOCKER_FILE=${OPTARG};;\n    *) show_usage; exit ;;\n  esac\ndone\n\nif [ -z \"${REPOSITORY_DIR}\" ]; then\n   show_usage\n   exit 1\nfi\n\nif [ -z \"${DOCKER_FILE}\" ];\nthen\n    DOCKER_FILE=${REPOSITORY_DIR}/resources/rootfs/Dockerfile\nfi\n\n\nbuild_image \"${IMAGE_NAME}\" \\\n\t\"${IMAGE_VERSION}\" \\\n\t\"${REPOSITORY_DIR}/\" \\\n  \"${DOCKER_FILE}\""
  },
  {
    "path": "zipkin/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  Copyright (c) 2017, MegaEase\n  All rights reserved.\n\n  Licensed under the Apache License, Version 2.0 (the \"License\");\n  you may not use this file except in compliance with the License.\n  You may obtain a copy of the License at\n\n      http://www.apache.org/licenses/LICENSE-2.0\n\n  Unless required by applicable law or agreed to in writing, software\n  distributed under the License is distributed on an \"AS IS\" BASIS,\n  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  See the License for the specific language governing permissions and\n  limitations under the License.\n-->\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>easeagent</artifactId>\n        <groupId>com.megaease.easeagent</groupId>\n        <version>2.3.0</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>zipkin</artifactId>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>config</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>report</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>io.zipkin.brave</groupId>\n            <artifactId>brave</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>io.zipkin.brave</groupId>\n            <artifactId>brave-instrumentation-messaging</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>io.zipkin.brave</groupId>\n            <artifactId>brave-instrumentation-rpc</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>io.zipkin.reporter2</groupId>\n            <artifactId>zipkin-sender-urlconnection</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>org.slf4j</groupId>\n            <artifactId>slf4j-api</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>utils-mock</artifactId>\n            <version>${project.version}</version>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>log4j2-mock</artifactId>\n            <version>${project.version}</version>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>report-mock</artifactId>\n            <version>${project.version}</version>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.megaease.easeagent</groupId>\n            <artifactId>context-mock</artifactId>\n            <version>${project.version}</version>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.apache.logging.log4j</groupId>\n            <artifactId>log4j-slf4j-impl</artifactId>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.apache.logging.log4j</groupId>\n            <artifactId>log4j-core</artifactId>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>ch.qos.logback</groupId>\n            <artifactId>logback-core</artifactId>\n            <version>1.2.9</version>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>ch.qos.logback</groupId>\n            <artifactId>logback-access</artifactId>\n            <version>1.1.7</version>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>ch.qos.logback</groupId>\n            <artifactId>logback-classic</artifactId>\n            <version>1.2.0</version>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.github.dreamhead</groupId>\n            <artifactId>moco-core</artifactId>\n            <scope>test</scope>\n        </dependency>\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "zipkin/src/main/java/com/megaease/easeagent/zipkin/CustomTagsSpanHandler.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.zipkin;\n\nimport brave.handler.MutableSpan;\nimport brave.handler.SpanHandler;\nimport brave.propagation.TraceContext;\nimport com.megaease.easeagent.plugin.api.ProgressFields;\nimport com.megaease.easeagent.plugin.api.middleware.RedirectProcessor;\n\nimport java.util.Map;\nimport java.util.function.Supplier;\n\npublic class CustomTagsSpanHandler extends SpanHandler {\n    public static final String TAG_INSTANCE = \"i\";\n    private final String instance;\n    private final Supplier<String> serviceName;\n\n    public CustomTagsSpanHandler(Supplier<String> serviceName, String instance) {\n        this.serviceName = serviceName;\n        this.instance = instance;\n    }\n\n    @Override\n    public boolean end(TraceContext context, MutableSpan span, Cause cause) {\n        span.tag(TAG_INSTANCE, this.instance);\n        span.localServiceName(this.serviceName.get());\n        fillTags(span, ProgressFields.getServiceTags());\n        fillTags(span, RedirectProcessor.tags());\n        return true;\n    }\n\n    protected void fillTags(MutableSpan span, Map<String, String> tags) {\n        if (tags == null || tags.isEmpty()) {\n            return;\n        }\n        for (Map.Entry<String, String> entry : tags.entrySet()) {\n            span.tag(entry.getKey(), entry.getValue());\n        }\n    }\n}\n"
  },
  {
    "path": "zipkin/src/main/java/com/megaease/easeagent/zipkin/TracingProviderImpl.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.zipkin;\n\nimport brave.Tracing;\nimport brave.propagation.ThreadLocalCurrentTraceContext;\nimport brave.sampler.BoundarySampler;\nimport brave.sampler.CountingSampler;\nimport brave.sampler.RateLimitingSampler;\nimport brave.sampler.Sampler;\nimport com.megaease.easeagent.config.AutoRefreshConfigItem;\nimport com.megaease.easeagent.config.ConfigAware;\nimport com.megaease.easeagent.log4j2.Logger;\nimport com.megaease.easeagent.log4j2.LoggerFactory;\nimport com.megaease.easeagent.plugin.annotation.Injection;\nimport com.megaease.easeagent.plugin.api.config.Config;\nimport com.megaease.easeagent.plugin.api.config.ConfigConst;\nimport com.megaease.easeagent.plugin.api.trace.ITracing;\nimport com.megaease.easeagent.plugin.api.trace.TracingProvider;\nimport com.megaease.easeagent.plugin.api.trace.TracingSupplier;\nimport com.megaease.easeagent.plugin.bean.AgentInitializingBean;\nimport com.megaease.easeagent.plugin.bean.BeanProvider;\nimport com.megaease.easeagent.plugin.report.AgentReport;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport com.megaease.easeagent.plugin.utils.AdditionalAttributes;\nimport com.megaease.easeagent.report.AgentReportAware;\nimport com.megaease.easeagent.zipkin.impl.TracingImpl;\nimport com.megaease.easeagent.zipkin.logging.AgentMDCScopeDecorator;\nimport zipkin2.reporter.Reporter;\nimport zipkin2.reporter.brave.ConvertZipkinSpanHandler;\n\npublic class TracingProviderImpl implements BeanProvider, AgentReportAware, ConfigAware, AgentInitializingBean, TracingProvider {\n    private static final Logger LOGGER = LoggerFactory.getLogger(TracingProviderImpl.class);\n    private static final String ENV_ZIPKIN_SERVER_URL = \"ZIPKIN_SERVER_URL\";\n\n    public static final String SAMPLER_TYPE_COUNTING = \"counting\";\n    public static final String SAMPLER_TYPE_RATE_LIMITING = \"rate_limiting\";\n    public static final String SAMPLER_TYPE_BOUNDARY = \"boundary\";\n    private Tracing tracing;\n    private volatile ITracing iTracing;\n    private AgentReport agentReport;\n    private Config config;\n    private AutoRefreshConfigItem<String> serviceName;\n\n\n    @Override\n    public void setConfig(Config config) {\n        this.config = config;\n    }\n\n    @Override\n    public void setAgentReport(AgentReport report) {\n        this.agentReport = report;\n    }\n\n    @Override\n    public void afterPropertiesSet() {\n        ThreadLocalCurrentTraceContext traceContext = ThreadLocalCurrentTraceContext.newBuilder()\n            .addScopeDecorator(AgentMDCScopeDecorator.get())\n            .addScopeDecorator(AgentMDCScopeDecorator.getV2())\n            .addScopeDecorator(AgentMDCScopeDecorator.getAgentDecorator())\n            .build();\n\n        serviceName = new AutoRefreshConfigItem<>(config, ConfigConst.SERVICE_NAME, Config::getString);\n\n        Reporter<ReportSpan> reporter;\n        reporter = span -> agentReport.report(span);\n        this.tracing = Tracing.newBuilder()\n            .localServiceName(getServiceName())\n            .traceId128Bit(false)\n            .sampler(getSampler())\n            .addSpanHandler(new CustomTagsSpanHandler(this::getServiceName, AdditionalAttributes.getHostName()))\n            .addSpanHandler(ConvertZipkinSpanHandler\n                .builder(reporter)\n                .alwaysReportSpans(true)\n                .build()\n            )\n            .currentTraceContext(traceContext)\n            .build();\n    }\n\n\n    protected Sampler getSampler() {\n        String sampledType = this.config.getString(ConfigConst.Observability.TRACE_SAMPLED_TYPE);\n        if (sampledType == null) {\n            return Sampler.ALWAYS_SAMPLE;\n        }\n        Double probability = this.config.getDouble(ConfigConst.Observability.TRACE_SAMPLED);\n        if (probability == null) {\n            return Sampler.ALWAYS_SAMPLE;\n        }\n        try {\n            switch (sampledType) {\n                case SAMPLER_TYPE_COUNTING:\n                    return CountingSampler.create(probability.floatValue());\n                case SAMPLER_TYPE_RATE_LIMITING:\n                    return RateLimitingSampler.create(probability.intValue());\n                case SAMPLER_TYPE_BOUNDARY:\n                    return BoundarySampler.create(probability.floatValue());\n                default:\n                    return Sampler.ALWAYS_SAMPLE;\n            }\n        } catch (IllegalArgumentException e) {\n            LOGGER.warn(\"observability.tracings.sampled error, use Sampler.ALWAYS_SAMPLE for.\", e.getMessage());\n            return Sampler.ALWAYS_SAMPLE;\n        }\n    }\n\n\n    @Injection.Bean\n    public Tracing tracing() {\n        return tracing;\n    }\n\n    @Override\n    public TracingSupplier tracingSupplier() {\n        return supplier -> {\n            if (iTracing != null) {\n                return iTracing;\n            }\n            synchronized (TracingProviderImpl.class) {\n                if (iTracing != null) {\n                    return iTracing;\n                }\n                iTracing = TracingImpl.build(supplier, tracing);\n            }\n            return iTracing;\n        };\n    }\n\n    private String getServiceName() {\n        return this.serviceName.getValue();\n    }\n}\n"
  },
  {
    "path": "zipkin/src/main/java/com/megaease/easeagent/zipkin/impl/AsyncRequest.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.zipkin.impl;\n\nimport com.megaease.easeagent.plugin.api.trace.Request;\nimport com.megaease.easeagent.plugin.api.trace.Span;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\npublic class AsyncRequest implements Request {\n    private final Request request;\n    private final Map<String, String> header;\n\n    public AsyncRequest(Request request) {\n        this.request = request;\n        this.header = new HashMap<>();\n    }\n\n    @Override\n    public Span.Kind kind() {\n        return request.kind();\n    }\n\n    @Override\n    public String name() {\n        return request.name();\n    }\n\n    @Override\n    public String header(String name) {\n        String value = request.header(name);\n        return value == null ? header.get(name) : value;\n    }\n\n    @Override\n    public boolean cacheScope() {\n        return request.cacheScope();\n    }\n\n    @Override\n    public void setHeader(String name, String value) {\n        request.setHeader(name, value);\n        header.put(name, value);\n    }\n\n    public Map<String, String> getHeaders() {\n        return header;\n    }\n}\n"
  },
  {
    "path": "zipkin/src/main/java/com/megaease/easeagent/zipkin/impl/MessageImpl.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.zipkin.impl;\n\nimport brave.propagation.TraceContextOrSamplingFlags;\nimport com.megaease.easeagent.plugin.api.trace.Message;\n\nimport javax.annotation.Nonnull;\n\npublic class MessageImpl implements Message<TraceContextOrSamplingFlags> {\n    private final TraceContextOrSamplingFlags msg;\n\n    public MessageImpl(@Nonnull TraceContextOrSamplingFlags msg) {\n        this.msg = msg;\n    }\n\n    @Override\n    public TraceContextOrSamplingFlags get() {\n        return msg;\n    }\n}\n"
  },
  {
    "path": "zipkin/src/main/java/com/megaease/easeagent/zipkin/impl/RemoteGetterImpl.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.zipkin.impl;\n\nimport brave.Span;\nimport brave.propagation.Propagation;\nimport com.megaease.easeagent.plugin.api.trace.Request;\n\npublic class RemoteGetterImpl<R extends Request> implements Propagation.RemoteGetter<R> {\n    private final brave.Span.Kind kind;\n\n    public RemoteGetterImpl(Span.Kind kind) {\n        this.kind = kind;\n    }\n\n    @Override\n    public Span.Kind spanKind() {\n        return kind;\n    }\n\n    @Override\n    public String get(R request, String fieldName) {\n        return request.header(fieldName);\n    }\n\n}\n"
  },
  {
    "path": "zipkin/src/main/java/com/megaease/easeagent/zipkin/impl/RemoteSetterImpl.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.zipkin.impl;\n\nimport brave.Span;\nimport brave.propagation.Propagation;\nimport com.megaease.easeagent.plugin.api.trace.Request;\n\npublic class RemoteSetterImpl<R extends Request> implements Propagation.RemoteSetter<R> {\n    private final brave.Span.Kind kind;\n\n    public RemoteSetterImpl(Span.Kind kind) {\n        this.kind = kind;\n    }\n\n    @Override\n    public Span.Kind spanKind() {\n        return kind;\n    }\n\n    @Override\n    public void put(Request request, String fieldName, String value) {\n        request.setHeader(fieldName, value);\n    }\n}\n"
  },
  {
    "path": "zipkin/src/main/java/com/megaease/easeagent/zipkin/impl/RequestContextImpl.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.zipkin.impl;\n\nimport com.megaease.easeagent.plugin.api.ProgressFields;\nimport com.megaease.easeagent.plugin.api.context.RequestContext;\nimport com.megaease.easeagent.plugin.api.trace.Response;\nimport com.megaease.easeagent.plugin.api.trace.Scope;\nimport com.megaease.easeagent.plugin.api.trace.Span;\n\nimport java.util.Map;\n\npublic class RequestContextImpl implements RequestContext {\n    private final Span span;\n    private final Scope scope;\n    private final AsyncRequest asyncRequest;\n\n    public RequestContextImpl(Span span, Scope scope, AsyncRequest asyncRequest) {\n        this.span = span;\n        this.scope = scope;\n        this.asyncRequest = asyncRequest;\n    }\n\n    @Override\n    public boolean isNoop() {\n        return span.isNoop();\n    }\n\n    @Override\n    public Span span() {\n        return span;\n    }\n\n    @Override\n    public Scope scope() {\n        return scope;\n    }\n\n    @Override\n    public void setHeader(String name, String value) {\n        asyncRequest.setHeader(name, value);\n    }\n\n    @Override\n    public Map<String, String> getHeaders() {\n        return asyncRequest.getHeaders();\n    }\n\n    @Override\n    public void finish(Response response) {\n        String[] fields = ProgressFields.getResponseHoldTagFields();\n        if (!ProgressFields.isEmpty(fields)) {\n            for (String field : fields) {\n                span.tag(field, response.header(field));\n            }\n        }\n        span.finish();\n    }\n}\n"
  },
  {
    "path": "zipkin/src/main/java/com/megaease/easeagent/zipkin/impl/ScopeImpl.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.zipkin.impl;\n\nimport brave.propagation.CurrentTraceContext;\nimport com.megaease.easeagent.plugin.api.trace.Scope;\n\npublic class ScopeImpl implements Scope {\n    private final CurrentTraceContext.Scope scope;\n\n    public ScopeImpl(CurrentTraceContext.Scope scope) {\n        this.scope = scope;\n    }\n\n    @Override\n    public void close() {\n        scope.close();\n    }\n\n    @Override\n    public Object unwrap() {\n        return scope;\n    }\n}\n"
  },
  {
    "path": "zipkin/src/main/java/com/megaease/easeagent/zipkin/impl/SpanContextImpl.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.zipkin.impl;\n\nimport brave.propagation.TraceContext;\nimport com.megaease.easeagent.plugin.api.trace.SpanContext;\n\nimport javax.annotation.Nonnull;\n\npublic class SpanContextImpl implements SpanContext {\n    private final TraceContext traceContext;\n\n    public SpanContextImpl(@Nonnull TraceContext traceContext) {\n        this.traceContext = traceContext;\n    }\n\n    @Override\n    public boolean isNoop() {\n        return false;\n    }\n\n    @Override\n    public Object unwrap() {\n        return traceContext;\n    }\n}\n"
  },
  {
    "path": "zipkin/src/main/java/com/megaease/easeagent/zipkin/impl/SpanImpl.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.zipkin.impl;\n\nimport brave.Tracing;\nimport brave.propagation.CurrentTraceContext;\nimport brave.propagation.TraceContext;\nimport brave.propagation.TraceContextOrSamplingFlags;\nimport com.megaease.easeagent.plugin.api.trace.Request;\nimport com.megaease.easeagent.plugin.api.trace.Scope;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.bridge.NoOpTracer;\n\nimport javax.annotation.Nonnull;\nimport javax.annotation.Nullable;\nimport java.util.Collections;\nimport java.util.EnumMap;\nimport java.util.Map;\n\npublic class SpanImpl implements Span {\n    private static final Map<Kind, brave.Span.Kind> KINDS;\n\n    static {\n        Map<Kind, brave.Span.Kind> kinds = new EnumMap<>(Kind.class);\n        kinds.put(Kind.CLIENT, brave.Span.Kind.CLIENT);\n        kinds.put(Kind.SERVER, brave.Span.Kind.SERVER);\n        kinds.put(Kind.PRODUCER, brave.Span.Kind.PRODUCER);\n        kinds.put(Kind.CONSUMER, brave.Span.Kind.CONSUMER);\n        KINDS = Collections.unmodifiableMap(kinds);\n    }\n\n    private final Tracing tracing;\n    private final brave.Span span;\n    private CurrentTraceContext.Scope scope;\n    private final TraceContext.Injector<Request> injector;\n\n    private SpanImpl(@Nonnull Tracing tracing, @Nonnull brave.Span span,\n                     @Nonnull TraceContext.Injector<Request> injector) {\n        this.tracing = tracing;\n        this.span = span;\n        this.injector = injector;\n    }\n\n    public static Span build(Tracing tracing,\n                             brave.Span span,\n                             boolean cachedScope,\n                             TraceContext.Injector<? extends Request> injector) {\n        if (span == null) {\n            return NoOpTracer.NO_OP_SPAN;\n        }\n\n        TraceContext.Injector<Request> ci = (TraceContext.Injector<Request>) injector;\n        SpanImpl eSpan = new SpanImpl(tracing, span, ci);\n\n        if (cachedScope) {\n            eSpan.cacheScope();\n        }\n        return eSpan;\n    }\n\n    public static Span build(Tracing tracing, brave.Span span, TraceContext.Injector<Request> injector) {\n        return build(tracing, span, false, injector);\n    }\n\n    public static brave.Span.Kind braveKind(Kind kind) {\n        return KINDS.get(kind);\n    }\n\n    public static brave.Span nextBraveSpan(Tracing tracing,\n                                           TraceContext.Extractor<? extends Request> extractor, Request request) {\n        TraceContext maybeParent = tracing.currentTraceContext().get();\n        // Unlike message consumers, we try current span before trying extraction. This is the proper\n        // order because the span in scope should take precedence over a potentially stale header entry.\n        //\n        brave.Span span;\n        if (maybeParent == null) {\n            TraceContext.Extractor<Request> rExtractor = (TraceContext.Extractor<Request>) extractor;\n            TraceContextOrSamplingFlags extracted = rExtractor.extract(request);\n            span = tracing.tracer().nextSpan(extracted);\n        } else { // If we have a span in scope assume headers were cleared before\n            span = tracing.tracer().newChild(maybeParent);\n        }\n        if (span.isNoop()) {\n            return span;\n        }\n        setInfo(span, request);\n        return span;\n    }\n\n    private static void setInfo(brave.Span span, Request request) {\n        Span.Kind kind = request.kind();\n        if (kind != null) {\n            span.kind(SpanImpl.braveKind(kind));\n        }\n        span.name(request.name());\n    }\n\n    @Override\n    public Span name(String name) {\n        span.name(name);\n        return this;\n    }\n\n    @Override\n    public Span tag(String key, String value) {\n        if (key == null || value == null) {\n            return this;\n        }\n        span.tag(key, value);\n        return this;\n    }\n\n    @Override\n    public Span annotate(String value) {\n        span.annotate(value);\n        return this;\n    }\n\n    @Override\n    public boolean isNoop() {\n        return span.isNoop();\n    }\n\n    @Override\n    public Span start() {\n        span.start();\n        return this;\n    }\n\n    @Override\n    public Span start(long timestamp) {\n        span.start(timestamp);\n        return this;\n    }\n\n    @Override\n    public Span kind(@Nullable Kind kind) {\n        span.kind(KINDS.get(kind));\n        return this;\n    }\n\n    @Override\n    public Span annotate(long timestamp, String value) {\n        span.annotate(timestamp, value);\n        return this;\n    }\n\n    @Override\n    public Span error(Throwable throwable) {\n        span.error(throwable);\n        return this;\n    }\n\n    @Override\n    public Span remoteServiceName(String remoteServiceName) {\n        span.remoteServiceName(remoteServiceName);\n        return this;\n    }\n\n    @Override\n    public boolean remoteIpAndPort(@Nullable String remoteIp, int remotePort) {\n        span.remoteIpAndPort(remoteIp, remotePort);\n        return false;\n    }\n\n    @Override\n    public void abandon() {\n        span.abandon();\n    }\n\n    @Override\n    public void finish() {\n        closeScope();\n        span.finish();\n    }\n\n    @Override\n    public void finish(long timestamp) {\n        closeScope();\n        span.finish(timestamp);\n    }\n\n    private void closeScope() {\n        if (scope != null) {\n            scope.close();\n        }\n    }\n\n    @Override\n    public void flush() {\n        span.flush();\n    }\n\n    @Override\n    public void inject(Request request) {\n        injector.inject(span.context(), request);\n    }\n\n    @Override\n    public Scope maybeScope() {\n        return new ScopeImpl(tracing.currentTraceContext().maybeScope(span.context()));\n    }\n\n    @Override\n    public Span cacheScope() {\n        if (scope != null) {\n            return this;\n        }\n        scope = tracing.currentTraceContext().maybeScope(span.context());\n        return this;\n    }\n\n    @Override\n    public String traceIdString() {\n        return span.context().traceIdString();\n    }\n\n    @Override\n    public String spanIdString() {\n        return span.context().spanIdString();\n    }\n\n    @Override\n    public String parentIdString() {\n        return span.context().parentIdString();\n    }\n\n    @Override\n    public Long traceId() {\n        return span.context().traceId();\n    }\n\n    @Override\n    public Long spanId() {\n        return span.context().spanId();\n    }\n\n    @Override\n    public Long parentId() {\n        return span.context().parentId();\n    }\n\n    @Override\n    public Object unwrap() {\n        return span;\n    }\n}\n"
  },
  {
    "path": "zipkin/src/main/java/com/megaease/easeagent/zipkin/impl/TracingImpl.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.zipkin.impl;\n\nimport brave.propagation.CurrentTraceContext;\nimport brave.propagation.Propagation;\nimport brave.propagation.TraceContext;\nimport brave.propagation.TraceContextOrSamplingFlags;\nimport com.megaease.easeagent.log4j2.Logger;\nimport com.megaease.easeagent.log4j2.LoggerFactory;\nimport com.megaease.easeagent.plugin.api.InitializeContext;\nimport com.megaease.easeagent.plugin.api.context.RequestContext;\nimport com.megaease.easeagent.plugin.api.trace.*;\nimport com.megaease.easeagent.plugin.bridge.NoOpContext;\nimport com.megaease.easeagent.plugin.bridge.NoOpTracer;\nimport com.megaease.easeagent.zipkin.impl.message.MessagingTracingImpl;\n\nimport javax.annotation.Nonnull;\nimport java.util.List;\nimport java.util.function.Supplier;\n\npublic class TracingImpl implements ITracing {\n    private static final Logger LOGGER = LoggerFactory.getLogger(TracingImpl.class);\n    private final Supplier<InitializeContext> supplier;\n    private final brave.Tracing tracing;\n    private final brave.Tracer tracer;\n\n    private final TraceContext.Injector<Request> defaultZipkinInjector;\n    private final TraceContext.Injector<Request> clientZipkinInjector;\n    private final TraceContext.Extractor<Request> defaultZipkinExtractor;\n\n    private final MessagingTracing<MessagingRequest> messagingTracing;\n    private final List<String> propagationKeys;\n\n    private TracingImpl(@Nonnull Supplier<InitializeContext> supplier,\n                        @Nonnull brave.Tracing tracing) {\n        this.supplier = supplier;\n        this.tracing = tracing;\n        this.tracer = tracing.tracer();\n        this.propagationKeys = tracing.propagation().keys();\n        Propagation<String> propagation = tracing.propagation();\n\n        this.defaultZipkinInjector = propagation.injector(Request::setHeader);\n        this.clientZipkinInjector = propagation.injector(new RemoteSetterImpl<>(brave.Span.Kind.CLIENT));\n        this.defaultZipkinExtractor = propagation.extractor(Request::header);\n        this.messagingTracing = MessagingTracingImpl.build(tracing);\n    }\n\n    public static ITracing build(Supplier<InitializeContext> supplier, brave.Tracing tracing) {\n        if (tracing == null) {\n            return NoOpTracer.NO_OP_TRACING;\n        }\n\n        return new TracingImpl(supplier, tracing);\n    }\n\n    @Override\n    public boolean isNoop() {\n        return false;\n    }\n\n    @Override\n    public boolean hasCurrentSpan() {\n        return tracing().currentTraceContext().get() != null;\n    }\n\n\n    private brave.Tracer tracer() {\n        return this.tracer;\n    }\n\n    private brave.Tracing tracing() {\n        return this.tracing;\n    }\n\n    @Override\n    public Span currentSpan() {\n        Span span = NoOpTracer.NO_OP_SPAN;\n        if (tracer != null) {\n            span = build(tracer.currentSpan());\n        }\n        return NoOpTracer.noNullSpan(span);\n    }\n\n    private Span build(brave.Span bSpan) {\n        return build(bSpan, false);\n    }\n\n    private Span build(brave.Span bSpan, boolean cacheScope) {\n        return SpanImpl.build(tracing(), bSpan, cacheScope, defaultZipkinInjector);\n    }\n\n    private void setInfo(brave.Span span, Request request) {\n        Span.Kind kind = request.kind();\n        if (kind != null) {\n            span.kind(SpanImpl.braveKind(kind));\n        }\n        span.name(request.name());\n    }\n\n    private TraceContext currentTraceContext() {\n        if (tracer == null) {\n            LOGGER.debug(\"tracer was null.\");\n            return null;\n        }\n        brave.Span span = tracer.currentSpan();\n        if (span == null) {\n            return null;\n        }\n        return span.context();\n    }\n\n    @Override\n    public SpanContext exportAsync() {\n        TraceContext traceContext = currentTraceContext();\n        if (traceContext == null) {\n            return NoOpTracer.NO_OP_SPAN_CONTEXT;\n        }\n        return new SpanContextImpl(traceContext);\n    }\n\n    @Override\n    public Scope importAsync(SpanContext snapshot) {\n        if (snapshot.isNoop()) {\n            return NoOpTracer.NO_OP_SCOPE;\n        }\n        Object context = snapshot.unwrap();\n        if (context instanceof TraceContext) {\n            TraceContext traceContext = (TraceContext) context;\n            CurrentTraceContext.Scope scope = tracing().currentTraceContext().maybeScope(traceContext);\n            return new ScopeImpl(scope);\n        } else {\n            LOGGER.warn(\"import async span to brave.Tracing fail: SpanContext.unwrap() result Class<{}> must be Class<{}>\", context.getClass().getName(), TraceContext.class.getName());\n        }\n        return NoOpTracer.NO_OP_SCOPE;\n    }\n\n    @Override\n    public RequestContext clientRequest(Request request) {\n        brave.Span span = SpanImpl.nextBraveSpan(tracing, defaultZipkinExtractor, request);\n        AsyncRequest asyncRequest = new AsyncRequest(request);\n        clientZipkinInjector.inject(span.context(), asyncRequest);\n        Span newSpan = build(span, request.cacheScope());\n        return new RequestContextImpl(newSpan, newSpan.maybeScope(), asyncRequest);\n    }\n\n    @Override\n    public RequestContext serverReceive(Request request) {\n        TraceContext maybeParent = tracing.currentTraceContext().get();\n        // Unlike message consumers, we try current span before trying extraction. This is the proper\n        // order because the span in scope should take precedence over a potentially stale header entry.\n        //\n        brave.Span span;\n        if (maybeParent == null) {\n            TraceContextOrSamplingFlags extracted = defaultZipkinExtractor.extract(request);\n            span = extracted.context() != null\n                ? tracer().joinSpan(extracted.context())\n                : tracer().nextSpan(extracted);\n        } else { // If we have a span in scope assume headers were cleared before\n            span = tracing.tracer().newChild(maybeParent);\n        }\n\n        setInfo(span, request);\n        AsyncRequest asyncRequest = new AsyncRequest(request);\n        defaultZipkinInjector.inject(span.context(), asyncRequest);\n        Span newSpan = build(span, request.cacheScope());\n        return new RequestContextImpl(newSpan, newSpan.maybeScope(), asyncRequest);\n    }\n\n    @Override\n    public List<String> propagationKeys() {\n        return propagationKeys;\n    }\n\n    @Override\n    public Span nextSpan() {\n        return build(tracer().nextSpan(), false);\n    }\n\n    @Override\n    public MessagingTracing<MessagingRequest> messagingTracing() {\n        return messagingTracing;\n    }\n\n    @Override\n    public Object unwrap() {\n        return tracing;\n    }\n\n    @Override\n    public Span consumerSpan(MessagingRequest request) {\n        return this.messagingTracing.consumerSpan(request);\n    }\n\n    @Override\n    public Span producerSpan(MessagingRequest request) {\n        return this.messagingTracing.producerSpan(request);\n    }\n}\n"
  },
  {
    "path": "zipkin/src/main/java/com/megaease/easeagent/zipkin/impl/message/MessagingTracingImpl.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.zipkin.impl.message;\n\nimport brave.propagation.TraceContext;\nimport brave.propagation.TraceContextOrSamplingFlags;\nimport com.megaease.easeagent.plugin.api.trace.*;\nimport com.megaease.easeagent.plugin.bridge.NoOpTracer;\nimport com.megaease.easeagent.zipkin.impl.MessageImpl;\nimport com.megaease.easeagent.zipkin.impl.RemoteGetterImpl;\nimport com.megaease.easeagent.zipkin.impl.RemoteSetterImpl;\nimport com.megaease.easeagent.zipkin.impl.SpanImpl;\n\nimport javax.annotation.Nonnull;\nimport java.util.function.Function;\n\npublic class MessagingTracingImpl<R extends MessagingRequest> implements MessagingTracing<R> {\n    private final brave.messaging.MessagingTracing messagingTracing;\n    private final Extractor<R> producerExtractor;\n    private final Extractor<R> consumerExtractor;\n    private final Injector<R> producerInjector;\n    private final Injector<R> consumerInjector;\n\n    private final Function<R, Boolean> consumerSampler;\n    private final Function<R, Boolean> producerSampler;\n\n    private final TraceContext.Injector<R> zipkinProducerInjector;\n    private final TraceContext.Injector<R> zipkinConsumerInjector;\n    private final TraceContext.Extractor<R> zipkinProducerExtractor;\n    private final TraceContext.Extractor<R> zipkinConsumerExtractor;\n\n    private MessagingTracingImpl(brave.messaging.MessagingTracing messagingTracing) {\n        this.messagingTracing = messagingTracing;\n        this.zipkinProducerExtractor = messagingTracing.propagation().extractor(new RemoteGetterImpl<>(brave.Span.Kind.PRODUCER));\n        this.zipkinConsumerExtractor = messagingTracing.propagation().extractor(new RemoteGetterImpl<>(brave.Span.Kind.CONSUMER));\n        this.zipkinProducerInjector = messagingTracing.propagation().injector(new RemoteSetterImpl<>(brave.Span.Kind.PRODUCER));\n        this.zipkinConsumerInjector = messagingTracing.propagation().injector(new RemoteSetterImpl<>(brave.Span.Kind.CONSUMER));\n\n        this.producerExtractor = new ExtractorImpl(this.zipkinProducerExtractor);\n        this.consumerExtractor = new ExtractorImpl(this.zipkinConsumerExtractor);\n        this.producerInjector = new InjectorImpl(this.zipkinProducerInjector);\n        this.consumerInjector = new InjectorImpl(this.zipkinConsumerInjector);\n\n        this.consumerSampler = new SamplerFunction(ZipkinConsumerRequest::new, messagingTracing.consumerSampler());\n        this.producerSampler = new SamplerFunction(ZipkinProducerRequest::new, messagingTracing.producerSampler());\n    }\n\n    public static MessagingTracing<MessagingRequest> build(brave.Tracing tracing) {\n        if (tracing == null) {\n            return NoOpTracer.NO_OP_MESSAGING_TRACING;\n        }\n        brave.messaging.MessagingTracing messagingTracing = brave.messaging.MessagingTracing\n            .newBuilder(tracing).build();\n\n        return new MessagingTracingImpl<>(messagingTracing);\n    }\n\n    @Override\n    public Span consumerSpan(MessagingRequest request) {\n        brave.Tracing tracing = messagingTracing.tracing();\n        brave.Span span = SpanImpl.nextBraveSpan(tracing, this.zipkinConsumerExtractor, request);\n        if (span.isNoop()) {\n            return NoOpTracer.NO_OP_SPAN;\n        }\n        setMessageInfo(span, request);\n        Span eSpan = SpanImpl.build(messagingTracing.tracing(), span,\n            request.cacheScope(), this.zipkinConsumerInjector);\n\n        return NoOpTracer.noNullSpan(eSpan);\n    }\n\n    @Override\n    public Span producerSpan(MessagingRequest request) {\n        brave.Tracing tracing = messagingTracing.tracing();\n        brave.Span span = SpanImpl.nextBraveSpan(tracing, this.zipkinProducerExtractor, request);\n        if (span.isNoop()) {\n            return NoOpTracer.NO_OP_SPAN;\n        }\n        setMessageInfo(span, request);\n        Span eSpan = SpanImpl.build(messagingTracing.tracing(), span, request.cacheScope(), zipkinProducerInjector);\n        producerInjector.inject(eSpan, (R) request);\n        return NoOpTracer.noNullSpan(eSpan);\n    }\n\n    @Override\n    public Extractor<R> producerExtractor() {\n        return producerExtractor;\n    }\n\n    @Override\n    public Extractor<R> consumerExtractor() {\n        return consumerExtractor;\n    }\n\n    @Override\n    public Injector<R> producerInjector() {\n        return producerInjector;\n    }\n\n    @Override\n    public Injector<R> consumerInjector() {\n        return consumerInjector;\n    }\n\n    @Override\n    public Function<R, Boolean> consumerSampler() {\n        return consumerSampler;\n    }\n\n    @Override\n    public Function<R, Boolean> producerSampler() {\n        return producerSampler;\n    }\n\n    private void setMessageInfo(brave.Span span, MessagingRequest request) {\n        if (request.operation() != null) {\n            span.tag(\"messaging.operation\", request.operation());\n        }\n        if (request.channelKind() != null) {\n            span.tag(\"messaging.channel_kind\", request.channelKind());\n        }\n        if (request.channelName() != null) {\n            span.tag(\"messaging.channel_name\", request.channelName());\n        }\n    }\n\n    public class ExtractorImpl implements Extractor<R> {\n        private final TraceContext.Extractor<R> extractor;\n\n        public ExtractorImpl(TraceContext.Extractor<R> extractor) {\n            this.extractor = extractor;\n        }\n\n        @Override\n        public Message<TraceContextOrSamplingFlags> extract(R request) {\n            return new MessageImpl(extractor.extract(request));\n        }\n    }\n\n    public class InjectorImpl implements Injector<R> {\n        private final TraceContext.Injector<R> injector;\n\n        public InjectorImpl(TraceContext.Injector<R> injector) {\n            this.injector = injector;\n        }\n\n        @Override\n        public void inject(Span span, R request) {\n            Object spanO = span.unwrap();\n            if (spanO instanceof brave.Span) {\n                this.injector.inject(((brave.Span) spanO).context(), request);\n            }\n        }\n\n        public TraceContext.Injector<R> getInjector() {\n            return this.injector;\n        }\n    }\n\n    public class SamplerFunction implements Function<R, Boolean> {\n        private final Function<R, brave.messaging.MessagingRequest> builder;\n        private final brave.sampler.SamplerFunction<brave.messaging.MessagingRequest> function;\n\n        public SamplerFunction(@Nonnull Function<R, brave.messaging.MessagingRequest> builder,\n                               @Nonnull brave.sampler.SamplerFunction<brave.messaging.MessagingRequest> function) {\n            this.builder = builder;\n            this.function = function;\n        }\n\n        @Override\n        public Boolean apply(R request) {\n            return function.trySample(builder.apply(request));\n        }\n    }\n\n}\n"
  },
  {
    "path": "zipkin/src/main/java/com/megaease/easeagent/zipkin/impl/message/ZipkinConsumerRequest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.zipkin.impl.message;\n\nimport brave.messaging.ConsumerRequest;\nimport com.megaease.easeagent.plugin.api.trace.MessagingRequest;\n\npublic class ZipkinConsumerRequest<R extends MessagingRequest>  extends ConsumerRequest {\n\n    private final R request;\n\n    public ZipkinConsumerRequest(R request) {\n        this.request = request;\n    }\n\n    @Override\n    public String operation() {\n        return request.operation();\n    }\n\n    @Override\n    public String channelKind() {\n        return request.channelKind();\n    }\n\n    @Override\n    public String channelName() {\n        return request.channelName();\n    }\n\n    @Override\n    public Object unwrap() {\n        return request.unwrap();\n    }\n}\n"
  },
  {
    "path": "zipkin/src/main/java/com/megaease/easeagent/zipkin/impl/message/ZipkinProducerRequest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.megaease.easeagent.zipkin.impl.message;\n\nimport brave.messaging.ProducerRequest;\nimport com.megaease.easeagent.plugin.api.trace.MessagingRequest;\n\npublic class ZipkinProducerRequest<R extends MessagingRequest> extends ProducerRequest {\n    private final R request;\n\n    public ZipkinProducerRequest(R request) {\n        this.request = request;\n    }\n\n    @Override\n    public String operation() {\n        return request.operation();\n    }\n\n    @Override\n    public String channelKind() {\n        return request.channelKind();\n    }\n\n    @Override\n    public String channelName() {\n        return request.channelName();\n    }\n\n    @Override\n    public Object unwrap() {\n        return request.unwrap();\n    }\n}\n"
  },
  {
    "path": "zipkin/src/main/java/com/megaease/easeagent/zipkin/logging/AgentLogMDC.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.zipkin.logging;\n\n\nimport com.megaease.easeagent.plugin.utils.common.WeakConcurrentMap;\nimport com.megaease.easeagent.plugin.utils.common.WeakConcurrentMap.WeakKey;\n\nimport java.lang.ref.WeakReference;\nimport java.lang.reflect.Method;\n\npublic class AgentLogMDC {\n    static WeakConcurrentMap<ClassLoader, AgentLogMDC> appMdcMap = new WeakConcurrentMap<>();\n\n    public final Class<?> clazz;\n    private final Method method4Get;\n    private final Method method4Put;\n    private final Method method4Remove;\n\n\n    public static AgentLogMDC create(ClassLoader classLoader) {\n        AgentLogMDC mdc = appMdcMap.getIfPresent(classLoader);\n        if (mdc != null) {\n            return mdc;\n        }\n        Class<?> aClass = LogUtils.checkLog4JMDC(classLoader);\n        if (aClass == null) {\n            aClass = LogUtils.checkLogBackMDC(classLoader);\n        }\n\n        if (aClass != null) {\n            mdc = new AgentLogMDC(aClass);\n            appMdcMap.putIfProbablyAbsent(classLoader, mdc);\n            return mdc;\n        }\n        return null;\n    }\n\n    public AgentLogMDC(Class<?> aClass) {\n        this.clazz = aClass;\n        method4Get = LogUtils.findMethod(clazz, \"get\", String.class);\n        method4Put = LogUtils.findMethod(clazz, \"put\", String.class, String.class);\n        method4Remove = LogUtils.findMethod(clazz, \"remove\", String.class);\n    }\n\n    public void put(String name, String value) {\n        LogUtils.invokeMethod(method4Put, null, name, value);\n    }\n\n    public String get(String name) {\n        return (String) LogUtils.invokeMethod(method4Get, null, name);\n    }\n\n    public void remove(String name) {\n        LogUtils.invokeMethod(method4Remove, null, name);\n    }\n}\n"
  },
  {
    "path": "zipkin/src/main/java/com/megaease/easeagent/zipkin/logging/AgentMDCScopeDecorator.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.zipkin.logging;\n\nimport brave.baggage.CorrelationScopeDecorator;\nimport brave.internal.CorrelationContext;\nimport brave.internal.Nullable;\nimport brave.propagation.CurrentTraceContext;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\n\npublic class AgentMDCScopeDecorator {\n    static final CurrentTraceContext.ScopeDecorator INSTANCE = new BuilderApp().build();\n    static final CurrentTraceContext.ScopeDecorator INSTANCE_V2 = new BuilderEaseLogger().build();\n    static final CurrentTraceContext.ScopeDecorator INSTANCE_EASEAGENT_LOADER = new BuilderAgentLoader().build();\n\n    public static CurrentTraceContext.ScopeDecorator get() {\n        return INSTANCE;\n    }\n\n    public static CurrentTraceContext.ScopeDecorator getV2() {\n        return INSTANCE_V2;\n    }\n\n    public static CurrentTraceContext.ScopeDecorator getAgentDecorator() {\n        return INSTANCE_EASEAGENT_LOADER;\n    }\n\n    static final class BuilderApp extends CorrelationScopeDecorator.Builder {\n        BuilderApp() {\n            super(MDCContextApp.INSTANCE);\n        }\n    }\n\n    static final class BuilderEaseLogger extends CorrelationScopeDecorator.Builder {\n        BuilderEaseLogger() {\n            super(MDCContextEaseLogger.INSTANCE);\n        }\n    }\n\n    static final class BuilderAgentLoader extends CorrelationScopeDecorator.Builder {\n        BuilderAgentLoader() {\n            super(MDCContextAgentLoader.INSTANCE);\n        }\n    }\n\n    enum MDCContextAgentLoader implements CorrelationContext {\n        INSTANCE;\n\n        @Override\n        public String getValue(String name) {\n            return org.slf4j.MDC.get(name);\n        }\n\n        @Override\n        public boolean update(String name, @Nullable String value) {\n            if (value != null) {\n                org.slf4j.MDC.put(name, value);\n            } else {\n                org.slf4j.MDC.remove(name);\n            }\n            return true;\n        }\n    }\n\n    enum MDCContextApp implements CorrelationContext {\n        INSTANCE;\n\n        @Override\n        public String getValue(String name) {\n            ClassLoader classLoader = getUserClassLoader();\n            AgentLogMDC agentLogMDC = AgentLogMDC.create(classLoader);\n            if (agentLogMDC == null) {\n                return null;\n            }\n            return agentLogMDC.get(name);\n        }\n\n        @Override\n        public boolean update(String name, @Nullable String value) {\n            ClassLoader classLoader = getUserClassLoader();\n            AgentLogMDC agentLogMDC = AgentLogMDC.create(classLoader);\n            if (agentLogMDC == null) {\n                return true;\n            }\n            if (value != null) {\n                agentLogMDC.put(name, value);\n            } else {\n                agentLogMDC.remove(name);\n            }\n            return true;\n        }\n\n        private ClassLoader getUserClassLoader() {\n            return Thread.currentThread().getContextClassLoader();\n        }\n    }\n\n    enum MDCContextEaseLogger implements CorrelationContext {\n        INSTANCE;\n\n        @Override\n        public String getValue(String name) {\n            return EaseAgent.loggerMdc.get(name);\n        }\n\n        @Override\n        public boolean update(String name, @Nullable String value) {\n            if (value != null) {\n                EaseAgent.loggerMdc.put(name, value);\n            } else {\n                EaseAgent.loggerMdc.remove(name);\n            }\n            return true;\n        }\n    }\n}\n"
  },
  {
    "path": "zipkin/src/main/java/com/megaease/easeagent/zipkin/logging/LogUtils.java",
    "content": "/*\n * Copyright (c) 2017, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.zipkin.logging;\n\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Method;\nimport java.lang.reflect.Modifier;\nimport java.lang.reflect.UndeclaredThrowableException;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\n\n@SuppressWarnings(\"unchecked\")\npublic class LogUtils {\n    /**\n     * Cache for {@link Class#getDeclaredMethods()} plus equivalent default methods\n     * from Java 8 based interfaces, allowing for fast iteration.\n     */\n    private static final Map<Class<?>, Method[]> declaredMethodsCache = new ConcurrentHashMap<>(256);\n    private static final Method[] EMPTY_METHOD_ARRAY = new Method[0];\n\n    private static final Class<?>[] EMPTY_CLASS_ARRAY = new Class<?>[0];\n    private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];\n\n    private static final String LOG4J_MDC_CLASS_NAME = \"org.apache.logging.log4j.ThreadContext\";\n    private static final String LOGBACK_MDC_CLASS_NAME = \"org.slf4j.MDC\";\n\n    private static final String LOG4J_CHECK_CLASS_NAME = \"org.apache.logging.log4j.core.Appender\";\n    private static final String LOGBACK_CHECK_CLASS_NAME = \"ch.qos.logback.core.Appender\";\n\n    private static Boolean log4jLoaded;\n    private static Boolean logbackLoaded;\n\n    private static Class<?> log4jMdcClass;\n    private static Class<?> logbackMdcClass;\n\n    private LogUtils() {}\n\n    public static Class<?> checkLog4JMDC(ClassLoader classLoader) {\n        if (log4jLoaded != null) {\n            return log4jMdcClass;\n        }\n        if (loadClass(classLoader, LOG4J_CHECK_CLASS_NAME) != null) {\n            log4jMdcClass = loadClass(classLoader, LOG4J_MDC_CLASS_NAME);\n        }\n        log4jLoaded = true;\n        return log4jMdcClass;\n    }\n\n    public static Class<?> checkLogBackMDC(ClassLoader classLoader) {\n        if (logbackLoaded != null) {\n            return logbackMdcClass;\n        }\n        if (loadClass(classLoader, LOGBACK_CHECK_CLASS_NAME) != null) {\n            logbackMdcClass = loadClass(classLoader, LOGBACK_MDC_CLASS_NAME);\n        }\n        logbackLoaded = true;\n        return logbackMdcClass;\n    }\n\n    public static Class<?> loadClass(ClassLoader classLoader, String className) {\n        try {\n            return classLoader.loadClass(className);\n        } catch (ClassNotFoundException e) {\n            return null;\n        }\n    }\n\n    /**\n     * Attempt to find a {@link Method} on the supplied class with the supplied name\n     * and no parameters. Searches all superclasses up to {@code Object}.\n     * <p>Returns {@code null} if no {@link Method} can be found.\n     *\n     * @param clazz the class to introspect\n     * @param name  the name of the method\n     * @return the Method object, or {@code null} if none found\n     */\n\n    public static Method findMethod(Class<?> clazz, String name) {\n        return findMethod(clazz, name, EMPTY_CLASS_ARRAY);\n    }\n\n    /**\n     * Attempt to find a {@link Method} on the supplied class with the supplied name\n     * and parameter types. Searches all superclasses up to {@code Object}.\n     * <p>Returns {@code null} if no {@link Method} can be found.\n     *\n     * @param clazz      the class to introspect\n     * @param name       the name of the method\n     * @param paramTypes the parameter types of the method\n     *                   (may be {@code null} to indicate any signature)\n     * @return the Method object, or {@code null} if none found\n     */\n\n    public static Method findMethod(Class<?> clazz, String name, Class<?>... paramTypes) {\n        Class<?> searchType = clazz;\n        while (searchType != null) {\n            Method[] methods = (searchType.isInterface() ? searchType.getMethods() :\n                getDeclaredMethods(searchType, false));\n            for (Method method : methods) {\n                if (name.equals(method.getName()) && (paramTypes == null || hasSameParams(method, paramTypes))) {\n                    return method;\n                }\n            }\n            searchType = searchType.getSuperclass();\n        }\n        return null;\n    }\n\n    private static Method[] getDeclaredMethods(Class<?> clazz, boolean defensive) {\n        Method[] result = declaredMethodsCache.get(clazz);\n        if (result == null) {\n            try {\n                Method[] declaredMethods = clazz.getDeclaredMethods();\n                List<Method> defaultMethods = findConcreteMethodsOnInterfaces(clazz);\n                if (defaultMethods != null) {\n                    result = new Method[declaredMethods.length + defaultMethods.size()];\n                    System.arraycopy(declaredMethods, 0, result, 0, declaredMethods.length);\n                    int index = declaredMethods.length;\n                    for (Method defaultMethod : defaultMethods) {\n                        result[index] = defaultMethod;\n                        index++;\n                    }\n                } else {\n                    result = declaredMethods;\n                }\n                declaredMethodsCache.put(clazz, (result.length == 0 ? EMPTY_METHOD_ARRAY : result));\n            } catch (Exception ex) {\n                throw new IllegalStateException(\"Failed to introspect Class [\" + clazz.getName() +\n                    \"] from ClassLoader [\" + clazz.getClassLoader() + \"]\", ex);\n            }\n        }\n        return (result.length == 0 || !defensive) ? result : result.clone();\n    }\n\n    private static List<Method> findConcreteMethodsOnInterfaces(Class<?> clazz) {\n        List<Method> result = null;\n        for (Class<?> ifc : clazz.getInterfaces()) {\n            for (Method ifcMethod : ifc.getMethods()) {\n                if (!Modifier.isAbstract(ifcMethod.getModifiers())) {\n                    if (result == null) {\n                        result = new ArrayList<>();\n                    }\n                    result.add(ifcMethod);\n                }\n            }\n        }\n        return result;\n    }\n\n    private static boolean hasSameParams(Method method, Class<?>[] paramTypes) {\n        return (paramTypes.length == method.getParameterCount() &&\n            Arrays.equals(paramTypes, method.getParameterTypes()));\n    }\n\n    /**\n     * Invoke the specified {@link Method} against the supplied target object with no arguments.\n     * The target object can be {@code null} when invoking a static {@link Method}.\n     * <p>Thrown exceptions are handled via a call to {@link #handleReflectionException}.\n     *\n     * @param method the method to invoke\n     * @param target the target object to invoke the method on\n     * @return the invocation result, if any\n     * @see #invokeMethod(java.lang.reflect.Method, Object, Object[])\n     */\n\n    public static Object invokeMethod(Method method, Object target) {\n        return invokeMethod(method, target, EMPTY_OBJECT_ARRAY);\n    }\n\n    /**\n     * Invoke the specified {@link Method} against the supplied target object with the\n     * supplied arguments. The target object can be {@code null} when invoking a\n     * static {@link Method}.\n     * <p>Thrown exceptions are handled via a call to {@link #handleReflectionException}.\n     *\n     * @param method the method to invoke\n     * @param target the target object to invoke the method on\n     * @param args   the invocation arguments (may be {@code null})\n     * @return the invocation result, if any\n     */\n\n    public static Object invokeMethod(Method method, Object target, Object... args) {\n        try {\n            return method.invoke(target, args);\n        } catch (Exception ex) {\n            handleReflectionException(ex);\n        }\n        throw new IllegalStateException(\"Should never get here\");\n    }\n\n\n    // Exception handling\n\n    /**\n     * Handle the given reflection exception.\n     * <p>Should only be called if no checked exception is expected to be thrown\n     * by a target method, or if an error occurs while accessing a method or field.\n     * <p>Throws the underlying RuntimeException or Error in case of an\n     * InvocationTargetException with such a root cause. Throws an\n     * IllegalStateException with an appropriate message or\n     * UndeclaredThrowableException otherwise.\n     *\n     * @param ex the reflection exception to handle\n     */\n    public static void handleReflectionException(Exception ex) {\n        if (ex instanceof NoSuchMethodException) {\n            throw new IllegalStateException(\"Method not found: \" + ex.getMessage());\n        }\n        if (ex instanceof IllegalAccessException) {\n            throw new IllegalStateException(\"Could not access method or field: \" + ex.getMessage());\n        }\n        if (ex instanceof InvocationTargetException) {\n            handleInvocationTargetException((InvocationTargetException) ex);\n        }\n        if (ex instanceof RuntimeException) {\n            throw (RuntimeException) ex;\n        }\n        throw new UndeclaredThrowableException(ex);\n    }\n\n    /**\n     * Handle the given invocation target exception. Should only be called if no\n     * checked exception is expected to be thrown by the target method.\n     * <p>Throws the underlying RuntimeException or Error in case of such a root\n     * cause. Throws an UndeclaredThrowableException otherwise.\n     *\n     * @param ex the invocation target exception to handle\n     */\n    public static void handleInvocationTargetException(InvocationTargetException ex) {\n        rethrowRuntimeException(ex.getTargetException());\n    }\n\n    /**\n     * Rethrow the given {@link Throwable exception}, which is presumably the\n     * <em>target exception</em> of an {@link InvocationTargetException}.\n     * Should only be called if no checked exception is expected to be thrown\n     * by the target method.\n     * <p>Rethrows the underlying exception cast to a {@link RuntimeException} or\n     * {@link Error} if appropriate; otherwise, throws an\n     * {@link UndeclaredThrowableException}.\n     *\n     * @param ex the exception to rethrow\n     * @throws RuntimeException the rethrown exception\n     */\n    public static void rethrowRuntimeException(Throwable ex) {\n        if (ex instanceof RuntimeException) {\n            throw (RuntimeException) ex;\n        }\n        if (ex instanceof Error) {\n            throw (Error) ex;\n        }\n        throw new UndeclaredThrowableException(ex);\n    }\n}\n"
  },
  {
    "path": "zipkin/src/main/resources/META-INF/services/com.megaease.easeagent.plugin.bean.BeanProvider",
    "content": "com.megaease.easeagent.zipkin.TracingProviderImpl\n"
  },
  {
    "path": "zipkin/src/test/java/brave/TracerTestUtils.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage brave;\n\nimport brave.internal.collect.WeakConcurrentMapTestUtils;\nimport brave.internal.recorder.PendingSpans;\n\npublic class TracerTestUtils {\n    public static void clean(Tracer tracer) {\n        PendingSpans pendingSpans = tracer.pendingSpans;\n        WeakConcurrentMapTestUtils.runExpungeStaleEntries(pendingSpans);\n    }\n}\n"
  },
  {
    "path": "zipkin/src/test/java/brave/internal/collect/WeakConcurrentMapTestUtils.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage brave.internal.collect;\n\npublic class WeakConcurrentMapTestUtils {\n    public static void runExpungeStaleEntries(WeakConcurrentMap weakConcurrentMap) {\n        weakConcurrentMap.expungeStaleEntries();\n    }\n}\n"
  },
  {
    "path": "zipkin/src/test/java/com/megaease/easeagent/zipkin/CustomTagsSpanHandlerTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.zipkin;\n\nimport brave.Span;\nimport brave.handler.MutableSpan;\nimport org.junit.Test;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertFalse;\nimport static org.junit.Assert.assertTrue;\n\npublic class CustomTagsSpanHandlerTest {\n    @Test\n    public void end() {\n        String serviceName = \"testName\";\n        String i = \"mac\";\n        CustomTagsSpanHandler customTagsSpanHandler = new CustomTagsSpanHandler(() -> serviceName, i);\n        MutableSpan mutableSpan = new MutableSpan();\n        customTagsSpanHandler.end(null, mutableSpan, null);\n        assertEquals(serviceName, mutableSpan.localServiceName());\n        assertEquals(i, mutableSpan.tag(CustomTagsSpanHandler.TAG_INSTANCE));\n    }\n\n    @Test\n    public void fillTags() {\n        String serviceName = \"testName\";\n        String i = \"mac\";\n        CustomTagsSpanHandler customTagsSpanHandler = new CustomTagsSpanHandler(() -> serviceName, i);\n        MutableSpan mutableSpan = new MutableSpan();\n        assertTrue(mutableSpan.tags().isEmpty());\n        Map<String, String> tags = new HashMap<>();\n        tags.put(\"tag1\", \"value1\");\n        tags.put(\"tag2\", \"value2\");\n        customTagsSpanHandler.fillTags(mutableSpan, tags);\n        assertFalse(mutableSpan.tags().isEmpty());\n        assertEquals(2, mutableSpan.tags().size());\n        assertEquals(\"value1\", mutableSpan.tag(\"tag1\"));\n        assertEquals(\"value2\", mutableSpan.tag(\"tag2\"));\n    }\n}\n"
  },
  {
    "path": "zipkin/src/test/java/com/megaease/easeagent/zipkin/TracingProviderImplMock.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.zipkin;\n\nimport brave.handler.MutableSpan;\nimport com.megaease.easeagent.mock.config.MockConfig;\nimport com.megaease.easeagent.mock.report.MockReport;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\n\npublic class TracingProviderImplMock {\n    public static final TracingProviderImpl TRACING_PROVIDER = new TracingProviderImpl();\n\n    static {\n        TRACING_PROVIDER.setConfig(MockConfig.getCONFIGS());\n        TRACING_PROVIDER.setAgentReport(MockReport.getAgentReport());\n        TRACING_PROVIDER.afterPropertiesSet();\n    }\n\n\n    public static MutableSpan getMutableSpan(Span span) {\n        return AgentFieldReflectAccessor.getFieldValue(span.unwrap(), \"state\");\n    }\n}\n"
  },
  {
    "path": "zipkin/src/test/java/com/megaease/easeagent/zipkin/TracingProviderImplTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.zipkin;\n\nimport brave.Tracing;\nimport brave.handler.MutableSpan;\nimport brave.propagation.TraceContext;\nimport brave.sampler.BoundarySampler;\nimport brave.sampler.CountingSampler;\nimport brave.sampler.RateLimitingSampler;\nimport brave.sampler.Sampler;\nimport com.megaease.easeagent.config.GlobalConfigs;\nimport com.megaease.easeagent.mock.report.MockReport;\nimport com.megaease.easeagent.plugin.api.config.Config;\nimport com.megaease.easeagent.plugin.api.context.RequestContext;\nimport com.megaease.easeagent.plugin.api.trace.ITracing;\nimport com.megaease.easeagent.plugin.api.trace.Scope;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.api.trace.TracingSupplier;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\nimport com.megaease.easeagent.zipkin.impl.RequestMock;\nimport com.megaease.easeagent.zipkin.impl.SpanImpl;\nimport com.megaease.easeagent.zipkin.impl.TracingImpl;\nimport org.junit.Test;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static org.junit.Assert.*;\n\npublic class TracingProviderImplTest {\n\n    @Test\n    public void setConfig() {\n        afterPropertiesSet();\n    }\n\n    @Test\n    public void setAgentReport() {\n        afterPropertiesSet();\n\n    }\n\n    @Test\n    public void afterPropertiesSet() {\n        TracingProviderImpl tracingProvider = TracingProviderImplMock.TRACING_PROVIDER;\n        assertNotNull(tracingProvider.tracing());\n        assertNotNull(tracingProvider.tracingSupplier());\n        TracingSupplier tracingSupplier = tracingProvider.tracingSupplier();\n        assertNotNull(tracingSupplier.get(() -> null));\n    }\n\n    @Test\n    public void tracing() {\n        afterPropertiesSet();\n    }\n\n    @Test\n    public void tracingSupplier() {\n        afterPropertiesSet();\n    }\n\n    @Test\n    public void getSampler() {\n        TracingProviderImpl tracingProvider = new TracingProviderImpl();\n        {\n            Map<String, String> initConfigs = new HashMap<>();\n            Config config = new GlobalConfigs(initConfigs);\n            tracingProvider.setConfig(config);\n            Sampler sampler = tracingProvider.getSampler();\n            assertSame(sampler, Sampler.ALWAYS_SAMPLE);\n        }\n        {\n            Map<String, String> initConfigs = new HashMap<>();\n            initConfigs.put(\"observability.tracings.sampledType\", TracingProviderImpl.SAMPLER_TYPE_COUNTING);\n            Config config = new GlobalConfigs(initConfigs);\n            tracingProvider.setConfig(config);\n            Sampler sampler = tracingProvider.getSampler();\n            assertSame(sampler, Sampler.ALWAYS_SAMPLE);\n        }\n        {\n            Map<String, String> initConfigs = new HashMap<>();\n            initConfigs.put(\"observability.tracings.sampledType\", \"error\");\n            initConfigs.put(\"observability.tracings.sampled\", \"1\");\n            Config config = new GlobalConfigs(initConfigs);\n            tracingProvider.setConfig(config);\n            Sampler sampler = tracingProvider.getSampler();\n            assertSame(sampler, Sampler.ALWAYS_SAMPLE);\n        }\n        {\n            Map<String, String> initConfigs = new HashMap<>();\n            initConfigs.put(\"observability.tracings.sampledType\", TracingProviderImpl.SAMPLER_TYPE_COUNTING);\n            initConfigs.put(\"observability.tracings.sampled\", \"10\");\n            Config config = new GlobalConfigs(initConfigs);\n            tracingProvider.setConfig(config);\n            Sampler sampler = tracingProvider.getSampler();\n            assertSame(sampler, Sampler.ALWAYS_SAMPLE);\n        }\n        {\n            Map<String, String> initConfigs = new HashMap<>();\n            initConfigs.put(\"observability.tracings.sampledType\", TracingProviderImpl.SAMPLER_TYPE_COUNTING);\n            initConfigs.put(\"observability.tracings.sampled\", \"0.1\");\n            Config config = new GlobalConfigs(initConfigs);\n            tracingProvider.setConfig(config);\n            Sampler sampler = tracingProvider.getSampler();\n            assertTrue(sampler instanceof CountingSampler);\n        }\n        {\n            Map<String, String> initConfigs = new HashMap<>();\n            initConfigs.put(\"observability.tracings.sampledType\", TracingProviderImpl.SAMPLER_TYPE_RATE_LIMITING);\n            initConfigs.put(\"observability.tracings.sampled\", \"10\");\n            Config config = new GlobalConfigs(initConfigs);\n            tracingProvider.setConfig(config);\n            Sampler sampler = tracingProvider.getSampler();\n            assertTrue(sampler instanceof RateLimitingSampler);\n        }\n        {\n            Map<String, String> initConfigs = new HashMap<>();\n            initConfigs.put(\"observability.tracings.sampledType\", TracingProviderImpl.SAMPLER_TYPE_BOUNDARY);\n            initConfigs.put(\"observability.tracings.sampled\", \"0.0001\");\n            Config config = new GlobalConfigs(initConfigs);\n            tracingProvider.setConfig(config);\n            Sampler sampler = tracingProvider.getSampler();\n            assertTrue(sampler instanceof BoundarySampler);\n        }\n    }\n\n    @Test\n    public void testCountingSampler() throws InterruptedException {\n        String name = \"test_name\";\n        Map<String, String> initConfigs = new HashMap<>();\n        initConfigs.put(\"observability.tracings.sampledType\", TracingProviderImpl.SAMPLER_TYPE_COUNTING);\n        initConfigs.put(\"observability.tracings.sampled\", \"0.01\");\n        initConfigs.put(\"name\", \"test_name\");\n        Config config = new GlobalConfigs(initConfigs);\n        TracingProviderImpl tracingProvider = new TracingProviderImpl();\n        tracingProvider.setConfig(config);\n        tracingProvider.setAgentReport(MockReport.getAgentReport());\n        tracingProvider.afterPropertiesSet();\n        Tracing tracing = tracingProvider.tracing();\n        ITracing iTracing = TracingImpl.build(() -> null, tracing);\n        int noopCount = 0;\n        for (int i = 0; i < 100; i++) {\n            RequestMock requestMock = new RequestMock().setKind(Span.Kind.SERVER).setName(name);\n            assertFalse(iTracing.hasCurrentSpan());\n            RequestContext requestContext = iTracing.serverReceive(requestMock);\n            assertTrue(iTracing.hasCurrentSpan());\n            boolean isNoop = requestContext.isNoop();\n            if (isNoop) {\n                noopCount++;\n            }\n            String id;\n            if (isNoop) {\n                TraceContext traceContext = AgentFieldReflectAccessor.getFieldValue(requestContext.span().unwrap(), \"context\");\n                id = traceContext.traceIdString();\n                Span span = iTracing.currentSpan();\n                assertTrue(span instanceof SpanImpl);\n                assertTrue(span.isNoop());\n                Span nextSpan = iTracing.nextSpan();\n                assertTrue(nextSpan instanceof SpanImpl);\n                assertTrue(nextSpan.isNoop());\n                nextSpan.finish();\n            } else {\n                MutableSpan mutableSpan = TracingProviderImplMock.getMutableSpan(requestContext.span());\n                assertEquals(brave.Span.Kind.SERVER, mutableSpan.kind());\n                assertEquals(name, mutableSpan.name());\n                assertNull(mutableSpan.parentId());\n                id = mutableSpan.id();\n            }\n\n            RequestContext requestContext2 = iTracing.serverReceive(requestMock);\n            try (Scope scope = requestContext2.scope()) {\n                assertNotNull(requestContext2.span().parentIdString());\n                assertEquals(id, requestContext2.span().parentIdString());\n            }\n\n            try (Scope scope = requestContext.scope()) {\n                assertNull(requestContext.span().parentIdString());\n                assertTrue(iTracing.hasCurrentSpan());\n\n                RequestMock clientRequest = new RequestMock().setKind(Span.Kind.SERVER).setName(name);\n                RequestContext clientRequestContext = iTracing.clientRequest(clientRequest);\n                assertEquals(requestContext.span().traceIdString(), clientRequestContext.span().traceIdString());\n                assertEquals(requestContext.span().spanIdString(), clientRequestContext.span().parentIdString());\n\n                Thread thread = new Thread(() -> {\n                    RequestMock serverRequest = new RequestMock().setHeaders(clientRequest.getHeaders()).setKind(Span.Kind.SERVER);\n                    RequestContext serverReceive = iTracing.serverReceive(serverRequest);\n                    try (Scope scope1 = serverReceive.scope()) {\n                        assertNotNull(serverReceive.span().parentIdString());\n                        assertEquals(clientRequestContext.span().traceIdString(), serverReceive.span().traceIdString());\n                        assertEquals(clientRequestContext.span().spanIdString(), serverReceive.span().spanIdString());\n                        assertEquals(clientRequestContext.span().parentIdString(), serverReceive.span().parentIdString());\n                    }\n                });\n                thread.start();\n                thread.join();\n\n                clientRequestContext.scope().close();\n            }\n            assertFalse(iTracing.hasCurrentSpan());\n        }\n        assertEquals(99, noopCount);\n    }\n}\n"
  },
  {
    "path": "zipkin/src/test/java/com/megaease/easeagent/zipkin/impl/AsyncRequestTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.zipkin.impl;\n\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\npublic class AsyncRequestTest {\n\n    @Test\n    public void kind() {\n        AsyncRequest asyncRequest = new AsyncRequest(new RequestMock());\n        assertNull(asyncRequest.kind());\n        asyncRequest = new AsyncRequest(new RequestMock().setKind(Span.Kind.PRODUCER));\n        assertEquals(Span.Kind.PRODUCER, asyncRequest.kind());\n    }\n\n    @Test\n    public void name() {\n        AsyncRequest asyncRequest = new AsyncRequest(new RequestMock());\n        assertNull(asyncRequest.name());\n        String name = \"testName\";\n        asyncRequest = new AsyncRequest(new RequestMock().setName(name));\n        assertEquals(name, asyncRequest.name());\n    }\n\n    @Test\n    public void header() {\n        AsyncRequest asyncRequest = new AsyncRequest(new RequestMock());\n        String name = \"testName\";\n        assertNull(asyncRequest.header(name));\n        RequestMock requestMock = new RequestMock();\n        asyncRequest = new AsyncRequest(requestMock);\n        String value = \"value\";\n        requestMock.setHeader(name, value);\n        assertEquals(value, asyncRequest.header(name));\n        assertEquals(value, requestMock.header(name));\n    }\n\n    @Test\n    public void cacheScope() {\n        AsyncRequest asyncRequest = new AsyncRequest(new RequestMock());\n        assertFalse(asyncRequest.cacheScope());\n        asyncRequest = new AsyncRequest(new RequestMock().setCacheScope(true));\n        assertTrue(asyncRequest.cacheScope());\n    }\n\n    @Test\n    public void setHeader() {\n        header();\n    }\n\n    @Test\n    public void getHeaders() {\n        AsyncRequest asyncRequest = new AsyncRequest(new RequestMock());\n        String name = \"testName\";\n        assertNull(asyncRequest.header(name));\n        RequestMock requestMock = new RequestMock();\n        asyncRequest = new AsyncRequest(requestMock);\n        String value = \"value\";\n        asyncRequest.setHeader(name, value);\n        assertEquals(1, asyncRequest.getHeaders().size());\n        assertEquals(value, asyncRequest.getHeaders().get(name));\n    }\n}\n"
  },
  {
    "path": "zipkin/src/test/java/com/megaease/easeagent/zipkin/impl/MessageImplTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.zipkin.impl;\n\nimport brave.Tracing;\nimport brave.propagation.TraceContext;\nimport brave.propagation.TraceContextOrSamplingFlags;\nimport com.megaease.easeagent.plugin.api.trace.Request;\nimport com.megaease.easeagent.zipkin.TracingProviderImplMock;\nimport org.junit.Before;\nimport org.junit.Test;\n\nimport static org.junit.Assert.assertEquals;\n\npublic class MessageImplTest {\n    Tracing tracing;\n    TraceContext.Injector<Request> injector;\n    TraceContext.Extractor<Request> extractor;\n\n\n    @Before\n    public void before() {\n        tracing = TracingProviderImplMock.TRACING_PROVIDER.tracing();\n        injector = tracing.propagation().injector(Request::setHeader);\n        extractor = tracing.propagation().extractor(Request::header);\n    }\n\n    @Test\n    public void get() {\n        brave.Span bSpanN = SpanImpl.nextBraveSpan(tracing, extractor, new RequestMock());\n        RequestMock requestMock = new RequestMock();\n        injector.inject(bSpanN.context(), requestMock);\n        TraceContextOrSamplingFlags traceContextOrSamplingFlags = extractor.extract(requestMock);\n        MessageImpl message = new MessageImpl(traceContextOrSamplingFlags);\n        assertEquals(traceContextOrSamplingFlags, message.get());\n\n    }\n}\n"
  },
  {
    "path": "zipkin/src/test/java/com/megaease/easeagent/zipkin/impl/MessagingRequestMock.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.zipkin.impl;\n\nimport com.megaease.easeagent.plugin.api.trace.MessagingRequest;\n\npublic class MessagingRequestMock extends RequestMock implements MessagingRequest {\n    private String operation;\n    private String channelKind;\n    private String channelName;\n\n\n    public MessagingRequestMock setOperation(String operation) {\n        this.operation = operation;\n        return this;\n    }\n\n    public MessagingRequestMock setChannelKind(String channelKind) {\n        this.channelKind = channelKind;\n        return this;\n    }\n\n    public MessagingRequestMock setChannelName(String channelName) {\n        this.channelName = channelName;\n        return this;\n    }\n\n    @Override\n    public String operation() {\n        return operation;\n    }\n\n    @Override\n    public String channelKind() {\n        return channelKind;\n    }\n\n    @Override\n    public String channelName() {\n        return channelName;\n    }\n\n    @Override\n    public Object unwrap() {\n        return this;\n    }\n}\n"
  },
  {
    "path": "zipkin/src/test/java/com/megaease/easeagent/zipkin/impl/RemoteGetterImplTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.zipkin.impl;\n\nimport brave.Span;\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\npublic class RemoteGetterImplTest {\n\n    @Test\n    public void spanKind() {\n        RemoteGetterImpl getter = new RemoteGetterImpl(Span.Kind.PRODUCER);\n        assertEquals(Span.Kind.PRODUCER, getter.spanKind());\n    }\n\n    @Test\n    public void get() {\n        RemoteGetterImpl getter = new RemoteGetterImpl(Span.Kind.PRODUCER);\n        RequestMock requestMock = new RequestMock();\n        String name = \"test_name\";\n        String value = \"test_value\";\n        requestMock.setHeader(name, value);\n        assertEquals(value, getter.get(requestMock, name));\n    }\n}\n"
  },
  {
    "path": "zipkin/src/test/java/com/megaease/easeagent/zipkin/impl/RemoteSetterImplTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.zipkin.impl;\n\nimport brave.Span;\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\npublic class RemoteSetterImplTest {\n\n    @Test\n    public void spanKind() {\n        RemoteSetterImpl remoteSetter = new RemoteSetterImpl(Span.Kind.PRODUCER);\n        assertEquals(Span.Kind.PRODUCER, remoteSetter.spanKind());\n    }\n\n    @Test\n    public void put() {\n        RemoteSetterImpl remoteSetter = new RemoteSetterImpl(Span.Kind.PRODUCER);\n        RequestMock requestMock = new RequestMock();\n        String name = \"test_name\";\n        String value = \"test_value\";\n        remoteSetter.put(requestMock, name, value);\n        assertEquals(value, requestMock.header(name));\n    }\n}\n"
  },
  {
    "path": "zipkin/src/test/java/com/megaease/easeagent/zipkin/impl/RequestContextImplTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.zipkin.impl;\n\nimport brave.Tracing;\nimport brave.handler.MutableSpan;\nimport brave.propagation.TraceContext;\nimport com.megaease.easeagent.plugin.api.ProgressFields;\nimport com.megaease.easeagent.plugin.api.trace.Request;\nimport com.megaease.easeagent.plugin.api.trace.Response;\nimport com.megaease.easeagent.plugin.api.trace.Scope;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\nimport com.megaease.easeagent.zipkin.TracingProviderImplMock;\nimport org.junit.Before;\nimport org.junit.Test;\n\nimport java.util.Collections;\nimport java.util.Map;\nimport java.util.function.Consumer;\n\nimport static com.megaease.easeagent.plugin.api.ProgressFields.OBSERVABILITY_TRACINGS_TAG_RESPONSE_HEADERS_CONFIG;\nimport static org.junit.Assert.*;\n\npublic class RequestContextImplTest {\n    Tracing tracing;\n    TraceContext.Injector<Request> injector;\n\n    @Before\n    public void before() {\n        tracing = TracingProviderImplMock.TRACING_PROVIDER.tracing();\n        injector = tracing.propagation().injector(Request::setHeader);\n    }\n\n    private void buildOne(Consumer<RequestContextImpl> consumer) {\n        buildOne(new RequestMock(), consumer);\n    }\n\n    private void buildOne(Request request, Consumer<RequestContextImpl> consumer) {\n        Span span = SpanImpl.build(tracing, tracing.tracer().nextSpan(), injector);\n        try (Scope scope = span.maybeScope()) {\n            AsyncRequest asyncRequest = new AsyncRequest(request);\n            RequestContextImpl requestContext = new RequestContextImpl(span, scope, asyncRequest);\n            consumer.accept(requestContext);\n        }\n    }\n\n    @Test\n    public void isNoop() {\n        buildOne(c -> assertFalse(c.isNoop()));\n\n    }\n\n    @Test\n    public void span() {\n        buildOne(c -> {\n            assertNotNull(c.span());\n            assertFalse(c.span().isNoop());\n        });\n    }\n\n    @Test\n    public void scope() {\n        buildOne(c -> {\n            assertNotNull(c.scope());\n            assertTrue(c.scope() instanceof ScopeImpl);\n        });\n    }\n\n    @Test\n    public void setHeader() {\n        String name = \"test_name\";\n        String value = \"test_value\";\n        RequestMock requestMock = new RequestMock();\n        buildOne(requestMock, c -> {\n            c.setHeader(name, value);\n        });\n        assertEquals(value, requestMock.header(name));\n    }\n\n    @Test\n    public void getHeaders() {\n        String name = \"test_name\";\n        String value = \"test_value\";\n        RequestMock requestMock = new RequestMock();\n        buildOne(requestMock, c -> {\n            c.setHeader(name, value);\n            Map<String, String> headers = c.getHeaders();\n            assertEquals(1, headers.size());\n            assertEquals(value, headers.get(name));\n        });\n\n    }\n\n    @Test\n    public void finish() {\n        final String tagName = \"test_name\";\n        final String tagValue = \"test_value\";\n        Response response = name -> {\n            if (tagName.equals(name)) {\n                return tagValue;\n            }\n            return null;\n        };\n        buildOne(c -> {\n            c.span().start();\n            c.finish(response);\n            MutableSpan state = AgentFieldReflectAccessor.getFieldValue(c.span().unwrap(), \"state\");\n            assertNull(state.tag(tagName));\n        });\n        String keyPrefix = OBSERVABILITY_TRACINGS_TAG_RESPONSE_HEADERS_CONFIG;\n        ProgressFields.changeListener().accept(Collections.singletonMap(keyPrefix + tagName, tagName));\n        buildOne(c -> {\n            c.span().start();\n            c.finish(response);\n            MutableSpan state = AgentFieldReflectAccessor.getFieldValue(c.span().unwrap(), \"state\");\n            assertEquals(tagValue, state.tag(tagName));\n        });\n        ProgressFields.changeListener().accept(Collections.singletonMap(keyPrefix + tagName, \"\"));\n    }\n}\n"
  },
  {
    "path": "zipkin/src/test/java/com/megaease/easeagent/zipkin/impl/RequestMock.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.zipkin.impl;\n\nimport com.megaease.easeagent.plugin.api.trace.Request;\nimport com.megaease.easeagent.plugin.api.trace.Span;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\npublic class RequestMock implements Request {\n    private String name;\n    private Span.Kind kind;\n    private boolean cacheScope = false;\n    private Map<String, String> headers = new HashMap<>();\n\n    public RequestMock setName(String name) {\n        this.name = name;\n        return this;\n    }\n\n    public RequestMock setKind(Span.Kind kind) {\n        this.kind = kind;\n        return this;\n    }\n\n    public RequestMock setCacheScope(boolean cacheScope) {\n        this.cacheScope = cacheScope;\n        return this;\n    }\n\n    public RequestMock setHeaders(Map<String, String> headers) {\n        for (Map.Entry<String, String> entry : headers.entrySet()) {\n            this.headers.put(entry.getKey(), entry.getValue());\n        }\n        return this;\n    }\n\n    @Override\n    public Span.Kind kind() {\n        return kind;\n    }\n\n    @Override\n    public String header(String name) {\n        return headers.get(name);\n    }\n\n    @Override\n    public String name() {\n        return name;\n    }\n\n    @Override\n    public boolean cacheScope() {\n        return cacheScope;\n    }\n\n    @Override\n    public void setHeader(String name, String value) {\n        headers.put(name, value);\n    }\n\n    public Map<String, String> getHeaders() {\n        return headers;\n    }\n\n\n}\n"
  },
  {
    "path": "zipkin/src/test/java/com/megaease/easeagent/zipkin/impl/ScopeImplTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.zipkin.impl;\n\nimport brave.Span;\nimport brave.Tracing;\nimport brave.propagation.CurrentTraceContext;\nimport brave.propagation.TraceContext;\nimport com.megaease.easeagent.plugin.api.trace.Request;\nimport com.megaease.easeagent.plugin.api.trace.Scope;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\nimport com.megaease.easeagent.zipkin.TracingProviderImplMock;\nimport org.junit.Before;\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\npublic class ScopeImplTest {\n    Tracing tracing;\n    TraceContext.Injector<Request> injector;\n\n    @Before\n    public void before() {\n        tracing = TracingProviderImplMock.TRACING_PROVIDER.tracing();\n        injector = tracing.propagation().injector(Request::setHeader);\n    }\n\n    @Test\n    public void close() {\n        Span span = tracing.tracer().nextSpan();\n        assertNull(tracing.currentTraceContext().get());\n        CurrentTraceContext.Scope scope = tracing.currentTraceContext().maybeScope(span.context());\n        assertNotNull(tracing.currentTraceContext().get());\n        ScopeImpl scope1 = new ScopeImpl(scope);\n        scope1.close();\n        assertNull(tracing.currentTraceContext().get());\n    }\n\n    @Test\n    public void unwrap() {\n        Span span = tracing.tracer().nextSpan();\n        assertNull(tracing.currentTraceContext().get());\n        CurrentTraceContext.Scope scope = tracing.currentTraceContext().maybeScope(span.context());\n        ScopeImpl scope1 = new ScopeImpl(scope);\n        assertEquals(scope, scope1.unwrap());\n        scope1.close();\n        assertNull(tracing.currentTraceContext().get());\n\n    }\n}\n"
  },
  {
    "path": "zipkin/src/test/java/com/megaease/easeagent/zipkin/impl/SpanContextImplTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.zipkin.impl;\n\nimport brave.Tracing;\nimport com.megaease.easeagent.zipkin.TracingProviderImplMock;\nimport org.junit.Test;\n\nimport static org.junit.Assert.assertFalse;\n\npublic class SpanContextImplTest {\n    Tracing tracing = TracingProviderImplMock.TRACING_PROVIDER.tracing();\n\n    @Test\n    public void isNoop() {\n        SpanContextImpl spanContext = new SpanContextImpl(tracing.tracer().newTrace().context());\n        assertFalse(spanContext.isNoop());\n    }\n\n    @Test\n    public void unwrap() {\n        SpanContextImpl spanContext = new SpanContextImpl(tracing.tracer().newTrace().context());\n        assertFalse(spanContext.unwrap() == spanContext);\n    }\n}\n"
  },
  {
    "path": "zipkin/src/test/java/com/megaease/easeagent/zipkin/impl/SpanImplTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.zipkin.impl;\n\nimport brave.TracerTestUtils;\nimport brave.Tracing;\nimport brave.handler.MutableSpan;\nimport brave.propagation.CurrentTraceContext;\nimport brave.propagation.TraceContext;\nimport com.megaease.easeagent.mock.report.MockAtomicReferenceReportSpanReport;\nimport com.megaease.easeagent.mock.report.MockReport;\nimport com.megaease.easeagent.plugin.api.trace.Request;\nimport com.megaease.easeagent.plugin.api.trace.Scope;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\nimport com.megaease.easeagent.plugin.report.tracing.ReportSpan;\nimport com.megaease.easeagent.zipkin.TracingProviderImplMock;\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\npublic class SpanImplTest {\n    Tracing tracing;\n    TraceContext.Injector<Request> injector;\n    TraceContext.Extractor<Request> extractor;\n    brave.Span bSpan;\n    MutableSpan state;\n    Span span;\n\n\n    @Before\n    public void before() {\n        tracing = TracingProviderImplMock.TRACING_PROVIDER.tracing();\n        injector = tracing.propagation().injector(Request::setHeader);\n        extractor = tracing.propagation().extractor(Request::header);\n        bSpan = tracing.tracer().nextSpan();\n        state = AgentFieldReflectAccessor.getFieldValue(bSpan, \"state\");\n        span = SpanImpl.build(tracing, bSpan, false, injector);\n        TracerTestUtils.clean(tracing.tracer());\n        MockReport.cleanLastSpan();\n        MockReport.cleanSkipSpan();\n    }\n\n    @Test\n    public void build() {\n        Span bSpan = SpanImpl.build(tracing, null, true, injector);\n        assertTrue(bSpan.isNoop());\n        brave.Span brSpan = tracing.tracer().nextSpan();\n        bSpan = SpanImpl.build(tracing, brSpan, false, injector);\n        assertFalse(bSpan.isNoop());\n        assertNull(AgentFieldReflectAccessor.getFieldValue(bSpan, \"scope\"));\n        bSpan = SpanImpl.build(tracing, brSpan, true, injector);\n        assertFalse(bSpan.isNoop());\n        CurrentTraceContext.Scope scope = AgentFieldReflectAccessor.getFieldValue(bSpan, \"scope\");\n        assertNotNull(scope);\n        scope.close();\n    }\n\n    @Test\n    public void build1() {\n        Span bSpan = SpanImpl.build(tracing, null, injector);\n        assertTrue(bSpan.isNoop());\n        brave.Span brSpan = tracing.tracer().nextSpan();\n        bSpan = SpanImpl.build(tracing, brSpan, injector);\n        assertFalse(bSpan.isNoop());\n        assertNull(AgentFieldReflectAccessor.getFieldValue(bSpan, \"scope\"));\n    }\n\n    @Test\n    public void braveKind() {\n        assertEquals(brave.Span.Kind.CLIENT, SpanImpl.braveKind(Span.Kind.CLIENT));\n        assertEquals(brave.Span.Kind.SERVER, SpanImpl.braveKind(Span.Kind.SERVER));\n        assertEquals(brave.Span.Kind.PRODUCER, SpanImpl.braveKind(Span.Kind.PRODUCER));\n        assertEquals(brave.Span.Kind.CONSUMER, SpanImpl.braveKind(Span.Kind.CONSUMER));\n    }\n\n    @Test\n    public void nextBraveSpan() {\n        brave.Span bSpanN = SpanImpl.nextBraveSpan(tracing, extractor, new RequestMock());\n        assertNull(bSpanN.context().parentId());\n        assertEquals(bSpanN.context().spanId(), bSpanN.context().traceId());\n        try (CurrentTraceContext.Scope scope = tracing.currentTraceContext().maybeScope(bSpanN.context())) {\n            String name = \"testName\";\n            brave.Span bSpan2 = SpanImpl.nextBraveSpan(tracing, extractor, new RequestMock().setKind(Span.Kind.PRODUCER).setName(name));\n            assertEquals(bSpanN.context().traceId(), bSpan2.context().traceId());\n            assertNotNull(bSpan2.context().parentId());\n            assertEquals(bSpanN.context().spanId(), bSpan2.context().parentId().longValue());\n            MutableSpan stateN = AgentFieldReflectAccessor.getFieldValue(bSpan2, \"state\");\n            assertEquals(brave.Span.Kind.PRODUCER, stateN.kind());\n            assertEquals(name, stateN.name());\n        }\n\n        Request request = new RequestMock();\n        injector.inject(bSpanN.context(), request);\n        brave.Span bSpan2 = SpanImpl.nextBraveSpan(tracing, extractor, request);\n        assertEquals(bSpanN.context().traceId(), bSpan2.context().traceId());\n        assertNotNull(bSpan2.context().parentId());\n        assertEquals(bSpanN.context().spanId(), bSpan2.context().parentId().longValue());\n\n    }\n\n    @Test\n    public void name() {\n        String name = \"testName1\";\n        span.name(name);\n        assertEquals(name, state.name());\n    }\n\n    @Test\n    public void tag() {\n        String tagName = \"tag1\";\n        String value1 = \"value1\";\n        span.tag(tagName, value1);\n        assertEquals(value1, state.tag(tagName));\n    }\n\n    @Test\n    public void annotate() {\n        brave.Span bSpanA = tracing.tracer().nextSpan();\n        Span eSpan = SpanImpl.build(tracing, bSpanA, injector);\n        MutableSpan stateA = AgentFieldReflectAccessor.getFieldValue(bSpanA, \"state\");\n        assertEquals(0, stateA.startTimestamp());\n        assertEquals(0, stateA.finishTimestamp());\n        eSpan.annotate(\"cs\");\n        assertEquals(brave.Span.Kind.CLIENT, stateA.kind());\n        assertTrue(stateA.startTimestamp() > 0);\n        stateA.startTimestamp(0);\n        assertEquals(0, stateA.startTimestamp());\n        eSpan.annotate(\"sr\");\n        assertEquals(brave.Span.Kind.SERVER, stateA.kind());\n        assertTrue(stateA.startTimestamp() > 0);\n\n        assertEquals(0, stateA.finishTimestamp());\n        eSpan.annotate(\"cr\");\n        assertEquals(brave.Span.Kind.CLIENT, stateA.kind());\n        assertTrue(stateA.finishTimestamp() > 0);\n        stateA.finishTimestamp(0);\n\n        bSpanA = tracing.tracer().nextSpan();\n        eSpan = SpanImpl.build(tracing, bSpanA, injector);\n        stateA = AgentFieldReflectAccessor.getFieldValue(bSpanA, \"state\");\n        assertEquals(0, stateA.finishTimestamp());\n        eSpan.start();\n        eSpan.annotate(\"ss\");\n        assertEquals(brave.Span.Kind.SERVER, stateA.kind());\n        assertTrue(stateA.finishTimestamp() > 0);\n\n    }\n\n    @Test\n    public void isNoop() {\n        assertFalse(span.isNoop());\n    }\n\n    @Test\n    public void start() {\n        state.startTimestamp(0);\n        assertEquals(0, state.startTimestamp());\n        span.start();\n        assertTrue(state.startTimestamp() > 0);\n    }\n\n    @Test\n    public void start1() {\n        state.startTimestamp(0);\n        assertEquals(0, state.startTimestamp());\n        long start = System.nanoTime();\n        span.start(start);\n        assertEquals(start, state.startTimestamp());\n    }\n\n    @Test\n    public void kind() {\n        span.kind(Span.Kind.CLIENT);\n        assertEquals(brave.Span.Kind.CLIENT, state.kind());\n\n        span.kind(Span.Kind.SERVER);\n        assertEquals(brave.Span.Kind.SERVER, state.kind());\n\n        span.kind(Span.Kind.PRODUCER);\n        assertEquals(brave.Span.Kind.PRODUCER, state.kind());\n\n        span.kind(Span.Kind.CONSUMER);\n        assertEquals(brave.Span.Kind.CONSUMER, state.kind());\n    }\n\n    @Test\n    public void annotate1() {\n        brave.Span bSpanA = tracing.tracer().nextSpan();\n        Span eSpan = SpanImpl.build(tracing, bSpanA, injector);\n        MutableSpan stateA = AgentFieldReflectAccessor.getFieldValue(bSpanA, \"state\");\n        eSpan.annotate(System.nanoTime(), \"test_annotate\");\n        assertEquals(1, stateA.annotationCount());\n        assertEquals(\"test_annotate\", stateA.annotationValueAt(0));\n    }\n\n    @Test\n    public void error() {\n        brave.Span bSpanError = tracing.tracer().nextSpan();\n        Span eSpan = SpanImpl.build(tracing, bSpanError, injector);\n        MutableSpan stateError = AgentFieldReflectAccessor.getFieldValue(bSpanError, \"state\");\n        Throwable throwable = new Throwable(\"test error\");\n        eSpan.error(throwable);\n        assertEquals(throwable, stateError.error());\n    }\n\n    @Test\n    public void remoteServiceName() {\n        String remoteServiceName = \"test_remote_service_name\";\n        span.remoteServiceName(remoteServiceName);\n        assertEquals(remoteServiceName, state.remoteServiceName());\n    }\n\n    @Test\n    public void remoteIpAndPort() {\n        String ip = \"192.0.0.11\";\n        int port = 8081;\n        span.remoteIpAndPort(ip, port);\n        assertEquals(ip, state.remoteIp());\n        assertEquals(port, state.remotePort());\n    }\n\n    @Test\n    public void abandon() {\n        Span eSpan = SpanImpl.build(tracing, tracing.tracer().nextSpan(), injector);\n        MockAtomicReferenceReportSpanReport mockAtomicReferenceReport = new MockAtomicReferenceReportSpanReport();\n        MockReport.setMockSpanReport(mockAtomicReferenceReport);\n        eSpan.abandon();\n        assertNull(MockReport.getLastSpan());\n        MockReport.cleanSkipSpan();\n        eSpan.flush();\n        assertNull(MockReport.getLastSpan());\n    }\n\n    @Test\n    public void finish() {\n        Span eSpan = SpanImpl.build(tracing, tracing.tracer().nextSpan(), injector);\n        eSpan.start();\n        eSpan.finish();\n        assertNotNull(MockReport.getLastSpan());\n    }\n\n    @Test\n    public void finish1() {\n        Span eSpan = SpanImpl.build(tracing, tracing.tracer().nextSpan(), injector);\n        eSpan.start();\n        eSpan.finish(System.nanoTime());\n        assertNotNull(MockReport.getLastSpan());\n    }\n\n    @Test\n    public void flush() {\n        Span eSpan = SpanImpl.build(tracing, tracing.tracer().nextSpan(), injector);\n        eSpan.start();\n        eSpan.flush();\n        assertNull(MockReport.getLastSpan());\n        assertNotNull(MockReport.getLastSkipSpan());\n        ReportSpan reportSpan = MockReport.getLastSkipSpan();\n        Assert.assertNotNull(reportSpan);\n        assertEquals(eSpan.traceIdString(), reportSpan.traceId());\n        assertEquals(eSpan.spanIdString(), reportSpan.id());\n    }\n\n    @Test\n    public void inject() {\n        RequestMock requestMock = new RequestMock();\n        span.inject(requestMock);\n        assertTrue(requestMock.getHeaders().size() > 0);\n    }\n\n    @Test\n    public void maybeScope() {\n        Span eSpan = SpanImpl.build(tracing, tracing.tracer().nextSpan(), injector);\n        Span eSpan2 = SpanImpl.build(tracing, tracing.tracer().nextSpan(), injector);\n        assertNull(eSpan2.parentId());\n        try (Scope scope = eSpan.maybeScope()) {\n            eSpan2 = SpanImpl.build(tracing, tracing.tracer().nextSpan(), injector);\n            checkParentId(eSpan2, eSpan);\n        }\n    }\n\n    public void checkParentId(Span span, Span parent) {\n        assertEquals(span.traceId(), parent.traceId());\n        assertNotNull(span.parentId());\n        assertEquals(parent.spanId(), span.parentId());\n    }\n\n    @Test\n    public void cacheScope() {\n        Span eSpan = SpanImpl.build(tracing, tracing.tracer().nextSpan(), injector);\n        Span eSpan2 = SpanImpl.build(tracing, tracing.tracer().nextSpan(), injector);\n        assertNull(eSpan2.parentId());\n        eSpan.cacheScope();\n        eSpan.start();\n        try {\n            eSpan2 = SpanImpl.build(tracing, tracing.tracer().nextSpan(), injector);\n            checkParentId(eSpan2, eSpan);\n        } finally {\n            eSpan.finish();\n        }\n        eSpan2 = SpanImpl.build(tracing, tracing.tracer().nextSpan(), injector);\n        assertNull(eSpan2.parentId());\n    }\n\n    @Test\n    public void traceIdString() {\n        assertNotNull(span.traceIdString());\n    }\n\n    @Test\n    public void spanIdString() {\n        assertNotNull(span.spanIdString());\n    }\n\n    @Test\n    public void parentIdString() {\n        Span eSpan = SpanImpl.build(tracing, tracing.tracer().nextSpan(), injector);\n        Span eSpan2 = SpanImpl.build(tracing, tracing.tracer().nextSpan(), injector);\n        assertNull(eSpan.parentIdString());\n        assertNull(eSpan2.parentIdString());\n        try (Scope scope = eSpan.maybeScope()) {\n            eSpan2 = SpanImpl.build(tracing, tracing.tracer().nextSpan(), injector);\n            assertNotNull(eSpan2.parentIdString());\n            assertEquals(eSpan.spanIdString(), eSpan2.parentIdString());\n        }\n    }\n\n    @Test\n    public void traceId() {\n        assertNotNull(span.traceId());\n    }\n\n    @Test\n    public void spanId() {\n        assertNotNull(span.spanId());\n    }\n\n    @Test\n    public void parentId() {\n        Span eSpan = SpanImpl.build(tracing, tracing.tracer().nextSpan(), injector);\n        Span eSpan2 = SpanImpl.build(tracing, tracing.tracer().nextSpan(), injector);\n        assertNull(eSpan.parentId());\n        assertNull(eSpan2.parentId());\n        try (Scope scope = eSpan.maybeScope()) {\n            eSpan2 = SpanImpl.build(tracing, tracing.tracer().nextSpan(), injector);\n            assertNotNull(eSpan2.parentId());\n            assertEquals(eSpan.spanId(), eSpan2.parentId());\n        }\n    }\n\n    @Test\n    public void unwrap() {\n        Span eSpan = SpanImpl.build(tracing, tracing.tracer().nextSpan(), injector);\n        assertNotNull(eSpan.unwrap());\n        assertTrue(eSpan.unwrap() instanceof brave.Span);\n    }\n}\n"
  },
  {
    "path": "zipkin/src/test/java/com/megaease/easeagent/zipkin/impl/TracingImplTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.zipkin.impl;\n\nimport brave.Tracing;\nimport brave.handler.MutableSpan;\nimport brave.propagation.TraceContext;\nimport brave.propagation.TraceContextOrSamplingFlags;\nimport com.megaease.easeagent.plugin.api.InitializeContext;\nimport com.megaease.easeagent.plugin.api.context.RequestContext;\nimport com.megaease.easeagent.plugin.api.trace.*;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\nimport com.megaease.easeagent.zipkin.TracingProviderImplMock;\nimport org.junit.Before;\nimport org.junit.Test;\n\nimport java.util.function.Supplier;\n\nimport static org.junit.Assert.*;\n\npublic class TracingImplTest {\n    public static final String MESSAGE_B3_HEADER_NAME = \"b3\";\n    String operation = \"test_operation\";\n    String channelKind = \"test_channelKind\";\n    String channelName = \"test_channelName\";\n\n    Tracing tracing;\n    ITracing iTracing;\n    String name = \"test_name\";\n\n    @Before\n    public void before() {\n        tracing = TracingProviderImplMock.TRACING_PROVIDER.tracing();\n        iTracing = TracingImpl.build(() -> null, tracing);\n    }\n\n    @Test\n    public void build() {\n        assertNotNull(iTracing);\n    }\n\n    @Test\n    public void isNoop() {\n        assertFalse(iTracing.isNoop());\n    }\n\n    @Test\n    public void hasCurrentSpan() {\n        Span span = iTracing.nextSpan();\n        assertFalse(iTracing.hasCurrentSpan());\n        try (Scope scope = span.maybeScope()) {\n            assertTrue(iTracing.hasCurrentSpan());\n        }\n        assertFalse(iTracing.hasCurrentSpan());\n    }\n\n    @Test\n    public void currentSpan() {\n        Span span = iTracing.nextSpan();\n        Span currentSpan = iTracing.currentSpan();\n        assertTrue(currentSpan.isNoop());\n        try (Scope scope = span.maybeScope()) {\n            currentSpan = iTracing.currentSpan();\n            assertFalse(currentSpan.isNoop());\n            assertEquals(span.traceIdString(), currentSpan.traceIdString());\n            assertEquals(span.spanIdString(), currentSpan.spanIdString());\n            assertEquals(span.parentIdString(), currentSpan.parentIdString());\n        }\n        currentSpan = iTracing.currentSpan();\n        assertTrue(currentSpan.isNoop());\n    }\n\n    @Test\n    public void exportAsync() {\n        Span span = iTracing.nextSpan();\n        SpanContext spanContext = iTracing.exportAsync();\n        assertTrue(spanContext.isNoop());\n        try (Scope scope = span.maybeScope()) {\n            spanContext = iTracing.exportAsync();\n            assertFalse(spanContext.isNoop());\n            assertTrue(spanContext.unwrap() instanceof TraceContext);\n            TraceContext traceContext = (TraceContext) spanContext.unwrap();\n            assertEquals(span.traceIdString(), traceContext.traceIdString());\n            assertEquals(span.spanIdString(), traceContext.spanIdString());\n            assertEquals(span.parentIdString(), traceContext.parentIdString());\n        }\n        spanContext = iTracing.exportAsync();\n        assertTrue(spanContext.isNoop());\n    }\n\n    @Test\n    public void importAsync() throws InterruptedException {\n        Span span = iTracing.nextSpan();\n        SpanContext spanContext = iTracing.exportAsync();\n        assertTrue(spanContext.isNoop());\n        Scope importScope = iTracing.importAsync(spanContext);\n        assertNull(importScope.unwrap());\n        assertFalse(iTracing.hasCurrentSpan());\n        Scope scope = span.maybeScope();\n        final SpanContext context = iTracing.exportAsync();\n        assertFalse(context.isNoop());\n        scope.close();\n        assertFalse(iTracing.hasCurrentSpan());\n        Thread thread = new Thread(() -> {\n            assertFalse(iTracing.hasCurrentSpan());\n            iTracing.importAsync(context);\n            assertTrue(iTracing.hasCurrentSpan());\n            Span currentSpan = iTracing.currentSpan();\n            assertEquals(span.traceIdString(), currentSpan.traceIdString());\n            assertEquals(span.spanIdString(), currentSpan.spanIdString());\n            assertEquals(span.parentIdString(), currentSpan.parentIdString());\n\n            Span nextSpan = iTracing.nextSpan();\n            assertEquals(span.traceIdString(), nextSpan.traceIdString());\n            assertEquals(span.spanIdString(), nextSpan.parentIdString());\n        });\n        thread.start();\n        thread.join();\n    }\n\n    @Test\n    public void clientRequest() {\n        RequestMock requestMock = new RequestMock().setKind(Span.Kind.CLIENT).setName(name);\n        assertFalse(iTracing.hasCurrentSpan());\n        RequestContext requestContext = iTracing.clientRequest(requestMock);\n        assertTrue(iTracing.hasCurrentSpan());\n        MutableSpan mutableSpan = TracingProviderImplMock.getMutableSpan(requestContext.span());\n        assertEquals(brave.Span.Kind.CLIENT, mutableSpan.kind());\n        assertEquals(name, mutableSpan.name());\n        try (Scope scope = requestContext.scope()) {\n            assertNull(requestContext.span().parentIdString());\n            RequestContext requestContext2 = iTracing.clientRequest(requestMock);\n            assertEquals(requestContext.span().traceIdString(), requestContext2.span().traceIdString());\n            assertEquals(requestContext.span().spanIdString(), requestContext2.span().parentIdString());\n            requestContext2.scope().close();\n        }\n        assertFalse(iTracing.hasCurrentSpan());\n    }\n\n    @Test\n    public void serverReceive() throws InterruptedException {\n        RequestMock requestMock = new RequestMock().setKind(Span.Kind.SERVER).setName(name);\n        assertFalse(iTracing.hasCurrentSpan());\n        RequestContext requestContext = iTracing.serverReceive(requestMock);\n        assertTrue(iTracing.hasCurrentSpan());\n        MutableSpan mutableSpan = TracingProviderImplMock.getMutableSpan(requestContext.span());\n        assertEquals(brave.Span.Kind.SERVER, mutableSpan.kind());\n        assertEquals(name, mutableSpan.name());\n        assertNull(mutableSpan.parentId());\n\n        RequestContext requestContext2 = iTracing.serverReceive(requestMock);\n        try (Scope scope = requestContext2.scope()) {\n            assertNotNull(requestContext2.span().parentIdString());\n            assertEquals(mutableSpan.id(), requestContext2.span().parentIdString());\n        }\n\n        try (Scope scope = requestContext.scope()) {\n            assertNull(requestContext.span().parentIdString());\n            assertTrue(iTracing.hasCurrentSpan());\n\n            RequestMock clientRequest = new RequestMock().setKind(Span.Kind.SERVER).setName(name);\n            RequestContext clientRequestContext = iTracing.clientRequest(clientRequest);\n            assertEquals(requestContext.span().traceIdString(), clientRequestContext.span().traceIdString());\n            assertEquals(requestContext.span().spanIdString(), clientRequestContext.span().parentIdString());\n\n            Thread thread = new Thread(() -> {\n                RequestMock serverRequest = new RequestMock().setHeaders(clientRequest.getHeaders()).setKind(Span.Kind.SERVER);\n                RequestContext serverReceive = iTracing.serverReceive(serverRequest);\n                try (Scope scope1 = serverReceive.scope()) {\n                    assertNotNull(serverReceive.span().parentIdString());\n                    assertEquals(clientRequestContext.span().traceIdString(), serverReceive.span().traceIdString());\n                    assertEquals(clientRequestContext.span().spanIdString(), serverReceive.span().spanIdString());\n                    assertEquals(clientRequestContext.span().parentIdString(), serverReceive.span().parentIdString());\n                }\n            });\n            thread.start();\n            thread.join();\n\n            clientRequestContext.scope().close();\n        }\n        assertFalse(iTracing.hasCurrentSpan());\n    }\n\n    @Test\n    public void propagationKeys() {\n        assertNotNull(iTracing.propagationKeys());\n        assertTrue(iTracing.propagationKeys().size() > 0);\n        assertTrue(iTracing.propagationKeys().contains(\"b3\"));\n    }\n\n    @Test\n    public void nextSpan() {\n        Span span = iTracing.nextSpan();\n        assertFalse(iTracing.hasCurrentSpan());\n        assertNull(span.parentIdString());\n        try (Scope scope = span.maybeScope()) {\n            Span span1 = iTracing.nextSpan();\n            assertEquals(span.traceIdString(), span1.traceIdString());\n            assertEquals(span.spanIdString(), span1.parentIdString());\n        }\n    }\n\n    @Test\n    public void messagingTracing() {\n        assertNotNull(iTracing.messagingTracing());\n    }\n\n    @Test\n    public void unwrap() {\n        Object u = iTracing.unwrap();\n        assertTrue(u instanceof Tracing);\n    }\n\n    private void checkTag(MutableSpan state) {\n        assertEquals(operation, state.tag(\"messaging.operation\"));\n        assertEquals(channelKind, state.tag(\"messaging.channel_kind\"));\n        assertEquals(channelName, state.tag(\"messaging.channel_name\"));\n    }\n\n\n    @Test\n    public void consumerSpan() {\n        Span span = iTracing.consumerSpan(new MessagingRequestMock().setOperation(operation).setChannelKind(channelKind).setChannelName(channelName));\n        assertFalse(span.isNoop());\n        MutableSpan state = AgentFieldReflectAccessor.getFieldValue(span.unwrap(), \"state\");\n        checkTag(state);\n\n\n        span = iTracing.producerSpan(new MessagingRequestMock().setOperation(operation).setChannelKind(channelKind).setChannelName(channelName));\n\n        MessagingRequestMock messagingRequestMock = new MessagingRequestMock();\n        iTracing.messagingTracing().producerInjector().inject(span, messagingRequestMock);\n\n        MessagingRequestMock request1 = new MessagingRequestMock();\n        request1.setHeaders(messagingRequestMock.getHeaders());\n        Span newSpan1 = iTracing.consumerSpan(request1);\n        assertEquals(span.traceIdString(), newSpan1.traceIdString());\n        assertNotEquals(span.spanIdString(), newSpan1.spanIdString());\n\n        MessagingRequestMock request2 = new MessagingRequestMock();\n        request2.setHeaders(messagingRequestMock.getHeaders());\n        Span newSpan2 = iTracing.consumerSpan(request1);\n        assertEquals(span.traceIdString(), newSpan2.traceIdString());\n        assertNotEquals(span.spanIdString(), newSpan2.spanIdString());\n        assertNotEquals(newSpan1.spanIdString(), newSpan2.spanIdString());\n    }\n\n    @Test\n    public void producerSpan() {\n        Span span = iTracing.producerSpan(new MessagingRequestMock().setOperation(operation).setChannelKind(channelKind).setChannelName(channelName));\n        assertFalse(span.isNoop());\n        MutableSpan state = AgentFieldReflectAccessor.getFieldValue(span.unwrap(), \"state\");\n        checkTag(state);\n\n        MessagingRequestMock messagingRequestMock = new MessagingRequestMock();\n        iTracing.messagingTracing().producerInjector().inject(span, messagingRequestMock);\n\n        MessagingRequestMock request1 = new MessagingRequestMock();\n        request1.setHeaders(messagingRequestMock.getHeaders());\n        Span newSpan1 = iTracing.producerSpan(messagingRequestMock);\n        assertEquals(span.traceIdString(), newSpan1.traceIdString());\n        assertNotEquals(span.spanIdString(), newSpan1.spanIdString());\n\n        MessagingRequestMock request2 = new MessagingRequestMock();\n        request2.setHeaders(messagingRequestMock.getHeaders());\n        Span newSpan2 = iTracing.producerSpan(messagingRequestMock);\n        assertEquals(span.traceIdString(), newSpan2.traceIdString());\n        assertNotEquals(span.spanIdString(), newSpan2.spanIdString());\n        assertNotEquals(newSpan1.spanIdString(), newSpan2.spanIdString());\n    }\n}\n"
  },
  {
    "path": "zipkin/src/test/java/com/megaease/easeagent/zipkin/impl/message/MessagingTracingImplTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.zipkin.impl.message;\n\nimport brave.Tracing;\nimport brave.handler.MutableSpan;\nimport brave.propagation.TraceContextOrSamplingFlags;\nimport com.megaease.easeagent.plugin.api.trace.Message;\nimport com.megaease.easeagent.plugin.api.trace.MessagingRequest;\nimport com.megaease.easeagent.plugin.api.trace.MessagingTracing;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.bridge.NoOpTracer;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\nimport com.megaease.easeagent.zipkin.TracingProviderImplMock;\nimport com.megaease.easeagent.zipkin.impl.MessagingRequestMock;\nimport org.junit.Before;\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\npublic class MessagingTracingImplTest {\n    public static final String MESSAGE_B3_HEADER_NAME = \"b3\";\n    String operation = \"test_operation\";\n    String channelKind = \"test_channelKind\";\n    String channelName = \"test_channelName\";\n    Tracing tracing;\n    MessagingTracing<MessagingRequest> messagingTracing;\n\n    @Before\n    public void before() {\n        tracing = TracingProviderImplMock.TRACING_PROVIDER.tracing();\n        messagingTracing = MessagingTracingImpl.build(tracing);\n    }\n\n    @Test\n    public void build() {\n        MessagingTracing<MessagingRequest> messagingTracing = MessagingTracingImpl.build(null);\n        assertTrue(messagingTracing instanceof NoOpTracer.EmptyMessagingTracing);\n        messagingTracing = MessagingTracingImpl.build(tracing);\n        assertFalse(messagingTracing instanceof NoOpTracer.EmptyMessagingTracing);\n    }\n\n\n    private void checkTag(MutableSpan state) {\n        assertEquals(operation, state.tag(\"messaging.operation\"));\n        assertEquals(channelKind, state.tag(\"messaging.channel_kind\"));\n        assertEquals(channelName, state.tag(\"messaging.channel_name\"));\n    }\n\n    @Test\n    public void consumerSpan() {\n        Span span = messagingTracing.consumerSpan(new MessagingRequestMock().setOperation(operation).setChannelKind(channelKind).setChannelName(channelName));\n        assertFalse(span.isNoop());\n        MutableSpan state = AgentFieldReflectAccessor.getFieldValue(span.unwrap(), \"state\");\n        assertNotNull(state);\n        checkTag(state);\n\n\n        span = messagingTracing.producerSpan(new MessagingRequestMock().setOperation(operation).setChannelKind(channelKind).setChannelName(channelName));\n\n        MessagingRequestMock messagingRequestMock = new MessagingRequestMock();\n        messagingTracing.producerInjector().inject(span, messagingRequestMock);\n\n        MessagingRequestMock request1 = new MessagingRequestMock();\n        request1.setHeaders(messagingRequestMock.getHeaders());\n        Span newSpan1 = messagingTracing.consumerSpan(request1);\n        assertEquals(span.traceIdString(), newSpan1.traceIdString());\n        assertNotEquals(span.spanIdString(), newSpan1.spanIdString());\n\n        MessagingRequestMock request2 = new MessagingRequestMock();\n        request2.setHeaders(messagingRequestMock.getHeaders());\n        Span newSpan2 = messagingTracing.consumerSpan(request1);\n        assertEquals(span.traceIdString(), newSpan2.traceIdString());\n        assertNotEquals(span.spanIdString(), newSpan2.spanIdString());\n        assertNotEquals(newSpan1.spanIdString(), newSpan2.spanIdString());\n\n    }\n\n    @Test\n    public void producerSpan() {\n        Span span = messagingTracing.producerSpan(new MessagingRequestMock().setOperation(operation).setChannelKind(channelKind).setChannelName(channelName));\n        assertFalse(span.isNoop());\n        MutableSpan state = AgentFieldReflectAccessor.getFieldValue(span.unwrap(), \"state\");\n        assertNotNull(state);\n        checkTag(state);\n\n        MessagingRequestMock messagingRequestMock = new MessagingRequestMock();\n        messagingTracing.producerInjector().inject(span, messagingRequestMock);\n\n        MessagingRequestMock request1 = new MessagingRequestMock();\n        request1.setHeaders(messagingRequestMock.getHeaders());\n        Span newSpan1 = messagingTracing.producerSpan(messagingRequestMock);\n        assertEquals(span.traceIdString(), newSpan1.traceIdString());\n        assertNotEquals(span.spanIdString(), newSpan1.spanIdString());\n\n        MessagingRequestMock request2 = new MessagingRequestMock();\n        request2.setHeaders(messagingRequestMock.getHeaders());\n        Span newSpan2 = messagingTracing.producerSpan(messagingRequestMock);\n        assertEquals(span.traceIdString(), newSpan2.traceIdString());\n        assertNotEquals(span.spanIdString(), newSpan2.spanIdString());\n        assertNotEquals(newSpan1.spanIdString(), newSpan2.spanIdString());\n\n    }\n\n    @Test\n    public void producerInjector() {\n        Span span = messagingTracing.producerSpan(new MessagingRequestMock().setOperation(operation).setChannelKind(channelKind).setChannelName(channelName));\n        MessagingRequestMock messagingRequestMock = new MessagingRequestMock();\n        messagingTracing.producerInjector().inject(span, messagingRequestMock);\n        assertTrue(messagingRequestMock.getHeaders().size() > 0);\n        assertEquals(1, messagingRequestMock.getHeaders().size());\n        assertNotNull(messagingRequestMock.header(MESSAGE_B3_HEADER_NAME));\n        assertTrue(messagingRequestMock.header(MESSAGE_B3_HEADER_NAME).contains(span.traceIdString()));\n    }\n\n    @Test\n    public void consumerInjector() {\n        Span span = messagingTracing.consumerSpan(new MessagingRequestMock().setOperation(operation).setChannelKind(channelKind).setChannelName(channelName));\n        MessagingRequestMock messagingRequestMock = new MessagingRequestMock();\n        messagingTracing.consumerInjector().inject(span, messagingRequestMock);\n        assertTrue(messagingRequestMock.getHeaders().size() > 0);\n        assertEquals(1, messagingRequestMock.getHeaders().size());\n        assertNotNull(messagingRequestMock.header(MESSAGE_B3_HEADER_NAME));\n        assertTrue(messagingRequestMock.header(MESSAGE_B3_HEADER_NAME).contains(span.traceIdString()));\n    }\n\n\n    private void check(Span span, Message message) {\n        assertNotNull(message);\n        assertNotNull(message.get());\n        assertTrue(message.get() instanceof TraceContextOrSamplingFlags);\n        TraceContextOrSamplingFlags traceContextOrSamplingFlags = (TraceContextOrSamplingFlags) message.get();\n        assertTrue(traceContextOrSamplingFlags.sampled());\n        assertEquals(span.traceIdString(), traceContextOrSamplingFlags.context().traceIdString());\n        assertEquals(span.spanIdString(), traceContextOrSamplingFlags.context().spanIdString());\n        assertEquals(span.parentIdString(), traceContextOrSamplingFlags.context().parentIdString());\n    }\n\n    @Test\n    public void producerExtractor() {\n        Span span = messagingTracing.producerSpan(new MessagingRequestMock().setOperation(operation).setChannelKind(channelKind).setChannelName(channelName));\n        MessagingRequestMock messagingRequestMock = new MessagingRequestMock();\n        messagingTracing.producerInjector().inject(span, messagingRequestMock);\n        Message message = messagingTracing.producerExtractor().extract(messagingRequestMock);\n        check(span, message);\n\n        Span span2 = messagingTracing.consumerSpan(new MessagingRequestMock().setOperation(operation).setChannelKind(channelKind).setChannelName(channelName));\n        MessagingRequestMock messagingRequestMock2 = new MessagingRequestMock();\n        messagingTracing.consumerInjector().inject(span2, messagingRequestMock2);\n        Message message2 = messagingTracing.consumerExtractor().extract(messagingRequestMock2);\n        check(span2, message2);\n    }\n\n\n    @Test\n    public void consumerExtractor() {\n        Span span = messagingTracing.consumerSpan(new MessagingRequestMock().setOperation(operation).setChannelKind(channelKind).setChannelName(channelName));\n        MessagingRequestMock messagingRequestMock = new MessagingRequestMock();\n        messagingTracing.consumerInjector().inject(span, messagingRequestMock);\n        Message message = messagingTracing.consumerExtractor().extract(messagingRequestMock);\n        check(span, message);\n    }\n\n    @Test\n    public void consumerSampler() {\n        Span span = messagingTracing.consumerSpan(new MessagingRequestMock().setOperation(operation).setChannelKind(channelKind).setChannelName(channelName));\n        MessagingRequestMock messagingRequestMock = new MessagingRequestMock();\n        messagingTracing.consumerInjector().inject(span, messagingRequestMock);\n        assertNull(messagingTracing.consumerSampler().apply(messagingRequestMock));\n    }\n\n    @Test\n    public void producerSampler() {\n        Span span = messagingTracing.producerSpan(new MessagingRequestMock().setOperation(operation).setChannelKind(channelKind).setChannelName(channelName));\n        MessagingRequestMock messagingRequestMock = new MessagingRequestMock();\n        messagingTracing.consumerInjector().inject(span, messagingRequestMock);\n        assertNull(messagingTracing.producerSampler().apply(messagingRequestMock));\n    }\n\n}\n"
  },
  {
    "path": "zipkin/src/test/java/com/megaease/easeagent/zipkin/impl/message/ZipkinConsumerRequestTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.zipkin.impl.message;\n\nimport com.megaease.easeagent.zipkin.impl.MessagingRequestMock;\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\npublic class ZipkinConsumerRequestTest {\n\n    @Test\n    public void operation() {\n        String operation = \"operation\";\n        MessagingRequestMock messagingRequestMock = new MessagingRequestMock().setOperation(operation);\n        assertEquals(operation, new ZipkinConsumerRequest(messagingRequestMock).operation());\n    }\n\n    @Test\n    public void channelKind() {\n        String channelKind = \"channelKind\";\n        MessagingRequestMock messagingRequestMock = new MessagingRequestMock().setChannelKind(channelKind);\n        assertEquals(channelKind, new ZipkinConsumerRequest(messagingRequestMock).channelKind());\n\n    }\n\n    @Test\n    public void channelName() {\n        String channelName = \"channelName\";\n        MessagingRequestMock messagingRequestMock = new MessagingRequestMock().setChannelName(channelName);\n        assertEquals(channelName, new ZipkinConsumerRequest(messagingRequestMock).channelName());\n\n    }\n\n    @Test\n    public void unwrap() {\n        MessagingRequestMock messagingRequestMock = new MessagingRequestMock();\n        assertEquals(messagingRequestMock, new ZipkinConsumerRequest(messagingRequestMock).unwrap());\n    }\n}\n"
  },
  {
    "path": "zipkin/src/test/java/com/megaease/easeagent/zipkin/impl/message/ZipkinProducerRequestTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.zipkin.impl.message;\n\nimport com.megaease.easeagent.zipkin.impl.MessagingRequestMock;\nimport org.junit.Test;\n\nimport static org.junit.Assert.assertEquals;\n\npublic class ZipkinProducerRequestTest {\n\n    @Test\n    public void operation() {\n        String operation = \"operation\";\n        MessagingRequestMock messagingRequestMock = new MessagingRequestMock().setOperation(operation);\n        assertEquals(operation, new ZipkinProducerRequest(messagingRequestMock).operation());\n    }\n\n    @Test\n    public void channelKind() {\n        String channelKind = \"channelKind\";\n        MessagingRequestMock messagingRequestMock = new MessagingRequestMock().setChannelKind(channelKind);\n        assertEquals(channelKind, new ZipkinProducerRequest(messagingRequestMock).channelKind());\n\n    }\n\n    @Test\n    public void channelName() {\n        String channelName = \"channelName\";\n        MessagingRequestMock messagingRequestMock = new MessagingRequestMock().setChannelName(channelName);\n        assertEquals(channelName, new ZipkinProducerRequest(messagingRequestMock).channelName());\n\n    }\n\n    @Test\n    public void unwrap() {\n        MessagingRequestMock messagingRequestMock = new MessagingRequestMock();\n        assertEquals(messagingRequestMock, new ZipkinProducerRequest(messagingRequestMock).unwrap());\n    }\n}\n"
  },
  {
    "path": "zipkin/src/test/java/com/megaease/easeagent/zipkin/logging/AgentLogMDCTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.zipkin.logging;\n\nimport org.junit.Test;\nimport org.slf4j.MDC;\n\nimport static org.junit.Assert.*;\n\npublic class AgentLogMDCTest {\n\n    @Test\n    public void create() {\n        LogUtilsTest.reset();\n        try (LogUtilsTest.Close ignored = LogUtilsTest.reset()) {\n            ClassLoader classLoader1 = LogUtilsTest.getClassLoader(new String[]{\"log4j-slf4j-impl\", \"log4j-core\", \"log4j-api\"});\n            assertNotNull(AgentLogMDC.create(classLoader1));\n        }\n        try (LogUtilsTest.Close ignored = LogUtilsTest.reset()) {\n            ClassLoader classLoader2 = LogUtilsTest.getClassLoader(new String[]{\"slf4j-api\", \"logback-core\", \"logback-access\", \"logback-classic\"});\n            assertNotNull(AgentLogMDC.create(classLoader2));\n        }\n    }\n\n    @Test\n    public void put() {\n        try (LogUtilsTest.Close ignored = LogUtilsTest.reset()) {\n            AgentLogMDC agentLogMDC = AgentLogMDC.create(Thread.currentThread().getContextClassLoader());\n            String name = \"testName\";\n            String value = \"testValue\";\n            agentLogMDC.put(name, value);\n            assertEquals(value, agentLogMDC.get(name));\n            assertEquals(value, MDC.get(name));\n            MDC.remove(name);\n            assertNull(agentLogMDC.get(name));\n            assertNull(MDC.get(name));\n        }\n    }\n\n    @Test\n    public void get() {\n        put();\n    }\n\n    @Test\n    public void remove() {\n        put();\n    }\n}\n"
  },
  {
    "path": "zipkin/src/test/java/com/megaease/easeagent/zipkin/logging/AgentMDCScopeDecoratorTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.zipkin.logging;\n\nimport brave.Tracing;\nimport brave.baggage.BaggageFields;\nimport brave.propagation.TraceContext;\nimport com.megaease.easeagent.mock.context.MockContext;\nimport com.megaease.easeagent.plugin.api.trace.Request;\nimport com.megaease.easeagent.plugin.api.trace.Scope;\nimport com.megaease.easeagent.plugin.api.trace.Span;\nimport com.megaease.easeagent.plugin.bridge.EaseAgent;\nimport com.megaease.easeagent.zipkin.TracingProviderImplMock;\nimport com.megaease.easeagent.zipkin.impl.SpanImpl;\nimport org.junit.Before;\nimport org.junit.Test;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNull;\n\n@MockContext\npublic class AgentMDCScopeDecoratorTest {\n    Tracing tracing;\n    TraceContext.Injector<Request> injector;\n\n    @Before\n    public void before() {\n        tracing = TracingProviderImplMock.TRACING_PROVIDER.tracing();\n        injector = tracing.propagation().injector(Request::setHeader);\n    }\n\n\n    public void checkSpanIds(AgentLogMDC agentLogMDC, Span eSpan) {\n        assertEquals(eSpan.traceIdString(), agentLogMDC.get(BaggageFields.TRACE_ID.name()));\n        assertEquals(eSpan.spanIdString(), agentLogMDC.get(BaggageFields.SPAN_ID.name()));\n    }\n\n\n    public void checkEmptySpanIds(AgentLogMDC agentLogMDC) {\n        assertNull(agentLogMDC.get(BaggageFields.TRACE_ID.name()));\n        assertNull(agentLogMDC.get(BaggageFields.SPAN_ID.name()));\n    }\n\n    public void checkSpanIds(Span eSpan) {\n        assertEquals(eSpan.traceIdString(), EaseAgent.loggerMdc.get(BaggageFields.TRACE_ID.name()));\n        assertEquals(eSpan.spanIdString(), EaseAgent.loggerMdc.get(BaggageFields.SPAN_ID.name()));\n    }\n\n\n    public void checkEmptySpanIds() {\n        assertNull(EaseAgent.loggerMdc.get(BaggageFields.TRACE_ID.name()));\n        assertNull(EaseAgent.loggerMdc.get(BaggageFields.SPAN_ID.name()));\n\n        assertNull(org.slf4j.MDC.get(BaggageFields.TRACE_ID.name()));\n        assertNull(org.slf4j.MDC.get(BaggageFields.SPAN_ID.name()));\n    }\n\n    @Test\n    public void get() {\n        AgentLogMDC agentLogMDC = AgentLogMDC.create(Thread.currentThread().getContextClassLoader());\n        Span eSpan = SpanImpl.build(tracing, tracing.tracer().nextSpan(), injector);\n        checkEmptySpanIds(agentLogMDC);\n        Span eSpan2 = SpanImpl.build(tracing, tracing.tracer().nextSpan(), injector);\n        checkEmptySpanIds(agentLogMDC);\n        try (Scope scope = eSpan.maybeScope()) {\n            checkSpanIds(agentLogMDC, eSpan);\n            eSpan2 = SpanImpl.build(tracing, tracing.tracer().nextSpan(), injector);\n            try (Scope s = eSpan2.maybeScope()) {\n                checkSpanIds(agentLogMDC, eSpan2);\n            }\n            checkSpanIds(agentLogMDC, eSpan);\n        }\n        checkEmptySpanIds(agentLogMDC);\n    }\n\n    @Test\n    public void getV2() {\n        Span eSpan = SpanImpl.build(tracing, tracing.tracer().nextSpan(), injector);\n        checkEmptySpanIds();\n        Span eSpan2 = SpanImpl.build(tracing, tracing.tracer().nextSpan(), injector);\n        checkEmptySpanIds();\n        try (Scope scope = eSpan.maybeScope()) {\n            checkSpanIds(eSpan);\n            eSpan2 = SpanImpl.build(tracing, tracing.tracer().nextSpan(), injector);\n            try (Scope s = eSpan2.maybeScope()) {\n                checkSpanIds(eSpan2);\n            }\n            checkSpanIds(eSpan);\n        }\n        checkEmptySpanIds();\n\n    }\n}\n"
  },
  {
    "path": "zipkin/src/test/java/com/megaease/easeagent/zipkin/logging/LogUtilsTest.java",
    "content": "/*\n * Copyright (c) 2021, MegaEase\n * All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.megaease.easeagent.zipkin.logging;\n\nimport com.megaease.easeagent.log4j2.ClassLoaderUtils;\nimport com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor;\nimport org.junit.Test;\n\nimport java.io.Closeable;\nimport java.io.IOException;\nimport java.net.URL;\nimport java.net.URLClassLoader;\n\nimport static org.junit.Assert.assertNotNull;\nimport static org.junit.Assert.assertNull;\n\npublic class LogUtilsTest {\n\n    public static ClassLoader getClassLoader(String[] matches) {\n        URL[] urls = ClassLoaderUtils.getAllUrls(Thread.currentThread().getContextClassLoader(), url -> {\n            String path = url.getPath();\n            if (!path.endsWith(\".jar\")) {\n                return false;\n            }\n            for (String match : matches) {\n                if (path.indexOf(match) > 0) {\n                    return true;\n                }\n            }\n            return false;\n        });\n        return new URLClassLoader(urls, null);\n    }\n\n    private static void resetUtils() {\n        AgentFieldReflectAccessor.setStaticFieldValue(LogUtils.class, \"log4jLoaded\", null);\n        AgentFieldReflectAccessor.setStaticFieldValue(LogUtils.class, \"logbackLoaded\", null);\n        AgentFieldReflectAccessor.setStaticFieldValue(LogUtils.class, \"log4jMdcClass\", null);\n        AgentFieldReflectAccessor.setStaticFieldValue(LogUtils.class, \"logbackMdcClass\", null);\n    }\n\n    public static class Close implements Closeable {\n        public Close() {\n            resetUtils();\n        }\n\n        @Override\n        public void close() {\n            resetUtils();\n        }\n    }\n\n    public static Close reset() {\n        return new Close();\n    }\n\n    @Test\n    public void checkLog4JMDC() {\n        try (Close close = reset()) {\n            ClassLoader classLoader = getClassLoader(new String[]{\"log4j-slf4j-impl\", \"log4j-core\", \"log4j-api\"});\n            Class clzss = LogUtils.checkLog4JMDC(classLoader);\n            assertNotNull(clzss);\n            Class clzss2 = LogUtils.checkLogBackMDC(classLoader);\n            assertNull(clzss2);\n        }\n    }\n\n    @Test\n    public void checkLogBackMDC() {\n        try (Close close = reset()) {\n            ClassLoader classLoader = getClassLoader(new String[]{\"slf4j-api\", \"logback-core\", \"logback-access\", \"logback-classic\"});\n            Class clzss = LogUtils.checkLog4JMDC(classLoader);\n            assertNull(clzss);\n            Class clzss2 = LogUtils.checkLogBackMDC(classLoader);\n            assertNotNull(clzss2);\n        }\n\n    }\n\n    @Test\n    public void loadClass() {\n        Class clzss = LogUtils.loadClass(Thread.currentThread().getContextClassLoader(), \"org.apache.logging.log4j.ThreadContext\");\n        assertNotNull(clzss);\n        clzss = LogUtils.loadClass(Thread.currentThread().getContextClassLoader(), \"org.slf4j.MDC\");\n        assertNotNull(clzss);\n    }\n}\n"
  }
]