[
  {
    "path": ".git-blame-ignore-revs",
    "content": "# .git-blame-ignore-revs\n# Reformatting of code with Spotless\n969d12b2b997f23561af51530f9394e8ca34dfd7\n"
  },
  {
    "path": ".github/CODEOWNERS",
    "content": "* @jenkinsci/jira-plugin-developers\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: [ olamy, rantoniuk]\npatreon: # Replace with a single Patreon username\nopen_collective: # Replace with a single Open Collective username\nko_fi: # Replace with a single Ko-fi username\ntidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel\ncommunity_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry\nliberapay: # Replace with a single Liberapay username\nissuehunt: # Replace with a single IssueHunt username\notechie: # Replace with a single Otechie username\ncustom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/1-report-bug.yml",
    "content": "name: \"🐛 Bug report\"\ntype: \"Bug\"\ndescription: Create a bug report to help us improve\n\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        **Never report security issues on GitHub or other public channels (Gitter/Twitter/etc.)**\n        Follow these instruction to report security issues: https://www.jenkins.io/security/#reporting-vulnerabilities\n\n  - type: textarea\n    attributes:\n      label: Jenkins and plugins versions report\n      description: |\n        For easier bug reporting, you can get the full list of plugins with this Groovy script that you can run in **Jenkins > Manage Jenkins > Script Console**:\n        ```groovy\n        println(\"Jenkins: ${Jenkins.instance.getVersion()}\")\n        println(\"OS: ${System.getProperty('os.name')} - ${System.getProperty('os.version')}\")\n        println \"---\"\n\n        Jenkins.instance.pluginManager.plugins\n          .collect()\n          .sort { it.getShortName() }\n          .each {\n            plugin -> println(\"${plugin.getShortName()}:${plugin.getVersion()}\")\n          }\n        return\n        ```\n      placeholder: |\n        Jenkins: 2.326\n        OS: Linux - 3.10.0-1160.45.1.el7.x86_64\n        ---\n        ace-editor:1.1\n        ant:1.13\n        antisamy-markup-formatter:2.5\n        apache-httpcomponents-client-4-api:4.5.13-1.0\n        authentication-tokens:1.4\n        bootstrap4-api:4.6.0-3\n        bootstrap5-api:5.1.3-4\n        bouncycastle-api:2.25\n        ...\n      value: |\n        <details>\n          <summary>Environment</summary>\n\n          <!-- Paste your environment details below -->\n          ```text\n          Paste the output here\n          ```\n\n        </details>\n    validations:\n      required: true\n\n  - type: textarea\n    attributes:\n      label: What Operating System are you using (both controller, and any agents involved in the problem)?\n    validations:\n      required: true\n\n  - type: textarea\n    attributes:\n      label: Reproduction steps\n      description: |\n        Write bullet-point reproduction steps.\n        Be explicit about any relevant configuration, jobs, build history, user accounts, etc., redacting confidential information as needed.\n        The best reproduction steps start with a clean Jenkins install, perhaps a `docker run` command if possible.\n        Use screenshots where appropriate, copy textual output otherwise. When in doubt, do both.\n        Include relevant logs, debug if needed - https://www.jenkins.io/doc/book/system-administration/viewing-logs/\n      placeholder: |\n        1. Step 1: ...\n        2. Step 2: ...\n    validations:\n      required: true\n\n  - type: textarea\n    attributes:\n      label: Expected Results\n      description: What was your expected result?\n    validations:\n      required: true\n\n  - type: textarea\n    attributes:\n      label: Actual Results\n      description: What was the actual result?\n    validations:\n      required: true\n\n  - type: textarea\n    attributes:\n      label: Anything else?\n      description: You can provide additional context below.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/2-feature-request.yml",
    "content": "name: \"🚀 Feature request\"\ntype: \"feature\"\ndescription: I have a suggestion\n\nbody:\n  - type: textarea\n    attributes:\n      label: What feature do you want to see added?\n      description: A clear and concise description of your feature request.\n    validations:\n      required: true\n\n  - type: textarea\n    attributes:\n      label: Upstream changes\n      description: Link here any upstream changes that might be relevant to this request\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/3-documentation.yml",
    "content": "name: \"📝 Documentation\"\nlabels: [\"documentation\"]\ndescription: \"Let us know if any documentation is missing or could be improved\"\n\nbody:\n  - type: textarea\n    attributes:\n      label: Describe your use-case which is not covered by existing documentation.\n      description: If it is easier to submit a documentation patch instead of writing an issue, just do it!\n    validations:\n      required: true\n\n  - type: textarea\n    attributes:\n      label: Reference any relevant documentation, other materials or issues/pull requests that can be used for inspiration.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: true\ncontact_links:\n  - name: Community forum\n    url: https://community.jenkins.io/\n    about: Please ask and answer questions here\n  - name: Mailing lists\n    url: https://www.jenkins.io/mailing-lists/\n    about: You can also raise a question in one of the user or developer mailing lists\n"
  },
  {
    "path": ".github/pull_request_template.md",
    "content": "<!--\nPut an `x` into the [ ] to show you have filled the information\n-->\n\n### Related issue\n\n<!-- Paste links to the issues that are related/resolved by this PR -->\n<!-- If this PR is related to upstream libraries/API changes, include those links as well -->\n\n### Changes\n\n<!-- Describe changes: include any relevant information. If you have renamed any files or classes, state the reason to help the reviewer understand the context. -->\n\n### Tests\n\n<!-- Describe how did you test your changes. If you don't have a Jira Cloud instance to test on, ask the maintainer to grant access to https://jenkins-jira-plugin.atlassian.net/ --> \n\n- [ ] I have updated/added relevant documentation in the `docs/` directory\n- [ ] I have verified that the Code Coverage is not lower than before / that all the changes are covered as needed\n- [ ] I have tested my changes with a Jira Cloud / Jira Server\n\n<!-- provide here the flavor (Jira Cloud / Jira Server) and the version -->\n "
  },
  {
    "path": ".github/release-drafter.yml",
    "content": "_extends: github:jenkinsci/.github:/.github/release-drafter.yml\nname-template: $RESOLVED_VERSION\ntag-template: jira-$RESOLVED_VERSION\n\nversion-resolver:\n  major:\n    labels:\n      - 'major'\n      - 'breaking'\n  minor:\n    labels:\n      - 'minor'\n      - 'dependencies'\n  patch:\n    labels:\n      - 'bugfix'\n      - 'patch'\n  default: minor\n"
  },
  {
    "path": ".github/renovate.json",
    "content": "{\n  \"$schema\": \"https://docs.renovatebot.com/renovate-schema.json\",\n  \"extends\": [\n    \"github>jenkinsci/renovate-config\"\n  ],\n  \"packageRules\": [\n    {\n      \"matchManagers\": [\"maven\"],\n      \"matchPackageNames\": [\n        \"io.atlassian.fugue:fugue\"\n      ],\n      \"enabled\": false\n    },\n    {\n      \"matchManagers\": [\"maven\"],\n      \"groupName\": \"atlassian\",\n      \"matchPackageNames\": [\n        \"/^com\\.atlassian\\.plugins:.*$/\",\n        \"/^com\\.atlassian\\.jira:.*$/\",\n        \"/^io\\.atlassian\\.util\\.concurrent:.*$/\"\n      ]\n    },\n    {\n      \"matchManagers\": [\"maven\"],\n      \"groupName\": \"jenkins\",\n      \"matchPackageNames\": [\n        \"/^io\\.jenkins\\.tools\\.bom:.*$/\",\n        \"/^org\\.jenkins-ci\\.plugins:plugin$/\"\n      ]\n    }\n  ]\n}\n"
  },
  {
    "path": ".github/workflows/crowdin.yml",
    "content": "---\nname: Crowdin\n\non:\n  push:\n    branches:\n      - master\n  workflow_dispatch:\n\npermissions:\n  actions: write\n  contents: write\n  pull-requests: write\n\njobs:\n  synchronize-with-crowdin:\n    runs-on: ubuntu-latest\n\n    steps:\n\n      - name: Checkout\n        uses: actions/checkout@v6\n\n      - name: crowdin action\n        uses: crowdin/github-action@v2\n        with:\n          upload_translations: true\n          download_translations: true\n          skip_untranslated_files: true\n          auto_approve_imported: true\n          push_translations: true\n          export_only_approved: true\n          commit_message: 'New Crowdin translations'\n          create_pull_request: true\n          pull_request_title: 'Update localization'\n          pull_request_labels: 'localization'\n          base_url: 'https://jenkins.crowdin.com'\n          config: 'crowdin.yml'\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          CROWDIN_PROJECT_ID:  35\n          CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/jenkins-security-scan.yml",
    "content": "name: Jenkins Security Scan\n\non:\n  push:\n    branches:\n      - master\n  pull_request:\n    types: [ opened, synchronize, reopened ]\n  workflow_dispatch:\n\npermissions:\n  security-events: write\n  contents: read\n  actions: read\n\njobs:\n  security-scan:\n    uses: jenkins-infra/jenkins-security-scan/.github/workflows/jenkins-security-scan.yaml@v2\n    with:\n      java-cache: 'maven' # Optionally enable use of a build dependency cache. Specify 'maven' or 'gradle' as appropriate.\n      # java-version: 21 # Optionally specify what version of Java to set up for the build, or remove to use a recent default.\n"
  },
  {
    "path": ".github/workflows/release-drafter.yml",
    "content": "name: Release Drafter\non:\n  push:\n    branches:\n      - master\njobs:\n  update_release_draft:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: release-drafter/release-drafter@v7\n        with:\n          token: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/sonarcloud.yml",
    "content": "name: SonarCloud analysis\n\non:\n  push:\n    branches: [ \"master\" ]\n    tags:\n      - 'jira*'\n  pull_request:\n    branches: [ \"master\" ]\n  workflow_dispatch:\n\npermissions:\n  pull-requests: read \n\njobs:\n  Analysis:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout Code\n        uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n          persist-credentials: false\n\n      - uses: actions/setup-java@v5\n        with:\n          java-version: '21'\n          distribution: 'corretto'\n          cache: 'maven'\n\n      - name: Run the Maven verify phase\n        env:\n          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}\n        run: >\n           mvn --batch-mode -Penable-jacoco\n           -Dsonar.organization=jenkinsci\n           -Dsonar.projectKey=jenkinsci_jira-plugin\n           -Dsonar.qualitygate.wait\n           test jacoco:report org.sonarsource.scanner.maven:sonar-maven-plugin:5.5.0.6356:sonar\n\n"
  },
  {
    "path": ".gitignore",
    "content": ".classpath\n.project\n.settings\n.factorypath\nbuild\ntarget\nbin\nwork/\n.work\n.env*\n.docker\n*.code-workspace\n\n# ignore all Idea files except for common codestyle settings\n.DS_Store\n*.iml\n.vscode/\n.idea/*\n\natlassian-ide-plugin.xml\n"
  },
  {
    "path": ".mvn/extensions.xml",
    "content": "<extensions xmlns=\"http://maven.apache.org/EXTENSIONS/1.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/EXTENSIONS/1.0.0 http://maven.apache.org/xsd/core-extensions-1.0.0.xsd\">\n  <extension>\n    <groupId>io.jenkins.tools.incrementals</groupId>\n    <artifactId>git-changelist-maven-extension</artifactId>\n    <version>1.13</version>\n  </extension>\n</extensions>\n"
  },
  {
    "path": ".mvn/maven.config",
    "content": "-Pconsume-incrementals\n-Pmight-produce-incrementals\n"
  },
  {
    "path": ".pre-commit-config.yaml",
    "content": "repos:\n  - repo: https://github.com/pre-commit/pre-commit-hooks\n    rev: v4.6.0\n    hooks:\n      - id: check-yaml\n      - id: check-added-large-files\n  - repo: https://github.com/ORCID/pre-commit-spotless\n    rev: v1.0.0\n    hooks:\n      - id: spotless-check\n        types_or: [java]\n      - id: spotless-apply\n        types_or: [java]\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contribution guidelines\n\nGeneral rules:\n\n- check the [general Jenkins development guide](https://www.jenkins.io/doc/developer/book/)\n- make sure to provide tests\n- when adding new fields, make sure to [include backward-compatibility](https://www.jenkins.io/doc/developer/persistence/backward-compatibility/) and tests for that\n- mark the Pull Request as _draft_ initially, to make sure all the checks pass correctly, then convert it to non-draft.\n\n## Setting up your environment\n\n### Install pre-commit hooks\n\nThe [pre-commit](https://pre-commit.com/#install) hooks run various checks to make sure no unwanted files are committed and that the submitted change follows the code style and formatting rules:\n\n```sh\nbrew install pre-commit && pre-commit install --install-hooks\n```\n\n## Notes for maintainers\n\n### Local testing\n\nUse [docker-compose](./docker-compose.yml) to run a local Jenkins instance with the plugin installed. The configuration includes local volumes for both: Jenkins and ssh-agent, so you can easily test the plugin in a clean environment.\n\n\n### Atlassian sources import\n\nTo resolve [some binary compatibility issues](https://github.com/jenkinsci/jira-plugin/pull/140),\nthe sources from the artifact [com.atlassian.httpclient:atlassian-httpclient-plugin:0.23](https://packages.atlassian.com/maven-external/com/atlassian/httpclient/atlassian-httpclient-plugin/0.23.0/)\nhas been imported in the project to have control over http(s) protocol transport layer.\nThe downloaded sources didn't have any license headers but based on the [pom](https://packages.atlassian.com/maven-external/com/atlassian/httpclient/atlassian-httpclient-plugin/0.23.0/atlassian-httpclient-plugin-0.23.0.pom)\nsources are Apache License (see pom in src/main/resources/atlassian-httpclient-plugin-0.23.0.pom)\n\n### Testing\n\nThere is a [Jira Cloud](https://jenkins-jira-plugin.atlassian.net/) test instance that the changes can be tested against, official maintainers are admins that can grant access for testing to PR submitters on a need-to-have basis.\n\n### Releasing the plugin\n\nSee [releasing Jenkins plugins](https://www.jenkins.io/doc/developer/publishing/releasing-manually/).\n"
  },
  {
    "path": "Jenkinsfile",
    "content": "/*\n See the documentation for more options:\n https://github.com/jenkins-infra/pipeline-library/\n*/\nbuildPlugin(\n  forkCount: '1C', // run this number of tests in parallel for faster feedback.  If the number terminates with a 'C', the value will be multiplied by the number of available CPU cores\n  useContainerAgent: true, // Set to `false` if you need to use Docker for containerized tests\n  configurations: [\n    [platform: 'linux', jdk: 25],\n    [platform: 'windows', jdk: 21],\n])\n"
  },
  {
    "path": "LICENSE.md",
    "content": "The MIT License\n\nCopyright (c) 2007, Kohsuke Kawaguchi and jira-plugin contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n"
  },
  {
    "path": "README.md",
    "content": "# Jenkins Jira Plugin\n\n[![Jenkins Version](https://img.shields.io/badge/Jenkins-2.479.3-green.svg?label=min.%20Jenkins)](https://jenkins.io/download/)\n[![Jenkins Plugin Installs](https://img.shields.io/jenkins/plugin/i/jira.svg?color=blue)](https://stats.jenkins.io/pluginversions/jira.html)\n[![GitHub release](https://img.shields.io/github/release/jenkinsci/jira-plugin.svg?label=Release)](https://github.com/jenkinsci/jira-plugin/releases/latest)\n[![Jenkins CI](https://ci.jenkins.io/buildStatus/icon?job=Plugins/jira-plugin/master)](https://ci.jenkins.io/job/Plugins/job/jira-plugin/)\n[![Contributors](https://img.shields.io/github/contributors/jenkinsci/jira-plugin.svg)](https://github.com/jenkinsci/jira-plugin/graphs/contributors)\n[![Bugs](https://sonarcloud.io/api/project_badges/measure?project=jenkinsci_jira-plugin&metric=bugs)](https://sonarcloud.io/summary/new_code?id=jenkinsci_jira-plugin)\n[![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=jenkinsci_jira-plugin&metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=jenkinsci_jira-plugin)\n[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=jenkinsci_jira-plugin&metric=coverage)](https://sonarcloud.io/summary/new_code?id=jenkinsci_jira-plugin)\n[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=jenkinsci_jira-plugin&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=jenkinsci_jira-plugin)\n\n1. See user documentation at [https://jenkinsci.github.io/jira-plugin/](https://jenkinsci.github.io/jira-plugin/).\n1. Use [Declarative pipelines](https://www.jenkins.io/doc/book/pipeline/#declarative-versus-scripted-pipeline-syntax).\n1. Check [jira plugin steps reference](https://www.jenkins.io/doc/pipeline/steps/jira/).\n\n## i18n\n\n\n[![da translation](https://img.shields.io/badge/dynamic/json?color=blue&label=da&style=flat&logo=crowdin&query=%24.progress.0.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-200016380-35.json)](https://jenkins.crowdin.com/jira-plugin)\n[![de translation](https://img.shields.io/badge/dynamic/json?color=blue&label=de&style=flat&logo=crowdin&query=%24.progress.1.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-200016380-35.json)](https://jenkins.crowdin.com/jira-plugin)\n[![en translation](https://img.shields.io/badge/dynamic/json?color=blue&label=en&style=flat&logo=crowdin&query=%24.progress.2.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-200016380-35.json)](https://jenkins.crowdin.com/jira-plugin)\n[![es translation](https://img.shields.io/badge/dynamic/json?color=blue&label=es&style=flat&logo=crowdin&query=%24.progress.3.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-200016380-35.json)](https://jenkins.crowdin.com/jira-plugin)\n[![fr translation](https://img.shields.io/badge/dynamic/json?color=blue&label=fr&style=flat&logo=crowdin&query=%24.progress.4.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-200016380-35.json)](https://jenkins.crowdin.com/jira-plugin)\n[![it translation](https://img.shields.io/badge/dynamic/json?color=blue&label=it&style=flat&logo=crowdin&query=%24.progress.5.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-200016380-35.json)](https://jenkins.crowdin.com/jira-plugin)\n[![ja translation](https://img.shields.io/badge/dynamic/json?color=blue&label=ja&style=flat&logo=crowdin&query=%24.progress.6.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-200016380-35.json)](https://jenkins.crowdin.com/jira-plugin)\n[![pl translation](https://img.shields.io/badge/dynamic/json?color=blue&label=pl&style=flat&logo=crowdin&query=%24.progress.7.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-200016380-35.json)](https://jenkins.crowdin.com/jira-plugin)\n\nThis plugin uses [CrowdIn platform](https://jenkins.crowdin.com/jira-plugin) as the frontend to manage translations. If you would like to contribute translation of this plugin in your language,  you're most welcome! For details, see [jenkins.io CrowdIn introduction](https://www.jenkins.io/doc/developer/crowdin/translating-plugins/).\n\n"
  },
  {
    "path": "crowdin.yml",
    "content": "---\n\nfiles:\n  - source: '/src/main/resources/hudson/plugins/jira/**/*.properties'\n    ignore:\n      - '/src/main/resources/hudson/plugins/jira/**/%file_name%_%two_letters_code%.properties'\n    translation: '/src/main/resources/hudson/plugins/jira/**/%file_name%_%two_letters_code%.properties'\n    escape_quotes: 0\n    escape_special_characters: 0\n\nproject_id_env: CROWDIN_PROJECT_ID\napi_token_env: CROWDIN_PERSONAL_TOKEN\n"
  },
  {
    "path": "docker-compose.yml",
    "content": "#  docker compose up -d --build --force-recreate\nservices:\n    jenkins:\n        image: jenkins/jenkins:lts\n        restart: on-failure\n        hostname: jenkins\n        ports:\n            - \"8080:8080\"\n            - \"50000:50000\"\n        volumes:\n            - .docker/jenkins-data:/var/jenkins_home:rw\n            - ./casc.d:/var/jenkins_home/casc.d/:ro\n            # Mounting the ssh private key as \"container secret\" makes it available in JCasc as the variable ${SSH_AGENT_KEY}\n            - ./secrets/id_jenkins.pem:/run/secrets/SSH_AGENT_KEY:ro\n        environment:\n            - JENKINS_EXT_URL=http://localhost:8080\n            - CASC_JENKINS_CONFIG=/var/jenkins_home/casc.d/\n            # - org.jenkinsci.plugins.durabletask.BourneShellScript.LAUNCH_DIAGNOSTICS=true\n            # - JENKINS_OPTS=-Djenkins.install.runSetupWizard=false\n    jenkins-agent:\n        image: jenkins/ssh-agent:latest-jdk21\n        restart: on-failure\n        hostname: agent\n        #privileged: true\n        depends_on: \n            - jenkins\n        volumes:\n            - .docker/jenkins-agent-data:/home/jenkins:rw\n            # - /var/run/docker.sock:/var/run/docker.sock:rw\n        environment:\n            - JENKINS_AGENT_SSH_PUBKEY=ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBpNqXQ4x7fPPUBbYPxKF77Zqq6d35iPCD2chg644OUD noreply@jenkinsagent.local\n\nvolumes:\n    jenkins-data:\n    jenkins-agent-data:\n"
  },
  {
    "path": "docs/.nojekyll",
    "content": ""
  },
  {
    "path": "docs/README.md",
    "content": "# Jenkins Jira plugin\n\nThis plugin integrates with Jenkins the [Atlassian Jira Software](http://www.atlassian.com/software/jira/) (both Cloud and Server versions). \n\n## Configuration\n\n!> **Jira Cloud** does not support Bearer Authentication\n\nTo integrate Jenkins with Atlassian Jira Cloud, you need to use an API token as a _service user_. Jira Cloud requires an email address for all users, so you cannot create a user without one.\n\n### Steps\n\n1. **Create an API Token**\n\n    Follow the [Atlassian API tokens documentation](https://confluence.atlassian.com/cloud/api-tokens-938839638.html) to generate a new API token.\n\n2. **Add a Global Jenkins Credential**\n\n    - **Username:** Your Atlassian ID email address\n    - **Password:** The API token you created\n\n3. **Test Your API Token**\n\n    Verify your API token by running the following command (replace `<email>`, `<API token>`, `<YourCloudInstanceName>`, and `TEST-1` with your details):\n\n    ```bash\n    curl -X GET -u <email>:<API token> -H \"Content-Type: application/json\" \\\n      https://<YourCloudInstanceName>.atlassian.net/rest/api/latest/issue/TEST-1\n    ```\n\n    A successful response returns the issue details in JSON format.\n\n4. **Check for CAPTCHA**\n\n    Ensure that CAPTCHA is **not** triggered for your user, as this will prevent the API token from working. For more information, see the [CAPTCHA section in Atlassian REST API documentation](https://developer.atlassian.com/cloud/jira/platform/jira-rest-api-basic-authentication/).\n\n5. **Test Connection**\n    \n    Finally, use the **Validate Settings** button on the plugin configuration page, to see if it can connect to the Jira instance.\n\n![plugin-configuration](images/Plugin_Configuration.png)\n\n\n## Something doesn't work?\n\nFirst, check [Github Issues](https://github.com/jenkinsci/jira-plugin/issues) for already reported bugs.\n\nThen, Contribute or Sponsor!\n\nWe all love Open Source, but... Open Source Software relies on contributions of fellow developers. Please contribute by [opening Pull Requests](#contributing) or if you are not a developer, consider sponsoring one of the maintainers - see [\"Sponsor this project\" section.](https://github.com/jenkinsci/jira-plugin)"
  },
  {
    "path": "docs/_sidebar.md",
    "content": "* [Home](/)\n* [Features](features.md)\n* [System Properties](system-properties.md)\n* [Usage Examples](usage-examples.md)\n* [Troubleshooting](troubleshooting.md)\n* [Changelog](changelog.md)\n"
  },
  {
    "path": "docs/changelog.md",
    "content": "Changelog\n===\n\n### Newer versions\n\nSee [GitHub releases](https://github.com/jenkinsci/jira-plugin/releases)\n\n### Unreleased\n\nRelease date:  _March 2, 2020_\n\n* Changed all references of JIRA to Jira per [Atlassian branding updates](https://community.atlassian.com/t5/Feedback-Forum-articles/A-new-look-for-Atlassian/ba-p/638077)\n* JiraCreateIssueNotifier: regard all statuses in \"done\" category as finished\n\n### 3.0.11\n\nRelease date:  _Nov 21, 2019_\n\n* Fix security issue\n\n### 3.0.10\n\nRelease date:  _Sep 26, 2019_\n\n* dependencies cleanup (remove dependency on org.codehaus.jackson:*)\n\n### 3.0.9\n\nRelease date:  _Aug 21, 2019_\n\n* JENKINS-59001 IssueSelectorStep should handle NullPointerException caused by having no configured sites\n* JENKINS-57899 Jira site configuration lost after a restart\n\n### 3.0.8\n\nRelease date:  _Jun 28, 2019_\n\n* JENKINS-58244 JIRA Site at folder level doesn't show credentials for non-admin users\n* JENKINS-57664 On Release of a Version recieve a Null Pointer exception\n\n### 3.0.7\n\nRelease date:  _May 1, 2019_\n\n* JENKINS-56951 JIRA plugin config incorrectly mixed with properties section\n* JENKINS-52906 jira-plugin: FAILED TO EXPORT hudson.plugins.jira.JiraProjectProperty\n* JENKINS-33222 Concurrent builds are blocking with jira-plugin\n* JENKINS-19195 Add description field to Release JIRA version action\n\n### 3.0.6\n\nRelease date:  _March 30, 2019_\n\n* JENKINS-56810 Add current date in startDate when creating new version \n* JENKINS-56697 Simplify constructor, main URL is the only mandatory field \n* JENKINS-50643 Jira Plugin v2.4.2 leaks selectors resulting in Too Many Open Files\n\n## 3.0.5\n\nRelease date:  _Jul 7, 2018_\n\n* JENKINS-54469 Unable to set Thread Executor Size when configuring Jira Plugin \n* JENKINS-54131 \"Failed to parse changelog\" in JIRA plugin 3.0.3 \n* JENKINS-54116 Jenkins Jira Plugin - Unable to add version\n \n### 3.0.4\n\nRelease date:  _Oct 26, 2018_\n\n* JENKINS-54144 Job fails as JiraSCMListsener/JiraSite potentially creating Executor with 0 threads\n\n### 3.0.3\n\nRelease date:  _Oct 16, 2018_\n\n* JENKINS-54042 Fix some misconfiguration between connect time and read timeout\n* JENKINS-53808 Binary compatibility broken between JIRA plugin 3.0.2 and Artifactory plugin 2.16.2\n* JENKINS-53642 Configuration for \"Add timestamp to JIRA comments\" not observed/remembered\n\n### 3.0.2\n\nRelease date:  _Sep 25, 2018_\n\n* SECURITY-1029 - CSRF vulnerability and missing permission checks in Jira Plugin allowed capturing credentials\n\n### 3.0.1\n\nRelease date:  _Aug 22, 2018_\n\n* JENKINS-54093 Jenkins Jira Plugin sets connection timeout to default = 10 after Jenkins restart\n* JENKINS-53150 Remove Perforce Plugin dependency\n* JENKINS-51164 JIRA plugin doesn't honor proxy excludes\n* JENKINS-45789 Use Credentials Plugin for JIRA Global Configuration User\n\n### 3.0.0\n\nRelease date:  _May 20, 2018_\n\n* JENKINS-51312 Jira plugin core 2.60.1\n* JENKINS-51310 Update JIRA plugin to use jackson2-api-plugin\n\n### 2.5.2\n\nRelease date:  _May 4, 2018_\n\n* JENKINS-49975 BasicHttpCache error with JIRA Plugin 2.5.1\n* JENKINS-49231 jira-plugin 2.5.1 throws exception fails build\n* JENKINS-48357 Binary Compatibility between JIRA Plugin and Apache HttpComponents Client 4.x API\n* JENKINS-25829 Proxy configuration does not work\n\n### 2.4.2\n\nRelease date:  _Aug 8, 2017_\n\n* JENKINS-45992 Cannot validate JIRA site settings\n\n### Version 2.4\n\nRelease date:  _Aug 3, 2017_\n\n* JENKINS-44524 Support adding JIRA sites on folder\n\n### 2.3.1\n\nRelease date:  _May 23, 2017_\n\n* JENKINS-40571 JiraVersionCreatorBuilder fields missing in post-build form\n\n### 2.3\n\nRelease date:  _Dec 19, 2016_\n\n* JENKINS-39192 Support for updating custom fields \n* JENKINS-39091 Jira plugin fails to log in to Jira site after dependency updates \n* JENKINS-38142 Possibility to specify JIRA site in jenkins pipeline project \n* JENKINS-36726 Plugin comments on subsequent successful builds \n* JENKINS-35998 No issue update if at least one issue does not exists \n* JENKINS-34661 Bump LTS to 1.642.3 \n* JENKINS-33996 Issue selector step improvement \n* JENKINS-33859 NPE when well-formed Jira Issue doesn't exist \n* JENKINS-32602 jira-plugin missing License \n* JENKINS-32492 Rename all dropdown labels to include JIRA: prefix \n* JENKINS-32491 Rename Workflow Plugin to Pipeline \n* JENKINS-31164 Issue Type must be selectable from fetched dropdown \n* JENKINS-24207 Use Perforce Jobs attached to changelist to track JIRA issues \n* JENKINS-19286 Support multiple fix versions\n\n### 2.2.1\n\nRelease date:  _Mar 26, 2016_\n\n* JENKINS-33293 (Jira) Updater throws NullPointerException for labels\n* JENKINS-33211 NullPointerException in JiraVersionParameterValue.java\n\n### 2.2\n\nRelease date:  _Feb 20, 2016_\n\n* Split each SCM changes in paragraphs\n* support release candidates (RCs) via Maven ComparableVersion\n* Console logging improvements in various places\n* JiraEnvironmentVariableBuilder support\n* Support adding labels to updated issues\n* (optionally) add scm entry change date and time to description in JIRA tickets\n* JENKINS-32504 Make JiraEnvironmentVariableBuilder compatible with pipeline\n* JENKINS-32276 JIRA Release Version Parameter is truncating list of Jira versions\n* JENKINS-32170 Support CLI parameter submission for JiraIssueParameterDefinition\n* JENKINS-32106 Issue type \"UNKNOWN\" in Release Notes\n* JENKINS-31268 @Exported returns double XML value for getter\n* JENKINS-31113 Configurable HTTP timeout parameter for JiraRestService\n\n### 2.1\n\nRelease date:  _Now 18, 2015_\n\n* Bumped Jenkins Core to LTS v. 1.609.3\n* Added dependencies: mailer-plugin, matrix-plugin\n* Removed dependencies: maven-plugin\n* JENKINS-32949 Issue with JIRA plugins in JENKINS \n* JENKINS-31626 Expand JIRA Project Key variable in other build tasks \n* JENKINS-31349 Jira configuration - Validate Settings doesn't validate username/password \n* JENKINS-30829 JIRA Generate Release Notes needs default Environment Variable \n* JENKINS-30305 Allow CLI parameter submission for JiraVersionParameterDefinition \n* JENKINS-26701 Jira Plugin: I need sorting option in \"JIRA Release Version Parameter\". Is it possible? \n* JENKINS-25828 JIRA version name/value not picked up by remote API \n* JENKINS-17156 If Updater fails to update due to missing permission, it crashes and never flushes the comment queue \n* JENKINS-13436 Message logged by JIRA plugin should mention that the message relates to a Jenkins job. \n* JENKINS-12578 Jira issue parameter value is not exposed via remote API \n* JENKINS-3709 Jira login information should be scrambled in the configuration file\n\n### 2.0.3\n\nRelease date:  _Oct 26, 2015_\n\n* JENKINS-30682 Ticket creation fails when no components in JIRA project exist / JIRA rejects empty component list\n* JENKINS-30408 JIRA REST API requests lead to 404 (not found) \n* JENKINS-30333 Thread Leak due to use of deprecated JiraSize.createSession\n\n### 2.0.1\n\nRelease date:  _Sep 10, 2015_\n\n* JENKINS-30242 Update to 1.13.2 breaks global configuration submission when jira-plugin installed\n\n### 2.0\n\nRelease date:  _Sep 2, 2015_\n\n* switch from JIRA RPC SOAP to JIRA REST API communication - the former has been deprecated and dropped since JIRA v.7.0.\n* JENKINS-23257 Non well-formed response from JIRA error is hard to diagnose \n* JENKINS-18227 Add support for Atlassian OnDemand JIRA \n* JENKINS-18166 Add support for JIRA REST API - JIRA SOAP API will be removed in JIRA 7 \n* JENKINS-10223 This is a valid URL but it doesn't look like JIRA\n\n### 1.41\n\nRelease date:  _Jun 10, 2015_\n\n* JENKINS-22628 Change comments/description to use String.format() instead. \n* JENKINS-21776 Jenkins jira comment text typo \n* JENKINS-20528 Unable to link to Jira \n* JENKINS-9549 JIRA plugin does not create links to JIRA repository \n* JENKINS-1904 Can't get issue links generated with out user/password\n\n### Version 1.39\n\nRelease date:  _Oct 6, 2013_\n\n* Ability only to comment issue without processing of workflow (pull #38)\n\n### 1.38\n\nRelease date:  _Aug 23, 2013_\n\n* Post build step to create new JIRA version (pull #30)\n\n### 1.37\n\nRelease date:  _Jun 21, 2013_\n\n* Error with empty alternative url issue #18229\n\n### 1.35\n\nRelease date:  _Jul 29, 2012_\n\n* Prevents multiple comments on one issue for matrix builds. (PR #13)\n\n### 1.34\n\nRelease date:  _Jun 11, 2012_\n\n* Fix NPE when Jenkins user does not have access to perform any workflow actions JENKINS-13998\n\n### 1.33\n\nRelease date:  _Jun 1, 2012_\n\n* Support workflow steps as build actions and/or post-build notifiers JENKINS-13652\n\n### 1.32\n\nRelease date:  _May 15, 2012_\n\n* Option to show archived versions.\n\n### 1.31\n\nRelease date:  _May 1, 2012_\n\n* Add JiraIssueMigrator - a post build action that will move issues to a new fixVersion based on a JQL query.\n* Add Additional filtering of issues to be included in the release notes. Defaults to 'status in (Resolved, Closed)'\n\n### 1.30\n\nRelease date:  _April 25, 2012_\n\n* Add build parameter that providers a drop-down with JIRA release versions\n* Add a build wrapper that will assemble release notes based on issues in the release version and store it in an environment variable\n* JENKINS-123 Issue summary\n* JENKINS-124 Another Issue summary\n* JENKINS-321 Yet another issue summary\n* Add a post-build action that will mark a version as released in JIRA\n\n### 1.29\n\nRelease date:  _August 25, 2011_\n\n* JENKINS-10817 Jira-plugin should add the overall build result to the issue's comment\n* Include revisions also for non-subversion plugins; include revisions also if we don't have a repository browser\n* Defined a new parameter type for parameterized builds that allow you to select a JIRA ticket (from the result of a JQL query)\n\n### 1.28\n\nRelease date:  _Jun 15, 2011_\n\n* Improve the form validation error check JENKINS-9625\n* Supported security level of the comment JENKINS-1489\n\n### 1.27\n\nRelease date:  _Feb 27, 2011_\n\n* Updates for Jenkins\n\n### 1.26\n\nRelease date:  _Jan 14, 2011_\n\n* JENKINS-2508 : JIRA plugin not updating JIRA when perforce plugin used.\n\n### 1.25\n\n* JENKINS-6758: Failed to save system settings with JIRA Plugin.\n\n### 1.24\n\n* JENKINS-6462: Version 1.355 of Hudson and Jira Plugin 1.21: Images in Jira comments are not showing up.\n\n### Version 1.23\n\n* JENKINS-6264, JENKINS-6282: IndexOutOfBoundsException when no issue pattern is configured (default pattern wasn't used)\n* JENKINS-6381: configured patterned wasn't used for changelog annotation. Default pattern was always used for that.\n* improved default pattern to not match commit messages with dots in the number part (like 'projectname-1.2.3'). These messages are e.g. used by the Maven release plugin\n\n### Version 1.22\n\n* JENKINS-6043: Issue pattern can be configurable\n* JENKINS-6225: option to update jira issue whatever the build result is (even if failed)\n\n### 1.21\n\n* JENKINS-5989: option to record scm changes in jira.\n* JENKINS-6007: Added French localization.\n\n### 1.20\n\n* Added Japanese localization (JENKINS-5788)\n\n### 1.19\n\n* Fix: Prevent carrying forward invalid issue ids forever\n\n### 1.18\n\n* Case insensitive matching of JIRA ids also in the 'recent changes' view (JENKINS-4132)\n* fetch missing details for JIRA issues - i.e. completes issue title tooltip in 'recent changes' view (JENKINS-5252)\n* prevent build FAILURE if JIRA site is not available (JENKINS-3046)\n\n### 1.17\n\n* Fixed an ArrayIndexOutOfBoundsException when JIRA issues contain '$' in the name.\n* Support underscore in project names (JENKINS-4092)\n* Support digits in project names (JENKINS-729)\n* Case insensitive matching of JIRA ids (JENKINS-4132)\n* Don't strip JIRA id from posted comment\n* German translation\n\n### 1.15\n\nRelease date:  _Aug 22, 2008_\n\n* Update JIRA if the build is UNSTABLE or better.  Previously only updated if the build was stable.\n* Include relevant SCM comment in the JIRA comment which should make JIRA ticket history more meaningful.\n\n### 1.13\n\nRelease date:  _Auh 5, 2008_\n\n* Fixed a performance issue in a large enterprise deployment of JIRA (JENKINS-1703)\n\n### Version 1.12\n\n* A typo in the commit message shouldn't break builds (JENKINS-1593)\n* Postpone JIRA updates until a successful build is obtained (JENKINS-506)\n\n### Version 1.11\n\n* Added more logging and debug flag to examine issues that people are reporting (report)\n\n### Version 1.10\n\n* Wiki-style notation option wasn't persisted (JENKINS-977)\n* Fixed a packaging problem (JENKINS-1127)\n\n### 1.9\n\n* Fixed NPE when failed to talk to JIRA (JENKINS-1097)\n\n### 1.8\n\n* Be more graceful in dealing with URLs (JENKINS-896)\n* URLs need to be escaped (JENKINS-943)\n\n### 1.7\n\n* Fixed NPE when username/password is not set (JENKINS-828)\n\n### 1.6\n\n* Relaxed the JIRA project key regexp a little bit to allow numbers (JENKINS-729)\n\n### 1.5\n\n* Issue hyperlinking is now smart enough not to be confused by strings that look like JIRA issue that actually aren't.\n\n### 1.4\n\n* Fixed a bug that prevented tooltips for JIRA issues from being displayed JENKINS-694\n"
  },
  {
    "path": "docs/features.md",
    "content": "# Jenkins Jira plugin features\n\n### Using Jira REST API\n\nThis plugin has an optional feature to update Jira issues with a back pointer to Jenkins build pages. This allows the submitter and watchers to quickly find out which build they need to pick up to get the fix.\n\n![plugin-configuration](images/Plugin_Configuration.png)\n\n### Jira Issue links in build Changelog\n\nWhen you configure your Jira site in Jenkins, the plugin will automatically hyperlink all matching issue names to Jira.\n\nIf you have additionally provided username/password to Jira, the hyperlinks will also contain tooltips with the issue summary.\n\n![example-annotated-changelog](images/example_annotated_changelog.png)\n\n### Updating Jira issues with back pointers\n\nIf you also want to use this feature, you need to supply a valid user id/password. If you need the comment only to be visible to a certain Jira group, e.g. _Software Development_, enter the groupname.\n\nNow you also need to configure jobs. I figured you might not always have write access to the Jira (say you have a Jenkins build for one of the Apache commons project that you depend on), so that's why this is optional.  \n\nThe following screen shows how a Jira issue is updated:\n\n![jira-comments](images/Jira_Comments.jpg)\n\nBy taking advantages of Jenkins' [fingerprint](https://wiki.jenkins.io/display/JENKINS/Fingerprint) feature, when your other projects that depend on this project pick up a build with a fix, those build numbers can also be recorded in Jira.\n\nThis is quite handy when a bug is fixed in one of the libraries, yet the submitter wants a fix in a different project. This happens often in my work, where a bug is reported against JAX-WS but the fix is in JAXB.\n\nFor curious mind, see [this thread for how this works behind the scene](http://jenkins.361315.n4.nabble.com/How-can-does-Hudson-Jira-integration-works-td374680.html).\n\n### Referencing Jira Release version\n\nTo reference Jira Release versions in your build, you can pull these\nreleases directly from Jira by adding the Jira Release Version\nParameter.\n\nThis can be useful for generating release notes, trigerring\nparameterized build, etc.  \n![version-parameters](images/version_parameters.png)\n\n### Generating Release Notes\n\nYou can also generate release notes to be used during your build. These notes can be retrieved from an environment variable. See the [Maven Project Plugin](https://wiki.jenkins.io/display/JENKINS/Maven+Project+Plugin) for\nthe environment variables found within the POM.  \n![release-notes](images/release_notes.png)\n\nAfter your build has run, you can also have the plugin mark a release as resolved. This typically will be a release you specified in your Build Parameters.  \n![marking-as-resolved](images/mark_as_resolved.png)\n\nThe plugin can also move certain issues matching a JQL query to a new release version.  \n![moving-issues](images/move_issues.png)\n\nSample usage of generated Release Notes:\n![release-notes-config](images/release_notes_config.png)\n\n### Jira Authentication & Permissions required\n\n**Note:** As a rule of thumb, **you should be always using a service account** (instead of a personal account) to integrate Jenkins with Jira.\n\nMake sure that the Jira user used by Jenkins has enough permissions to execute its actions. You can do that via Jira Permission Helper tool.\n\n- For creating Jira issues, the user has to be able to Create Issues in the specified project\n- If you additionally enter assignee or component field values, make sure that:\n      - both of the fields are assigned to the corresponding Jira Screen\n      - the Jira user is Assignable in the project\n      - the Jenkins Jira user can Assign issues\n"
  },
  {
    "path": "docs/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <title>Document</title>\n  <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge,chrome=1\" />\n  <meta name=\"description\" content=\"Description\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, minimum-scale=1.0\">\n  <link rel=\"stylesheet\" href=\"//cdn.jsdelivr.net/npm/docsify@4/lib/themes/buble.css\">\n</head>\n<body>\n  <div id=\"app\"></div>\n  <script>\n    window.$docsify = {\n      name: 'Jenkins Jira plugin',\n      repo: 'https://github.com/jenkinsci/jira-plugin',\n      loadSidebar: true,\n    }\n  </script>\n  <!-- Docsify v4 -->\n  <script src=\"//cdn.jsdelivr.net/npm/docsify@4\"></script>\n  <script src=\"//cdn.jsdelivr.net/npm/docsify/lib/plugins/search.min.js\"></script>\n  <script src=\"//cdn.jsdelivr.net/npm/docsify/lib/plugins/zoom-image.min.js\"></script>\n  <script src=\"//cdn.jsdelivr.net/npm/prismjs/components/prism-groovy.min.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "docs/system-properties.md",
    "content": "# System Properties\n\nThere are some cases when you might want to change the plugin's behavior globally, by overriding [Jenkins system properties](https://www.jenkins.io/doc/book/managing/system-properties/). \n\nThis plugin provides the following additional settings, that are not available via UI:\n\n- `-Dhudson.plugins.jira.JiraMailAddressResolver.disabled=true`\n    Use to disable resolving user email from Jira usernames. \n"
  },
  {
    "path": "docs/troubleshooting.md",
    "content": "# Common issues\n\n## Adding a custom Log Recorder\n\nTo help debug any issues with this plugin, it's useful to look at more detailed logs. To enable them, create a [custom Log Recorder](https://www.jenkins.io/doc/book/system-administration/viewing-logs/#logs-in-jenkins) for Logger `hudson.plugins.jira` and Log Level `FINE`. \n\n## Jenkins <---> Jira SSL connectivity problems\n\nIf you encounter stacktrace like this:\n\n```stacktrace\nCaused by: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target\n```\n\nmake sure the JRE/JDK that Jenkins master is running (or the Jenkins slaves are running) contain the valid CA chain certificates that Jira is running with.\nYou can test it using this [SSLPoke.java class](https://gist.github.com/warden/e4ef13ea60f24d458405613be4ddbc51):\n\n```sh\n$ wget -O SSLPoke.java https://gist.githubusercontent.com/warden/e4ef13ea60f24d458405613be4ddbc51/raw/7f258a30be4ddea7b67239b40ae305f6a2e98e0a/SSLPoke.java\n\n$ /usr/java/jdk1.8.0_131/bin/javac SSLPoke.java\n\n$ /usr/java/jdk1.8.0_131/jre/bin/java SSLPoke jira.domain.com 443\nSuccessfully connected\n```\n\nReferences:\n\n- [Jenkins fails with PKIX Path building error](https://stackoverflow.com/questions/52842214/jenkins-fails-with-pkix-path-building-error)\n- [PKIX path building failed error message\n](https://support.cloudbees.com/hc/en-us/articles/217078498-PKIX-path-building-failed-error-message)\n"
  },
  {
    "path": "docs/usage-examples.md",
    "content": "# Usage examples\n\n## jiraCommentIssues usage example\n\nYou need keep reference to used scm.\nAs an example, you can write a flow:\n\n```groovy\nnode {\n    def gitScm = git url: 'git@github.com:jenkinsci/jira-plugin.git', branch: 'master'\n    sh 'make something'\n    jiraCommentIssues( \n            issueSelector: DefaultSelector(), \n            scm: gitScm)            \n    gitScm = null\n}\n```\n\nNote that a pointer to scm class should be better cleared to not serialize scm object between steps.\n\nYou can add some labels to issue in jira:\n\n```groovy\n    jiraCommentIssues( \n            issueSelector: DefaultSelector(), \n            scm: gitScm,\n            labels: [ \"$version\", \"jenkins\" ])            \n```\n\n## jiraExecuteWorkflow  usage example\n\n```groovy\nnode {\n    jiraExecuteWorkflow(\n            jqlSearch: \"project = EX and labels = 'jenkins' and labels = '${version}'\",\n            workflowActionName: 'Resolve Issue',\n            comment: 'comment')\n}\n```\n\n## jiraCreateReleaseNotes usage example\n\n```groovy\nnode {\n    jiraCreateReleaseNotes(jiraProjectKey: 'TST',\n            jiraRelease: '1.1.1', jiraEnvironmentVariable: 'notes', jiraFilter: 'status in (Resolved, Closed)')\n            {\n                //do some useful here\n                //release notes can be found in environment variable jiraEnvironmentVariable\n                print env.notes\n            }\n}\n```\n\n## jiraMarkVersionReleased usage example\n\n```groovy\nnode {\n    jiraMarkVersionReleased( \n            jiraProjectKey: 'TEST', \n            jiraRelease: '1.1.1')\n}\n```\n\n## jiraUpdateIssueField usage example\n\n```groovy\nnode {\n    jiraUpdateIssueField(\n            issueSelector: ExplicitSelector(\"JIRA-123\"),\n            fieldId: \"10001\",\n            fieldValue: \"value\"\n    )\n}\n```\n\n## SearchIssuesStep\n\nCustom pipeline step (see [step-api](https://github.com/jenkinsci/workflow-plugin/blob/master/step-api/README.md)) that allow to search by jql query directly from workflow.\n\nusage:\n\n```groovy\nnode {\n    List<String> issueKeys = jiraSearch(jql: \"project = EX and labels = 'jenkins' and labels = '${version}'\")\t\n}\n```\n\n## CommentStep\n\nInterface for Pipeline job types that simply want to post a comment e.g.:\n\n```groovy\nnode {\n    jiraComment(issueKey: \"EX-111\", body: \"Job '${env.JOB_NAME}' (${env.BUILD_NUMBER}) built. Please go to ${env.BUILD_URL}.\")\n}\n```\n\n## JiraEnvironmentVariableBuilder\n\nNot supported in Pipeline. You can get current jira url (if you are not using the Groovy sandbox):\n\n```groovy\nimport hudson.plugins.jira.JiraSite;\n\nnode {\n    String jiraUrl = JiraSite.get(currentBuild.rawBuild).name    \t\n    env.JIRA_URL = jiraUrl\n}\n```\n\nTo replace JIRA_ISSUES env variable, you can use pipeline step jiraIssueSelector:\n\n```groovy\n    List<String> issueKeys = jiraIssueSelector()\n```\n\nor if you use custom issue selector:\n\n```groovy\n    List<String> issueKeys = jiraIssueSelector(new CustomIssueSelector())\n```\n\n## Other features\n\nSome features are currently not supported in pipeline.\nIf you are adding new features please make sure that they support Jenkins pipeline Plugin.\nSee [here](https://github.com/jenkinsci/workflow-plugin/blob/master/COMPATIBILITY.md) for some information.\nSee [here](https://github.com/jenkinsci/workflow-plugin/blob/master/basic-steps/CORE-STEPS.md) for more information how core jenkins steps integrate with workflow jobs.\n\nRunning a notifiers is trickier since normally a flow in progress has no status yet, unlike a freestyle project whose status is determined before the notifier is called (never supported).\nSo notifiers will never be implemented as you can use the catchError step and run jira action manually.\nI'm going to create a special pipeline steps to replace this notifiers in future.\n"
  },
  {
    "path": "pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n  <parent>\n    <groupId>org.jenkins-ci.plugins</groupId>\n    <artifactId>plugin</artifactId>\n    <version>6.2138.v03274d462c13</version>\n    <relativePath />\n  </parent>\n\n  <artifactId>jira</artifactId>\n  <version>${revision}${changelist}</version>\n  <packaging>hpi</packaging>\n  <name>Jenkins Jira plugin</name>\n  <description>Integrates Jenkins to Jira</description>\n  <url>https://github.com/jenkinsci/${project.artifactId}-plugin</url>\n\n  <licenses>\n    <license>\n      <name>The MIT license</name>\n      <url>https://github.com/jenkinsci/jira-plugin/raw/master/LICENSE.md</url>\n      <distribution>repo</distribution>\n    </license>\n  </licenses>\n\n  <developers>\n    <developer>\n      <id>olamy</id>\n      <name>Olivier Lamy</name>\n    </developer>\n    <developer>\n      <id>warden</id>\n      <name>Radek Antoniuk</name>\n    </developer>\n  </developers>\n\n  <scm>\n    <connection>scm:git:https://github.com/${gitHubRepo}.git</connection>\n    <developerConnection>scm:git:git@github.com:${gitHubRepo}.git</developerConnection>\n    <tag>jira-3.20</tag>\n    <url>https://github.com/${gitHubRepo}</url>\n  </scm>\n\n  <issueManagement>\n    <system>Github</system>\n    <url>https://github.com/jenkinsci/jira-plugin/issues</url>\n  </issueManagement>\n\n  <properties>\n    <revision>3.22</revision>\n    <changelist>-SNAPSHOT</changelist>\n    <gitHubRepo>jenkinsci/${project.artifactId}-plugin</gitHubRepo>\n    <hpi.bundledArtifacts>atlassian-event,atlassian-httpclient-api,atlassian-util-concurrent,fugue,jira-rest-java-client-api,jira-rest-java-client-core,maven-artifact,plexus-utils,sal-api</hpi.bundledArtifacts>\n    <hpi.strictBundledArtifacts>true</hpi.strictBundledArtifacts>\n    <jira-rest-client.version>6.0.2</jira-rest-client.version>\n    <fugue.version>4.7.2</fugue.version>\n    <!-- https://www.jenkins.io/doc/developer/plugin-development/choosing-jenkins-baseline/ -->\n    <jenkins.baseline>2.492</jenkins.baseline>\n    <jenkins.version>${jenkins.baseline}.3</jenkins.version>\n    <spotless.check.skip>false</spotless.check.skip>\n    <ban-junit4-imports.skip>false</ban-junit4-imports.skip>\n    <ban-commons-lang-2.skip>false</ban-commons-lang-2.skip>\n  </properties>\n\n  <dependencyManagement>\n    <dependencies>\n      <dependency>\n        <groupId>io.jenkins.tools.bom</groupId>\n        <artifactId>bom-${jenkins.baseline}.x</artifactId>\n        <version>5473.vb_9533d9e5d88</version>\n        <type>pom</type>\n        <scope>import</scope>\n      </dependency>\n      <dependency>\n        <groupId>com.atlassian.plugins</groupId>\n        <artifactId>atlassian-plugins-core</artifactId>\n        <version>8.2.1</version>\n      </dependency>\n    </dependencies>\n  </dependencyManagement>\n\n  <dependencies>\n    <dependency>\n      <groupId>com.atlassian.jira</groupId>\n      <artifactId>jira-rest-java-client-api</artifactId>\n      <version>${jira-rest-client.version}</version>\n      <exclusions>\n        <!-- Deprecated and not needed at runtime -->\n        <exclusion>\n          <groupId>com.google.code.findbugs</groupId>\n          <artifactId>jsr305</artifactId>\n        </exclusion>\n        <!-- Provided by Jenkins core -->\n        <exclusion>\n          <groupId>com.google.guava</groupId>\n          <artifactId>guava</artifactId>\n        </exclusion>\n        <!-- Provided by joda-time-api plugin -->\n        <exclusion>\n          <groupId>joda-time</groupId>\n          <artifactId>joda-time</artifactId>\n        </exclusion>\n      </exclusions>\n    </dependency>\n    <dependency>\n      <groupId>com.atlassian.jira</groupId>\n      <artifactId>jira-rest-java-client-core</artifactId>\n      <version>${jira-rest-client.version}</version>\n      <exclusions>\n        <exclusion>\n          <groupId>com.atlassian.httpclient</groupId>\n          <artifactId>atlassian-httpclient-library</artifactId>\n        </exclusion>\n        <exclusion>\n          <groupId>com.atlassian.httpclient</groupId>\n          <artifactId>atlassian-httpclient-plugin</artifactId>\n        </exclusion>\n        <!-- Deprecated and not needed at runtime -->\n        <exclusion>\n          <groupId>com.google.code.findbugs</groupId>\n          <artifactId>jsr305</artifactId>\n        </exclusion>\n        <!-- Provided by Jenkins core -->\n        <exclusion>\n          <groupId>com.google.guava</groupId>\n          <artifactId>guava</artifactId>\n        </exclusion>\n        <exclusion>\n          <groupId>commons-codec</groupId>\n          <artifactId>commons-codec</artifactId>\n        </exclusion>\n        <!-- Provided by joda-time-api plugin -->\n        <exclusion>\n          <groupId>joda-time</groupId>\n          <artifactId>joda-time</artifactId>\n        </exclusion>\n        <!-- Provided by commons-lang3-api plugin -->\n        <exclusion>\n          <groupId>org.apache.commons</groupId>\n          <artifactId>commons-lang3</artifactId>\n        </exclusion>\n        <!-- Provided by apache-httpcomponents-client-4-api plugin -->\n        <exclusion>\n          <groupId>org.apache.httpcomponents</groupId>\n          <artifactId>httpasyncclient</artifactId>\n        </exclusion>\n        <exclusion>\n          <groupId>org.apache.httpcomponents</groupId>\n          <artifactId>httpasyncclient-cache</artifactId>\n        </exclusion>\n        <exclusion>\n          <groupId>org.apache.httpcomponents</groupId>\n          <artifactId>httpclient</artifactId>\n        </exclusion>\n        <exclusion>\n          <groupId>org.apache.httpcomponents</groupId>\n          <artifactId>httpclient-cache</artifactId>\n        </exclusion>\n        <exclusion>\n          <groupId>org.apache.httpcomponents</groupId>\n          <artifactId>httpcore</artifactId>\n        </exclusion>\n        <exclusion>\n          <groupId>org.apache.httpcomponents</groupId>\n          <artifactId>httpcore-nio</artifactId>\n        </exclusion>\n        <exclusion>\n          <groupId>org.apache.httpcomponents</groupId>\n          <artifactId>httpmime</artifactId>\n        </exclusion>\n        <!-- Provided by Jersey 2 API plugin -->\n        <exclusion>\n          <groupId>org.codehaus.jettison</groupId>\n          <artifactId>jettison</artifactId>\n        </exclusion>\n        <exclusion>\n          <groupId>org.glassfish.jersey.core</groupId>\n          <artifactId>jersey-client</artifactId>\n        </exclusion>\n        <exclusion>\n          <groupId>org.glassfish.jersey.core</groupId>\n          <artifactId>jersey-common</artifactId>\n        </exclusion>\n        <exclusion>\n          <groupId>org.glassfish.jersey.media</groupId>\n          <artifactId>jersey-media-jaxb</artifactId>\n        </exclusion>\n        <exclusion>\n          <groupId>org.glassfish.jersey.media</groupId>\n          <artifactId>jersey-media-json-jettison</artifactId>\n        </exclusion>\n        <!-- Provided by Jenkins core -->\n        <exclusion>\n          <groupId>org.slf4j</groupId>\n          <artifactId>slf4j-api</artifactId>\n        </exclusion>\n        <exclusion>\n          <groupId>org.springframework</groupId>\n          <artifactId>spring-beans</artifactId>\n        </exclusion>\n        <exclusion>\n          <groupId>org.springframework</groupId>\n          <artifactId>spring-core</artifactId>\n        </exclusion>\n        <exclusion>\n          <groupId>org.springframework</groupId>\n          <artifactId>spring-jcl</artifactId>\n        </exclusion>\n      </exclusions>\n    </dependency>\n    <dependency>\n      <groupId>io.atlassian.fugue</groupId>\n      <artifactId>fugue</artifactId>\n      <version>${fugue.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>io.atlassian.util.concurrent</groupId>\n      <artifactId>atlassian-util-concurrent</artifactId>\n      <version>4.1.0</version>\n    </dependency>\n    <dependency>\n      <groupId>io.jenkins.plugins</groupId>\n      <artifactId>caffeine-api</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>io.jenkins.plugins</groupId>\n      <artifactId>commons-collections4-api</artifactId>\n      <version>4.5.0-8.va_d5448ef9011</version>\n    </dependency>\n    <dependency>\n      <groupId>io.jenkins.plugins</groupId>\n      <artifactId>commons-lang3-api</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>io.jenkins.plugins</groupId>\n      <artifactId>jersey2-api</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>io.jenkins.plugins</groupId>\n      <artifactId>joda-time-api</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>io.jenkins.plugins</groupId>\n      <artifactId>json-api</artifactId>\n    </dependency>\n    <!-- required for VersionComparator -->\n    <dependency>\n      <groupId>org.apache.maven</groupId>\n      <artifactId>maven-artifact</artifactId>\n      <version>3.9.14</version>\n      <exclusions>\n        <!-- Provided by commons-lang3-api plugin -->\n        <exclusion>\n          <groupId>org.apache.commons</groupId>\n          <artifactId>commons-lang3</artifactId>\n        </exclusion>\n      </exclusions>\n    </dependency>\n    <dependency>\n      <groupId>org.jenkins-ci.plugins</groupId>\n      <artifactId>apache-httpcomponents-client-4-api</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>org.jenkins-ci.plugins</groupId>\n      <artifactId>branch-api</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>org.jenkins-ci.plugins</groupId>\n      <artifactId>credentials</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>org.jenkins-ci.plugins</groupId>\n      <artifactId>jackson2-api</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>org.jenkins-ci.plugins</groupId>\n      <artifactId>mailer</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>org.jenkins-ci.plugins</groupId>\n      <artifactId>matrix-project</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>org.jenkins-ci.plugins</groupId>\n      <artifactId>p4</artifactId>\n      <version>1.17.1</version>\n      <optional>true</optional>\n      <exclusions>\n        <exclusion>\n          <groupId>org.apache.commons</groupId>\n          <artifactId>commons-compress</artifactId>\n        </exclusion>\n        <exclusion>\n          <groupId>org.jenkins-ci.plugins</groupId>\n          <artifactId>*</artifactId>\n        </exclusion>\n      </exclusions>\n    </dependency>\n    <dependency>\n      <groupId>org.jenkins-ci.plugins.workflow</groupId>\n      <artifactId>workflow-job</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>org.jenkins-ci.plugins.workflow</groupId>\n      <artifactId>workflow-step-api</artifactId>\n      <optional>true</optional>\n    </dependency>\n    <dependency>\n      <groupId>io.jenkins.configuration-as-code</groupId>\n      <artifactId>test-harness</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <!-- test dependencies -->\n    <!-- required to start p4 tests -->\n    <dependency>\n      <groupId>org.jenkins-ci.plugins</groupId>\n      <artifactId>command-launcher</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.jenkins-ci.plugins.workflow</groupId>\n      <artifactId>workflow-basic-steps</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.jenkins-ci.plugins.workflow</groupId>\n      <artifactId>workflow-multibranch</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.jenkins-ci.plugins.workflow</groupId>\n      <artifactId>workflow-scm-step</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.jenkins-ci.plugins.workflow</groupId>\n      <artifactId>workflow-step-api</artifactId>\n      <classifier>tests</classifier>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.mockito</groupId>\n      <artifactId>mockito-core</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.mockito</groupId>\n      <artifactId>mockito-junit-jupiter</artifactId>\n      <scope>test</scope>\n    </dependency>\n  </dependencies>\n\n  <repositories>\n    <repository>\n      <releases>\n        <enabled>true</enabled>\n      </releases>\n      <snapshots>\n        <enabled>false</enabled>\n      </snapshots>\n      <id>repo.jenkins-ci.org</id>\n      <url>https://repo.jenkins-ci.org/public/</url>\n    </repository>\n  </repositories>\n\n  <pluginRepositories>\n    <pluginRepository>\n      <releases>\n        <enabled>true</enabled>\n      </releases>\n      <snapshots>\n        <enabled>false</enabled>\n      </snapshots>\n      <id>repo.jenkins-ci.org</id>\n      <url>https://repo.jenkins-ci.org/public/</url>\n    </pluginRepository>\n  </pluginRepositories>\n\n  <build>\n    <plugins>\n      <plugin>\n        <groupId>org.eluder.coveralls</groupId>\n        <artifactId>coveralls-maven-plugin</artifactId>\n        <version>4.3.0</version>\n        <!-- Explicit dependency on jaxb-api to avoid problems with\n          JDK9 and later, until a new release is made:\n          See also https://github.com/trautonen/coveralls-maven-plugin/issues/112\n        -->\n        <dependencies>\n          <dependency>\n            <groupId>javax.xml.bind</groupId>\n            <artifactId>jaxb-api</artifactId>\n            <version>2.3.1</version>\n          </dependency>\n        </dependencies>\n      </plugin>\n      <!-- spotbugs is defined in parent plugin-pom but overriding some things here -->\n      <plugin>\n        <groupId>com.github.spotbugs</groupId>\n        <artifactId>spotbugs-maven-plugin</artifactId>\n        <executions>\n          <execution>\n            <id>spotbugs</id>\n            <goals>\n              <goal>check</goal>\n            </goals>\n            <phase>verify</phase>\n            <configuration>\n              <failOnError>${spotbugs.failOnError}</failOnError>\n              <effort>${spotbugs.effort}</effort>\n              <threshold>${spotbugs.threshold}</threshold>\n              <onlyAnalyze>hudson.plugins.jira.*</onlyAnalyze>\n            </configuration>\n          </execution>\n        </executions>\n      </plugin>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-surefire-plugin</artifactId>\n        <configuration>\n          <reuseForks>false</reuseForks>\n          <!-- fix issues in test when trying to serialize some mocks and to run some tests within your IDE you will need this -->\n          <systemPropertyVariables>\n            <hudson.remoting.ClassFilter>hudson.plugins.jira.JiraFolderProperty,hudson.plugins.jira.JiraSite,hudson.plugins.jira.JiraJobAction,com.atlassian.jira.rest.client.api.domain.IssueType</hudson.remoting.ClassFilter>\n          </systemPropertyVariables>\n        </configuration>\n      </plugin>\n    </plugins>\n  </build>\n</project>\n"
  },
  {
    "path": "src/main/java/com/atlassian/httpclient/apache/httpcomponents/ApacheAsyncHttpClient.java",
    "content": "package com.atlassian.httpclient.apache.httpcomponents;\n\nimport static io.atlassian.util.concurrent.Promises.rejected;\nimport static java.lang.String.format;\n\nimport com.atlassian.event.api.EventPublisher;\nimport com.atlassian.httpclient.api.DefaultResponseTransformation;\nimport com.atlassian.httpclient.api.HttpClient;\nimport com.atlassian.httpclient.api.HttpStatus;\nimport com.atlassian.httpclient.api.Request;\nimport com.atlassian.httpclient.api.Response;\nimport com.atlassian.httpclient.api.ResponsePromise;\nimport com.atlassian.httpclient.api.ResponsePromises;\nimport com.atlassian.httpclient.api.ResponseTransformation;\nimport com.atlassian.httpclient.api.factory.HttpClientOptions;\nimport com.atlassian.httpclient.base.event.HttpRequestCompletedEvent;\nimport com.atlassian.httpclient.base.event.HttpRequestFailedEvent;\nimport com.atlassian.sal.api.ApplicationProperties;\nimport com.atlassian.sal.api.executor.ThreadLocalContextManager;\nimport hudson.ProxyConfiguration;\nimport io.atlassian.util.concurrent.ThreadFactories;\nimport java.io.IOException;\nimport java.net.URI;\nimport java.security.GeneralSecurityException;\nimport java.security.KeyManagementException;\nimport java.security.KeyStoreException;\nimport java.security.NoSuchAlgorithmException;\nimport java.security.cert.X509Certificate;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.Function;\nimport java.util.function.Supplier;\nimport java.util.regex.Pattern;\nimport javax.net.ssl.SSLContext;\nimport javax.net.ssl.SSLException;\nimport javax.net.ssl.SSLSession;\nimport javax.net.ssl.SSLSocket;\nimport jenkins.model.Jenkins;\nimport org.apache.commons.lang3.StringUtils;\nimport org.apache.http.Header;\nimport org.apache.http.HttpEntity;\nimport org.apache.http.HttpException;\nimport org.apache.http.HttpHost;\nimport org.apache.http.HttpRequest;\nimport org.apache.http.HttpResponse;\nimport org.apache.http.StatusLine;\nimport org.apache.http.auth.AuthScope;\nimport org.apache.http.auth.UsernamePasswordCredentials;\nimport org.apache.http.client.CredentialsProvider;\nimport org.apache.http.client.config.CookieSpecs;\nimport org.apache.http.client.config.RequestConfig;\nimport org.apache.http.client.methods.HttpDelete;\nimport org.apache.http.client.methods.HttpGet;\nimport org.apache.http.client.methods.HttpHead;\nimport org.apache.http.client.methods.HttpOptions;\nimport org.apache.http.client.methods.HttpPost;\nimport org.apache.http.client.methods.HttpPut;\nimport org.apache.http.client.methods.HttpRequestBase;\nimport org.apache.http.client.methods.HttpTrace;\nimport org.apache.http.config.Registry;\nimport org.apache.http.config.RegistryBuilder;\nimport org.apache.http.conn.ssl.SSLContextBuilder;\nimport org.apache.http.conn.ssl.TrustSelfSignedStrategy;\nimport org.apache.http.conn.ssl.X509HostnameVerifier;\nimport org.apache.http.impl.client.BasicCredentialsProvider;\nimport org.apache.http.impl.client.ProxyAuthenticationStrategy;\nimport org.apache.http.impl.conn.DefaultRoutePlanner;\nimport org.apache.http.impl.conn.DefaultSchemePortResolver;\nimport org.apache.http.impl.conn.SystemDefaultDnsResolver;\nimport org.apache.http.impl.nio.client.CloseableHttpAsyncClient;\nimport org.apache.http.impl.nio.client.HttpAsyncClientBuilder;\nimport org.apache.http.impl.nio.client.HttpAsyncClients;\nimport org.apache.http.impl.nio.conn.ManagedNHttpClientConnectionFactory;\nimport org.apache.http.impl.nio.conn.PoolingNHttpClientConnectionManager;\nimport org.apache.http.impl.nio.reactor.DefaultConnectingIOReactor;\nimport org.apache.http.impl.nio.reactor.IOReactorConfig;\nimport org.apache.http.nio.conn.NoopIOSessionStrategy;\nimport org.apache.http.nio.conn.SchemeIOSessionStrategy;\nimport org.apache.http.nio.conn.ssl.SSLIOSessionStrategy;\nimport org.apache.http.nio.reactor.IOReactorException;\nimport org.apache.http.nio.reactor.IOReactorExceptionHandler;\nimport org.apache.http.protocol.BasicHttpContext;\nimport org.apache.http.protocol.HttpContext;\nimport org.apache.http.util.Args;\nimport org.apache.http.util.TextUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.beans.factory.DisposableBean;\n\npublic final class ApacheAsyncHttpClient<C> implements HttpClient, DisposableBean {\n    private final Logger log = LoggerFactory.getLogger(this.getClass());\n\n    private static final Supplier<String> httpClientVersion =\n            () -> MavenUtils.getVersion(\"com.atlassian.httpclient\", \"atlassian-httpclient-api\");\n\n    private final Function<Object, Void> eventConsumer;\n    private final Supplier<String> applicationName;\n    private final ThreadLocalContextManager<C> threadLocalContextManager;\n    private final ExecutorService callbackExecutor;\n    private final HttpClientOptions httpClientOptions;\n\n    private final CloseableHttpAsyncClient nonCachingHttpClient;\n\n    public ApacheAsyncHttpClient(\n            EventPublisher eventConsumer,\n            ApplicationProperties applicationProperties,\n            ThreadLocalContextManager<C> threadLocalContextManager) {\n        this(eventConsumer, applicationProperties, threadLocalContextManager, new HttpClientOptions());\n    }\n\n    public ApacheAsyncHttpClient(\n            EventPublisher eventConsumer,\n            ApplicationProperties applicationProperties,\n            ThreadLocalContextManager<C> threadLocalContextManager,\n            HttpClientOptions options) {\n        this(\n                new DefaultApplicationNameSupplier(applicationProperties),\n                new EventConsumerFunction(eventConsumer),\n                threadLocalContextManager,\n                options);\n    }\n\n    public ApacheAsyncHttpClient(\n            final Supplier<String> applicationName,\n            final Function<Object, Void> eventConsumer,\n            final ThreadLocalContextManager<C> threadLocalContextManager,\n            final HttpClientOptions options) {\n        this.eventConsumer = Objects.requireNonNull(eventConsumer);\n        this.applicationName = Objects.requireNonNull(applicationName);\n        this.threadLocalContextManager = Objects.requireNonNull(threadLocalContextManager);\n        this.httpClientOptions = Objects.requireNonNull(options);\n\n        try {\n            final HttpAsyncClientBuilder clientBuilder = createClientBuilder();\n\n            this.nonCachingHttpClient = clientBuilder.build();\n            this.callbackExecutor = options.getCallbackExecutor();\n            nonCachingHttpClient.start();\n        } catch (IOReactorException e) {\n            throw new RuntimeException(\"Reactor \" + options.getThreadPrefix() + \"not set up correctly\", e);\n        }\n    }\n\n    private HttpAsyncClientBuilder createClientBuilder() throws IOReactorException {\n\n        final HttpClientOptions options = httpClientOptions;\n        final IOReactorConfig reactorConfig = IOReactorConfig.custom()\n                .setIoThreadCount(options.getIoThreadCount())\n                .setSelectInterval(options.getIoSelectInterval())\n                .setInterestOpQueued(true)\n                .build();\n\n        final DefaultConnectingIOReactor ioReactor = new DefaultConnectingIOReactor(reactorConfig);\n        ioReactor.setExceptionHandler(new IOReactorExceptionHandler() {\n            @Override\n            public boolean handle(final IOException e) {\n                log.error(\"IO exception in reactor \", e);\n                return false;\n            }\n\n            @Override\n            public boolean handle(final RuntimeException e) {\n                log.error(\"Fatal runtime error\", e);\n                return false;\n            }\n        });\n\n        final PoolingNHttpClientConnectionManager connectionManager =\n                new PoolingNHttpClientConnectionManager(\n                        ioReactor,\n                        ManagedNHttpClientConnectionFactory.INSTANCE,\n                        getRegistry(options),\n                        DefaultSchemePortResolver.INSTANCE,\n                        SystemDefaultDnsResolver.INSTANCE,\n                        options.getConnectionPoolTimeToLive(),\n                        TimeUnit.MILLISECONDS) {\n                    @Override\n                    protected void finalize() throws Throwable {\n                        // prevent the PoolingClientAsyncConnectionManager from logging - this causes exceptions due to\n                        // the ClassLoader probably having been removed when the plugin shuts down.  Added a\n                        // PluginEventListener to make sure the shutdown method is called while the plugin classloader\n                        // is still active.\n                        try {\n                            this.shutdown();\n                        } catch (Throwable e) {\n                            // ignore e.printStackTrace();\n                        }\n                    }\n                };\n\n        connectionManager.setDefaultMaxPerRoute(options.getMaxConnectionsPerHost());\n\n        final RequestConfig requestConfig = RequestConfig.custom()\n                .setConnectTimeout((int) options.getConnectionTimeout())\n                .setConnectionRequestTimeout((int) options.getLeaseTimeout())\n                .setCookieSpec(options.getIgnoreCookies() ? CookieSpecs.IGNORE_COOKIES : CookieSpecs.DEFAULT)\n                .setSocketTimeout((int) options.getSocketTimeout())\n                .build();\n\n        final HttpAsyncClientBuilder clientBuilder = HttpAsyncClients.custom()\n                .setThreadFactory(ThreadFactories.namedThreadFactory(\n                        options.getThreadPrefix() + \"-io\", ThreadFactories.Type.DAEMON))\n                .setDefaultIOReactorConfig(reactorConfig)\n                .setConnectionManager(connectionManager)\n                .setRedirectStrategy(new RedirectStrategy())\n                .setUserAgent(getUserAgent(options))\n                .setDefaultRequestConfig(requestConfig);\n\n        if (Jenkins.get() != null) {\n            ProxyConfiguration proxyConfiguration = Jenkins.getInstance().proxy;\n            if (proxyConfiguration != null) {\n                final HttpHost proxy = new HttpHost(proxyConfiguration.name, proxyConfiguration.port);\n                // clientBuilder.setProxy( proxy );\n                if (StringUtils.isNotBlank(proxyConfiguration.getUserName())) {\n                    clientBuilder.setProxyAuthenticationStrategy(ProxyAuthenticationStrategy.INSTANCE);\n                    CredentialsProvider credsProvider = new BasicCredentialsProvider();\n                    credsProvider.setCredentials(\n                            new AuthScope(proxyConfiguration.name, proxyConfiguration.port),\n                            new UsernamePasswordCredentials(\n                                    proxyConfiguration.getUserName(), proxyConfiguration.getPassword()));\n                    clientBuilder.setDefaultCredentialsProvider(credsProvider);\n                }\n\n                clientBuilder.setRoutePlanner(\n                        new JenkinsProxyRoutePlanner(proxy, proxyConfiguration.getNoProxyHostPatterns()));\n            }\n        }\n\n        return clientBuilder;\n    }\n\n    private class JenkinsProxyRoutePlanner extends DefaultRoutePlanner {\n        private final HttpHost proxy;\n        private final List<Pattern> nonProxyHosts;\n\n        public JenkinsProxyRoutePlanner(HttpHost proxy, List<Pattern> nonProxyHosts) {\n            super(null);\n            this.proxy = Args.notNull(proxy, \"Proxy host\");\n            this.nonProxyHosts = nonProxyHosts;\n        }\n\n        @Override\n        protected HttpHost determineProxy(HttpHost target, HttpRequest request, HttpContext context)\n                throws HttpException {\n            return bypassProxy(target.getHostName()) ? null : this.proxy;\n        }\n\n        private boolean bypassProxy(String host) {\n            for (Pattern p : nonProxyHosts) {\n                if (p.matcher(host).matches()) {\n                    return true;\n                }\n            }\n            return false;\n        }\n    }\n\n    private Registry<SchemeIOSessionStrategy> getRegistry(final HttpClientOptions options) {\n        try {\n            final TrustSelfSignedStrategy strategy =\n                    options.trustSelfSignedCertificates() ? new TrustSelfSignedStrategy() : null;\n\n            final SSLContext sslContext = new SSLContextBuilder()\n                    .useTLS()\n                    .loadTrustMaterial(null, strategy)\n                    .build();\n\n            final SSLIOSessionStrategy sslioSessionStrategy = new SSLIOSessionStrategy(\n                    sslContext,\n                    split(System.getProperty(\"https.protocols\")),\n                    split(System.getProperty(\"https.cipherSuites\")),\n                    options.trustSelfSignedCertificates()\n                            ? getSelfSignedVerifier()\n                            : SSLIOSessionStrategy.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);\n\n            return RegistryBuilder.<SchemeIOSessionStrategy>create()\n                    .register(\"http\", NoopIOSessionStrategy.INSTANCE)\n                    .register(\"https\", sslioSessionStrategy)\n                    .build();\n        } catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {\n            return getFallbackRegistry(e);\n        }\n    }\n\n    private X509HostnameVerifier getSelfSignedVerifier() {\n        return new X509HostnameVerifier() {\n            @Override\n            public void verify(final String host, final SSLSocket ssl) throws IOException {\n                log.debug(\"Verification for certificates from {} disabled\", host);\n            }\n\n            @Override\n            public void verify(final String host, final X509Certificate cert) throws SSLException {\n                log.debug(\"Verification for certificates from {} disabled\", host);\n            }\n\n            @Override\n            public void verify(final String host, final String[] cns, final String[] subjectAlts) throws SSLException {\n                log.debug(\"Verification for certificates from {} disabled\", host);\n            }\n\n            @Override\n            public boolean verify(final String host, final SSLSession sslSession) {\n                log.debug(\"Verification for certificates from {} disabled\", host);\n                return true;\n            }\n        };\n    }\n\n    private Registry<SchemeIOSessionStrategy> getFallbackRegistry(final GeneralSecurityException e) {\n        log.error(\"Error when creating scheme session strategy registry\", e);\n        return RegistryBuilder.<SchemeIOSessionStrategy>create()\n                .register(\"http\", NoopIOSessionStrategy.INSTANCE)\n                .register(\"https\", SSLIOSessionStrategy.getDefaultStrategy())\n                .build();\n    }\n\n    private String getUserAgent(HttpClientOptions options) {\n        return format(\n                \"Atlassian HttpClient %s / %s / %s\",\n                httpClientVersion.get(), applicationName.get(), options.getUserAgent());\n    }\n\n    @Override\n    public final ResponsePromise execute(final Request request) {\n        try {\n            return doExecute(request);\n        } catch (Throwable t) {\n            return ResponsePromises.toResponsePromise(rejected(t));\n        }\n    }\n\n    private ResponsePromise doExecute(final Request request) {\n        httpClientOptions.getRequestPreparer().accept(request);\n\n        final long start = System.currentTimeMillis();\n        final HttpRequestBase op;\n        final String uri = request.getUri().toString();\n        final Request.Method method = request.getMethod();\n        switch (method) {\n            case GET:\n                op = new HttpGet(uri);\n                break;\n            case POST:\n                op = new HttpPost(uri);\n                break;\n            case PUT:\n                op = new HttpPut(uri);\n                break;\n            case DELETE:\n                op = new HttpDelete(uri);\n                break;\n            case OPTIONS:\n                op = new HttpOptions(uri);\n                break;\n            case HEAD:\n                op = new HttpHead(uri);\n                break;\n            case TRACE:\n                op = new HttpTrace(uri);\n                break;\n            default:\n                throw new UnsupportedOperationException(method.toString());\n        }\n        if (request.hasEntity()) {\n            new RequestEntityEffect(request).apply(op);\n        }\n\n        for (Map.Entry<String, String> entry : request.getHeaders().entrySet()) {\n            op.setHeader(entry.getKey(), entry.getValue());\n        }\n\n        final PromiseHttpAsyncClient asyncClient = getPromiseHttpAsyncClient(request);\n        return ResponsePromises.toResponsePromise(asyncClient\n                .execute(op, new BasicHttpContext())\n                .fold(\n                        throwable -> {\n                            final long requestDuration = System.currentTimeMillis() - start;\n                            publishEvent(request, requestDuration, throwable);\n                            throw new RuntimeException(throwable);\n                        },\n                        httpResponse -> {\n                            final long requestDuration = System.currentTimeMillis() - start;\n                            publishEvent(\n                                    request,\n                                    requestDuration,\n                                    httpResponse.getStatusLine().getStatusCode());\n                            try {\n                                return translate(httpResponse);\n                            } catch (IOException e) {\n                                throw new RuntimeException(e);\n                            }\n                        }));\n    }\n\n    private void publishEvent(Request request, long requestDuration, int statusCode) {\n        if (HttpStatus.OK.code <= statusCode && statusCode < HttpStatus.MULTIPLE_CHOICES.code) {\n            eventConsumer.apply(new HttpRequestCompletedEvent(\n                    request.getUri().toString(),\n                    request.getMethod().name(),\n                    statusCode,\n                    requestDuration,\n                    request.getAttributes()));\n        } else {\n            eventConsumer.apply(new HttpRequestFailedEvent(\n                    request.getUri().toString(),\n                    request.getMethod().name(),\n                    statusCode,\n                    requestDuration,\n                    request.getAttributes()));\n        }\n    }\n\n    private void publishEvent(Request request, long requestDuration, Throwable ex) {\n        eventConsumer.apply(new HttpRequestFailedEvent(\n                request.getUri().toString(),\n                request.getMethod().name(),\n                ex.toString(),\n                requestDuration,\n                request.getAttributes()));\n    }\n\n    private PromiseHttpAsyncClient getPromiseHttpAsyncClient(Request request) {\n\n        log.trace(\"Creating new HttpAsyncClient\");\n        final CloseableHttpAsyncClient nonCachingHttpClient;\n        try {\n            final HttpAsyncClientBuilder clientBuilder = createClientBuilder();\n\n            nonCachingHttpClient = clientBuilder.build();\n            nonCachingHttpClient.start();\n        } catch (IOReactorException e) {\n            throw new RuntimeException(\"Reactor \" + httpClientOptions.getThreadPrefix() + \"not set up correctly\", e);\n        }\n\n        return new CompletableFuturePromiseHttpPromiseAsyncClient<>(\n                nonCachingHttpClient, threadLocalContextManager, callbackExecutor);\n    }\n\n    private Response translate(HttpResponse httpResponse) throws IOException {\n        StatusLine status = httpResponse.getStatusLine();\n        Response.Builder responseBuilder = DefaultResponse.builder()\n                .setMaxEntitySize(httpClientOptions.getMaxEntitySize())\n                .setStatusCode(status.getStatusCode())\n                .setStatusText(status.getReasonPhrase());\n\n        Header[] httpHeaders = httpResponse.getAllHeaders();\n        for (Header httpHeader : httpHeaders) {\n            responseBuilder.setHeader(httpHeader.getName(), httpHeader.getValue());\n        }\n        final HttpEntity entity = httpResponse.getEntity();\n        if (entity != null) {\n            responseBuilder.setEntityStream(entity.getContent());\n        }\n        return responseBuilder.build();\n    }\n\n    @Override\n    public void destroy() throws Exception {\n        try {\n            callbackExecutor.shutdown();\n        } catch (Exception e) {\n            log.warn(\"skip fail to shutdown callbackExecutor:\" + e.getMessage(), e);\n        }\n        try {\n            nonCachingHttpClient.close();\n        } catch (Exception e) {\n            log.warn(\"skip fail to shutdown nonCachingHttpClient:\" + e.getMessage(), e);\n        }\n    }\n\n    @Override\n    public void flushCacheByUriPattern(Pattern urlPattern) {\n        // httpCacheStorage.flushByUriPattern(urlPattern);\n    }\n\n    private static final class NoOpThreadLocalContextManager<C> implements ThreadLocalContextManager<C> {\n        @Override\n        public C getThreadLocalContext() {\n            return null;\n        }\n\n        @Override\n        public void setThreadLocalContext(C context) {}\n\n        @Override\n        public void clearThreadLocalContext() {}\n    }\n\n    private static final class DefaultApplicationNameSupplier implements Supplier<String> {\n        private final ApplicationProperties applicationProperties;\n\n        public DefaultApplicationNameSupplier(ApplicationProperties applicationProperties) {\n            this.applicationProperties = Objects.requireNonNull(applicationProperties);\n        }\n\n        @Override\n        public String get() {\n            return format(\n                    \"%s-%s (%s)\",\n                    applicationProperties.getDisplayName(),\n                    applicationProperties.getVersion(),\n                    applicationProperties.getBuildNumber());\n        }\n    }\n\n    private static class EventConsumerFunction implements Function<Object, Void> {\n        private final EventPublisher eventPublisher;\n\n        public EventConsumerFunction(EventPublisher eventPublisher) {\n            this.eventPublisher = eventPublisher;\n        }\n\n        @Override\n        public Void apply(Object event) {\n            if (eventPublisher != null) {\n                eventPublisher.publish(event);\n            }\n            return null;\n        }\n    }\n\n    private static String[] split(final String s) {\n        if (TextUtils.isBlank(s)) {\n            return null;\n        }\n        return s.split(\" *, *\");\n    }\n\n    @Override\n    public Request.Builder newRequest() {\n        return DefaultRequest.builder(this);\n    }\n\n    @Override\n    public Request.Builder newRequest(URI uri) {\n        return DefaultRequest.builder(this).setUri(uri);\n    }\n\n    @Override\n    public Request.Builder newRequest(URI uri, String contentType, String entity) {\n        return DefaultRequest.builder(this)\n                .setContentType(contentType)\n                .setEntity(entity)\n                .setUri(uri);\n    }\n\n    @Override\n    public Request.Builder newRequest(String uri) {\n        return newRequest(URI.create(uri));\n    }\n\n    @Override\n    public Request.Builder newRequest(String uri, String contentType, String entity) {\n        return newRequest(URI.create(uri), contentType, entity);\n    }\n\n    @Override\n    public <A> ResponseTransformation.Builder<A> transformation() {\n        return DefaultResponseTransformation.builder();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/atlassian/httpclient/apache/httpcomponents/CommonBuilder.java",
    "content": "package com.atlassian.httpclient.apache.httpcomponents;\n\nimport com.atlassian.httpclient.api.Common;\nimport java.io.InputStream;\nimport java.nio.charset.Charset;\nimport java.util.Map;\n\npublic class CommonBuilder<T> implements Common<CommonBuilder<T>> {\n    private final Headers.Builder headersBuilder = new Headers.Builder();\n    private InputStream entityStream;\n\n    @Override\n    public CommonBuilder<T> setHeader(final String name, final String value) {\n        headersBuilder.setHeader(name, value);\n        return this;\n    }\n\n    @Override\n    public CommonBuilder<T> setHeaders(final Map<String, String> headers) {\n        headersBuilder.setHeaders(headers);\n        return this;\n    }\n\n    @Override\n    public CommonBuilder<T> setEntity(final String entity) {\n        if (entity != null) {\n            final String charset = \"UTF-8\";\n            byte[] bytes = entity.getBytes(Charset.forName(charset));\n            setEntityStream(new EntityByteArrayInputStream(bytes), charset);\n        } else {\n            setEntityStream(null, null);\n        }\n        return this;\n    }\n\n    @Override\n    public CommonBuilder<T> setEntityStream(final InputStream entityStream) {\n        this.entityStream = entityStream;\n        return this;\n    }\n\n    @Override\n    public CommonBuilder<T> setContentCharset(final String contentCharset) {\n        headersBuilder.setContentCharset(contentCharset);\n        return this;\n    }\n\n    @Override\n    public CommonBuilder<T> setContentType(final String contentType) {\n        headersBuilder.setContentType(contentType);\n        return this;\n    }\n\n    @Override\n    public CommonBuilder<T> setEntityStream(final InputStream entityStream, final String charset) {\n        setEntityStream(entityStream);\n        headersBuilder.setContentCharset(charset);\n        return this;\n    }\n\n    public InputStream getEntityStream() {\n        return entityStream;\n    }\n\n    public Headers getHeaders() {\n        return headersBuilder.build();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/atlassian/httpclient/apache/httpcomponents/CompletableFuturePromiseHttpPromiseAsyncClient.java",
    "content": "package com.atlassian.httpclient.apache.httpcomponents;\n\nimport com.atlassian.sal.api.executor.ThreadLocalContextManager;\nimport io.atlassian.util.concurrent.Promise;\nimport io.atlassian.util.concurrent.Promises;\nimport java.io.IOException;\nimport java.util.Objects;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.Executor;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.TimeoutException;\nimport org.apache.http.HttpResponse;\nimport org.apache.http.client.methods.HttpUriRequest;\nimport org.apache.http.concurrent.FutureCallback;\nimport org.apache.http.impl.nio.client.CloseableHttpAsyncClient;\nimport org.apache.http.protocol.HttpContext;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nfinal class CompletableFuturePromiseHttpPromiseAsyncClient<C> implements PromiseHttpAsyncClient {\n    private final Logger log = LoggerFactory.getLogger(this.getClass());\n\n    private final CloseableHttpAsyncClient client;\n    private final ThreadLocalContextManager<C> threadLocalContextManager;\n    private final Executor executor;\n\n    CompletableFuturePromiseHttpPromiseAsyncClient(\n            CloseableHttpAsyncClient client,\n            ThreadLocalContextManager<C> threadLocalContextManager,\n            Executor executor) {\n        this.client = Objects.requireNonNull(client);\n        this.threadLocalContextManager = Objects.requireNonNull(threadLocalContextManager);\n        this.executor = new ThreadLocalDelegateExecutor<>(threadLocalContextManager, executor);\n    }\n\n    @Override\n    public Promise<HttpResponse> execute(HttpUriRequest request, HttpContext context) {\n        // TODO after migrating from atlassian-util-concurrent 3.0.0 to 4.0.0 the SettableFuture.create() maybe obsolete\n        // ?\n        CompletableFuture<HttpResponse> future = new CompletableFuture<>();\n        Future<org.apache.http.HttpResponse> clientFuture = client.execute(\n                request, context, new ThreadLocalContextAwareFutureCallback<C>(threadLocalContextManager) {\n\n                    @Override\n                    void doCompleted(final HttpResponse httpResponse) {\n                        executor.execute(() -> future.complete(httpResponse));\n                        log.trace(\"Closing in doCompleted()\");\n                        closeClient();\n                    }\n\n                    @Override\n                    void doFailed(final Exception ex) {\n                        executor.execute(() -> future.completeExceptionally(ex));\n                        log.trace(\"Closing in doFailed()\");\n                        closeClient();\n                    }\n\n                    @Override\n                    void doCancelled() {\n                        final TimeoutException timeoutException = new TimeoutException();\n                        executor.execute(() -> future.completeExceptionally(timeoutException));\n                        log.trace(\"Closing in doCancelled()\");\n                        closeClient();\n                    }\n                });\n        return Promises.forFuture(clientFuture, executor);\n    }\n\n    private void closeClient() {\n        try {\n            client.close();\n        } catch (IOException e) {\n            log.error(\"Close failed\", e);\n        }\n    }\n\n    static <C> void runInContext(\n            ThreadLocalContextManager<C> threadLocalContextManager,\n            C threadLocalContext,\n            ClassLoader contextClassLoader,\n            Runnable runnable) {\n        final C oldThreadLocalContext = threadLocalContextManager.getThreadLocalContext();\n        final ClassLoader oldCcl = Thread.currentThread().getContextClassLoader();\n        try {\n            Thread.currentThread().setContextClassLoader(contextClassLoader);\n            threadLocalContextManager.setThreadLocalContext(threadLocalContext);\n            runnable.run();\n        } finally {\n            threadLocalContextManager.setThreadLocalContext(oldThreadLocalContext);\n            Thread.currentThread().setContextClassLoader(oldCcl);\n        }\n    }\n\n    private abstract static class ThreadLocalContextAwareFutureCallback<C> implements FutureCallback<HttpResponse> {\n        private final ThreadLocalContextManager<C> threadLocalContextManager;\n        private final C threadLocalContext;\n        private final ClassLoader contextClassLoader;\n\n        private ThreadLocalContextAwareFutureCallback(ThreadLocalContextManager<C> threadLocalContextManager) {\n            this.threadLocalContextManager = Objects.requireNonNull(threadLocalContextManager);\n            this.threadLocalContext = threadLocalContextManager.getThreadLocalContext();\n            this.contextClassLoader = Thread.currentThread().getContextClassLoader();\n        }\n\n        abstract void doCompleted(HttpResponse response);\n\n        abstract void doFailed(Exception ex);\n\n        abstract void doCancelled();\n\n        @Override\n        public final void completed(final HttpResponse response) {\n            runInContext(\n                    threadLocalContextManager, threadLocalContext, contextClassLoader, () -> doCompleted(response));\n        }\n\n        @Override\n        public final void failed(final Exception ex) {\n            runInContext(threadLocalContextManager, threadLocalContext, contextClassLoader, () -> doFailed(ex));\n        }\n\n        @Override\n        public final void cancelled() {\n            runInContext(threadLocalContextManager, threadLocalContext, contextClassLoader, this::doCancelled);\n        }\n    }\n\n    private static final class ThreadLocalDelegateExecutor<C> implements Executor {\n        private final Executor delegate;\n        private final ThreadLocalContextManager<C> manager;\n\n        ThreadLocalDelegateExecutor(ThreadLocalContextManager<C> manager, Executor delegate) {\n            this.delegate = Objects.requireNonNull(delegate);\n            this.manager = Objects.requireNonNull(manager);\n        }\n\n        @Override\n        public void execute(Runnable runnable) {\n            delegate.execute(new ThreadLocalDelegateRunnable<>(manager, runnable));\n        }\n    }\n\n    private static final class ThreadLocalDelegateRunnable<C> implements Runnable {\n        private final C context;\n        private final Runnable delegate;\n        private final ClassLoader contextClassLoader;\n        private final ThreadLocalContextManager<C> manager;\n\n        ThreadLocalDelegateRunnable(ThreadLocalContextManager<C> manager, Runnable delegate) {\n            this.delegate = delegate;\n            this.manager = manager;\n            this.context = manager.getThreadLocalContext();\n            this.contextClassLoader = Thread.currentThread().getContextClassLoader();\n        }\n\n        @Override\n        public void run() {\n            runInContext(manager, context, contextClassLoader, delegate);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/atlassian/httpclient/apache/httpcomponents/DefaultHttpClientFactory.java",
    "content": "package com.atlassian.httpclient.apache.httpcomponents;\n\nimport com.atlassian.event.api.EventPublisher;\nimport com.atlassian.httpclient.api.HttpClient;\nimport com.atlassian.httpclient.api.factory.HttpClientFactory;\nimport com.atlassian.httpclient.api.factory.HttpClientOptions;\nimport com.atlassian.sal.api.ApplicationProperties;\nimport com.atlassian.sal.api.executor.ThreadLocalContextManager;\nimport edu.umd.cs.findbugs.annotations.NonNull;\nimport java.util.Objects;\nimport org.springframework.beans.factory.DisposableBean;\n\npublic final class DefaultHttpClientFactory implements HttpClientFactory, DisposableBean {\n    private final EventPublisher eventPublisher;\n    private final ApplicationProperties applicationProperties;\n    private final ThreadLocalContextManager threadLocalContextManager;\n    // shared http client\n    private static ApacheAsyncHttpClient httpClient;\n\n    public DefaultHttpClientFactory(\n            EventPublisher eventPublisher,\n            ApplicationProperties applicationProperties,\n            ThreadLocalContextManager threadLocalContextManager) {\n        this.eventPublisher = Objects.requireNonNull(eventPublisher);\n        this.applicationProperties = Objects.requireNonNull(applicationProperties);\n        this.threadLocalContextManager = Objects.requireNonNull(threadLocalContextManager);\n    }\n\n    @Override\n    public HttpClient create(HttpClientOptions options) {\n        return doCreate(options, threadLocalContextManager);\n    }\n\n    @Override\n    public HttpClient create(HttpClientOptions options, ThreadLocalContextManager threadLocalContextManager) {\n        return doCreate(options, threadLocalContextManager);\n    }\n\n    @Override\n    public void dispose(@NonNull final HttpClient httpClient) throws Exception {\n        if (httpClient instanceof ApacheAsyncHttpClient) {\n            ((ApacheAsyncHttpClient) httpClient).destroy();\n        }\n    }\n\n    private HttpClient doCreate(HttpClientOptions options, ThreadLocalContextManager threadLocalContextManager) {\n        Objects.requireNonNull(options);\n        // we create only one http client instance as we don't need more\n\n        if (httpClient != null) {\n            return httpClient;\n        }\n        synchronized (this) {\n            httpClient = new ApacheAsyncHttpClient(\n                    eventPublisher, applicationProperties, threadLocalContextManager, options);\n            return httpClient;\n        }\n    }\n\n    @Override\n    public void destroy() throws Exception {\n        httpClient.destroy();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/atlassian/httpclient/apache/httpcomponents/DefaultMessage.java",
    "content": "package com.atlassian.httpclient.apache.httpcomponents;\n\nimport com.atlassian.httpclient.api.Message;\nimport io.atlassian.fugue.Option;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.io.Reader;\nimport java.nio.charset.Charset;\nimport java.util.Map;\nimport org.apache.http.util.CharArrayBuffer;\n\n/**\n * An abstract base class for HTTP messages (i.e. Request and Response) with support for\n * header and entity management.\n */\nabstract class DefaultMessage implements Message {\n    private final InputStream entityStream;\n    private final Headers headers;\n    private final long maxEntitySize;\n    private boolean hasRead;\n\n    public DefaultMessage(final Headers headers, final InputStream entityStream, Option<Long> maxEntitySize) {\n        this.maxEntitySize = maxEntitySize.getOrElse((long) Integer.MAX_VALUE);\n        this.headers = headers;\n        this.entityStream = entityStream;\n    }\n\n    @Override\n    public String getContentType() {\n        return headers.getContentType();\n    }\n\n    @Override\n    public String getContentCharset() {\n        return headers.getContentCharset();\n    }\n\n    public String getAccept() {\n        return headers.getHeader(\"Accept\");\n    }\n\n    @Override\n    public InputStream getEntityStream() throws IllegalStateException {\n        checkRead();\n        return entityStream;\n    }\n\n    @Override\n    public String getEntity() throws IllegalStateException, IllegalArgumentException {\n        String entity = null;\n        if (hasEntity()) {\n            checkValidSize();\n            final String charsetAsString = getContentCharset();\n            final Charset charset =\n                    charsetAsString != null ? Charset.forName(charsetAsString) : Charset.forName(\"UTF-8\");\n            try {\n                InputStream instream = getEntityStream();\n                if (instream == null) {\n                    return null;\n                }\n                try {\n                    int bufferLength = 4096;\n                    String lengthHeader = getHeader(\"Content-Length\");\n                    if (lengthHeader != null) {\n                        bufferLength = Integer.parseInt(lengthHeader);\n                    }\n\n                    Reader reader = new InputStreamReader(instream, charset);\n                    CharArrayBuffer buffer = new CharArrayBuffer(bufferLength);\n                    char[] tmp = new char[1024];\n                    int l;\n                    while ((l = reader.read(tmp)) != -1) {\n                        if (buffer.length() + l > maxEntitySize) {\n                            throw new IllegalArgumentException(\"HTTP entity too large to be buffered in memory\");\n                        }\n                        buffer.append(tmp, 0, l);\n                    }\n                    return buffer.toString();\n                } finally {\n                    instream.close();\n                }\n            } catch (IOException e) {\n                throw new IllegalStateException(\"Unable to convert response body to String\", e);\n            }\n        }\n        return entity;\n    }\n\n    @Override\n    public boolean hasEntity() {\n        return entityStream != null;\n    }\n\n    @Override\n    public boolean hasReadEntity() {\n        return hasRead;\n    }\n\n    @Override\n    public Map<String, String> getHeaders() {\n        return headers.getHeaders();\n    }\n\n    @Override\n    public String getHeader(String name) {\n        return headers.getHeader(name);\n    }\n\n    public Message validate() {\n        if (hasEntity() && headers.getContentType() == null) {\n            throw new IllegalStateException(\"Property contentType must be set when entity is present\");\n        }\n        return this;\n    }\n\n    private void checkRead() throws IllegalStateException {\n        if (entityStream != null) {\n            if (hasRead) {\n                throw new IllegalStateException(\"Entity may only be accessed once\");\n            }\n            hasRead = true;\n        }\n    }\n\n    private void checkValidSize() throws IllegalArgumentException {\n        Integer contentLength;\n        String lengthHeader = getHeader(\"Content-Length\");\n        if (lengthHeader != null) {\n            contentLength = Integer.parseInt(lengthHeader);\n            if (contentLength > maxEntitySize) {\n                throw new IllegalArgumentException(\"HTTP entity too large to be buffered in memory\");\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/atlassian/httpclient/apache/httpcomponents/DefaultRequest.java",
    "content": "package com.atlassian.httpclient.apache.httpcomponents;\n\nimport static com.atlassian.httpclient.api.Request.Method.DELETE;\nimport static com.atlassian.httpclient.api.Request.Method.GET;\nimport static com.atlassian.httpclient.api.Request.Method.HEAD;\nimport static com.atlassian.httpclient.api.Request.Method.OPTIONS;\nimport static com.atlassian.httpclient.api.Request.Method.POST;\nimport static com.atlassian.httpclient.api.Request.Method.PUT;\nimport static com.atlassian.httpclient.api.Request.Method.TRACE;\n\nimport com.atlassian.httpclient.api.EntityBuilder;\nimport com.atlassian.httpclient.api.HttpClient;\nimport com.atlassian.httpclient.api.Request;\nimport com.atlassian.httpclient.api.ResponsePromise;\nimport io.atlassian.fugue.Option;\nimport java.io.InputStream;\nimport java.net.URI;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Objects;\n\npublic class DefaultRequest extends DefaultMessage implements Request {\n    private final URI uri;\n    private final boolean cacheDisabled;\n    private final Map<String, String> attributes;\n    private final Method method;\n    private final Option<Long> contentLength;\n\n    private DefaultRequest(\n            URI uri,\n            boolean cacheDisabled,\n            Map<String, String> attributes,\n            Headers headers,\n            Method method,\n            InputStream entityStream,\n            Option<Long> contentLength) {\n        super(headers, entityStream, Option.none());\n        this.uri = uri;\n        this.cacheDisabled = cacheDisabled;\n        this.attributes = attributes;\n        this.method = method;\n        this.contentLength = contentLength;\n    }\n\n    public static DefaultRequestBuilder builder(HttpClient httpClient) {\n        return new DefaultRequestBuilder(httpClient);\n    }\n\n    @Override\n    public Method getMethod() {\n        return method;\n    }\n\n    @Override\n    public URI getUri() {\n        return uri;\n    }\n\n    @Override\n    public String getAccept() {\n        return super.getAccept();\n    }\n\n    @Override\n    public String getAttribute(String name) {\n        return attributes.get(name);\n    }\n\n    @Override\n    public Map<String, String> getAttributes() {\n        return Collections.unmodifiableMap(attributes);\n    }\n\n    @Override\n    public Option<Long> getContentLength() {\n        return contentLength;\n    }\n\n    @Override\n    public boolean isCacheDisabled() {\n        return cacheDisabled;\n    }\n\n    @Override\n    public Request validate() {\n        super.validate();\n\n        Objects.nonNull(uri);\n        Objects.nonNull(method);\n\n        switch (method) {\n            case GET:\n            case DELETE:\n            case HEAD:\n                if (hasEntity()) {\n                    throw new IllegalStateException(\"Request method \" + method + \" does not support an entity\");\n                }\n                break;\n            case POST:\n            case PUT:\n            case TRACE:\n                // no-op\n                break;\n        }\n        return this;\n    }\n\n    public static class DefaultRequestBuilder implements Request.Builder {\n        private final HttpClient httpClient;\n        private final Map<String, String> attributes;\n        private final CommonBuilder<DefaultRequest> commonBuilder;\n\n        private URI uri;\n        private boolean cacheDisabled;\n        private Method method;\n        private Option<Long> contentLength;\n\n        public DefaultRequestBuilder(final HttpClient httpClient) {\n            this.httpClient = httpClient;\n            this.attributes = new HashMap<>();\n            commonBuilder = new CommonBuilder<>();\n            setAccept(\"*/*\");\n            contentLength = Option.none();\n        }\n\n        @Override\n        public DefaultRequestBuilder setUri(final URI uri) {\n            this.uri = uri;\n            return this;\n        }\n\n        @Override\n        public DefaultRequestBuilder setAccept(final String accept) {\n            setHeader(\"Accept\", accept);\n            return this;\n        }\n\n        @Override\n        public DefaultRequestBuilder setCacheDisabled() {\n            this.cacheDisabled = true;\n            return this;\n        }\n\n        @Override\n        public DefaultRequestBuilder setAttribute(final String name, final String value) {\n            attributes.put(name, value);\n            return this;\n        }\n\n        @Override\n        public DefaultRequestBuilder setAttributes(final Map<String, String> properties) {\n            attributes.putAll(properties);\n            return this;\n        }\n\n        @Override\n        public DefaultRequestBuilder setEntity(final EntityBuilder entityBuilder) {\n            EntityBuilder.Entity entity = entityBuilder.build();\n            final Map<String, String> headers = entity.getHeaders();\n            for (Map.Entry<String, String> headerEntry : headers.entrySet()) {\n                setHeader(headerEntry.getKey(), headerEntry.getValue());\n            }\n            setEntityStream(entity.getInputStream());\n            return this;\n        }\n\n        @Override\n        public DefaultRequestBuilder setHeader(final String name, final String value) {\n            commonBuilder.setHeader(name, value);\n            return this;\n        }\n\n        @Override\n        public DefaultRequestBuilder setHeaders(final Map<String, String> headers) {\n            commonBuilder.setHeaders(headers);\n            return this;\n        }\n\n        @Override\n        public DefaultRequestBuilder setEntity(final String entity) {\n            commonBuilder.setEntity(entity);\n            setContentLength(entity.length());\n            return this;\n        }\n\n        @Override\n        public DefaultRequestBuilder setEntityStream(final InputStream entityStream) {\n            commonBuilder.setEntityStream(entityStream);\n            return this;\n        }\n\n        @Override\n        public DefaultRequestBuilder setContentCharset(final String contentCharset) {\n            commonBuilder.setContentCharset(contentCharset);\n            return this;\n        }\n\n        @Override\n        public DefaultRequestBuilder setContentType(final String contentType) {\n            commonBuilder.setContentType(contentType);\n            return this;\n        }\n\n        @Override\n        public DefaultRequestBuilder setEntityStream(final InputStream entityStream, final String charset) {\n            setEntityStream(entityStream);\n            commonBuilder.setContentCharset(charset);\n            return this;\n        }\n\n        @Override\n        public DefaultRequestBuilder setContentLength(final long contentLength) {\n            if (contentLength < 0) {\n                throw new IllegalArgumentException(\"Content length must be greater than or equal to 0\");\n            }\n            this.contentLength = Option.some(contentLength);\n            return this;\n        }\n\n        @Override\n        public DefaultRequest build() {\n            return new DefaultRequest(\n                    uri,\n                    cacheDisabled,\n                    attributes,\n                    commonBuilder.getHeaders(),\n                    method,\n                    commonBuilder.getEntityStream(),\n                    contentLength);\n        }\n\n        @Override\n        public ResponsePromise get() {\n            return execute(GET);\n        }\n\n        @Override\n        public ResponsePromise post() {\n            return execute(POST);\n        }\n\n        @Override\n        public ResponsePromise put() {\n            return execute(PUT);\n        }\n\n        @Override\n        public ResponsePromise delete() {\n            return execute(DELETE);\n        }\n\n        @Override\n        public ResponsePromise options() {\n            return execute(OPTIONS);\n        }\n\n        @Override\n        public ResponsePromise head() {\n            return execute(HEAD);\n        }\n\n        @Override\n        public ResponsePromise trace() {\n            return execute(TRACE);\n        }\n\n        @Override\n        public ResponsePromise execute(Method method) {\n            Objects.nonNull(method);\n            setMethod(method);\n            return httpClient.execute(build().validate());\n        }\n\n        public void setMethod(final Method method) {\n            this.method = method;\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/atlassian/httpclient/apache/httpcomponents/DefaultResponse.java",
    "content": "package com.atlassian.httpclient.apache.httpcomponents;\n\nimport com.atlassian.httpclient.api.Response;\nimport io.atlassian.fugue.Option;\nimport java.io.InputStream;\nimport java.util.Map;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\npublic final class DefaultResponse extends DefaultMessage implements Response {\n    private int statusCode;\n    private String statusText;\n    private Logger log = LoggerFactory.getLogger(DefaultResponse.class);\n\n    public DefaultResponse(\n            Headers headers, InputStream entityStream, Option<Long> maxEntitySize, int statusCode, String statusText) {\n        super(headers, entityStream, maxEntitySize);\n        this.statusCode = statusCode;\n        this.statusText = statusText;\n    }\n\n    public static DefaultResponseBuilder builder() {\n        return new DefaultResponseBuilder();\n    }\n\n    @Override\n    public int getStatusCode() {\n        return statusCode;\n    }\n\n    @Override\n    public String getStatusText() {\n        return statusText;\n    }\n\n    @Override\n    public boolean isInformational() {\n        return statusCode >= 100 && statusCode < 200;\n    }\n\n    @Override\n    public boolean isSuccessful() {\n        return statusCode >= 200 && statusCode < 300;\n    }\n\n    @Override\n    public boolean isOk() {\n        return statusCode == 200;\n    }\n\n    @Override\n    public boolean isCreated() {\n        return statusCode == 201;\n    }\n\n    @Override\n    public boolean isNoContent() {\n        return statusCode == 204;\n    }\n\n    @Override\n    public boolean isRedirection() {\n        return statusCode >= 300 && statusCode < 400;\n    }\n\n    @Override\n    public boolean isSeeOther() {\n        return statusCode == 303;\n    }\n\n    @Override\n    public boolean isNotModified() {\n        return statusCode == 304;\n    }\n\n    @Override\n    public boolean isClientError() {\n        return statusCode >= 400 && statusCode < 500;\n    }\n\n    @Override\n    public boolean isBadRequest() {\n        return statusCode == 400;\n    }\n\n    @Override\n    public boolean isUnauthorized() {\n        return statusCode == 401;\n    }\n\n    @Override\n    public boolean isForbidden() {\n        return statusCode == 403;\n    }\n\n    @Override\n    public boolean isNotFound() {\n        return statusCode == 404;\n    }\n\n    @Override\n    public boolean isConflict() {\n        return statusCode == 409;\n    }\n\n    @Override\n    public boolean isServerError() {\n        return statusCode >= 500 && statusCode < 600;\n    }\n\n    @Override\n    public boolean isInternalServerError() {\n        return statusCode == 500;\n    }\n\n    @Override\n    public boolean isServiceUnavailable() {\n        return statusCode == 503;\n    }\n\n    @Override\n    public boolean isError() {\n        return isClientError() || isServerError();\n    }\n\n    @Override\n    public boolean isNotSuccessful() {\n        return isInformational() || isRedirection() || isError();\n    }\n\n    @Override\n    public Option<Long> getContentLength() {\n        String lengthString = getHeader(Headers.Names.CONTENT_LENGTH);\n        if (lengthString != null) {\n            try {\n                Option<Long> parsedLength = Option.some(Long.parseLong(lengthString));\n                return parsedLength.flatMap(aLong -> {\n                    if (aLong < 0) {\n                        log.warn(\"Unable to parse content length. Received out of range value {}\", aLong);\n                        return Option.none();\n                    } else {\n                        return Option.some(aLong);\n                    }\n                });\n            } catch (NumberFormatException e) {\n                log.warn(\"Unable to parse content length {}\", lengthString);\n                return Option.none();\n            }\n        } else {\n            return Option.none();\n        }\n    }\n\n    public static class DefaultResponseBuilder implements Builder {\n        private final CommonBuilder<DefaultResponse> commonBuilder;\n\n        private String statusText;\n        private int statusCode;\n        private long maxEntitySize;\n\n        private DefaultResponseBuilder() {\n            this.commonBuilder = new CommonBuilder<DefaultResponse>();\n        }\n\n        @Override\n        public DefaultResponseBuilder setContentType(final String contentType) {\n            commonBuilder.setContentType(contentType);\n            return this;\n        }\n\n        @Override\n        public DefaultResponseBuilder setContentCharset(final String contentCharset) {\n            commonBuilder.setContentCharset(contentCharset);\n            return this;\n        }\n\n        @Override\n        public DefaultResponseBuilder setHeaders(final Map<String, String> headers) {\n            commonBuilder.setHeaders(headers);\n            return this;\n        }\n\n        @Override\n        public DefaultResponseBuilder setHeader(final String name, final String value) {\n            commonBuilder.setHeader(name, value);\n            return this;\n        }\n\n        @Override\n        public DefaultResponseBuilder setEntity(final String entity) {\n            commonBuilder.setEntity(entity);\n            return this;\n        }\n\n        @Override\n        public DefaultResponseBuilder setEntityStream(final InputStream entityStream, final String encoding) {\n            commonBuilder.setEntityStream(entityStream);\n            commonBuilder.setContentCharset(encoding);\n            return this;\n        }\n\n        @Override\n        public DefaultResponseBuilder setEntityStream(final InputStream entityStream) {\n            commonBuilder.setEntityStream(entityStream);\n            return this;\n        }\n\n        @Override\n        public DefaultResponseBuilder setStatusText(final String statusText) {\n            this.statusText = statusText;\n            return this;\n        }\n\n        @Override\n        public DefaultResponseBuilder setStatusCode(final int statusCode) {\n            this.statusCode = statusCode;\n            return this;\n        }\n\n        public DefaultResponseBuilder setMaxEntitySize(long maxEntitySize) {\n            this.maxEntitySize = maxEntitySize;\n            return this;\n        }\n\n        @Override\n        public DefaultResponse build() {\n            return new DefaultResponse(\n                    commonBuilder.getHeaders(),\n                    commonBuilder.getEntityStream(),\n                    Option.option(maxEntitySize),\n                    statusCode,\n                    statusText);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/atlassian/httpclient/apache/httpcomponents/EntityByteArrayInputStream.java",
    "content": "package com.atlassian.httpclient.apache.httpcomponents;\n\nimport java.io.ByteArrayInputStream;\n\npublic class EntityByteArrayInputStream extends ByteArrayInputStream {\n    private byte[] bytes;\n\n    public EntityByteArrayInputStream(byte[] bytes) {\n        super(bytes);\n        this.bytes = bytes;\n    }\n\n    public byte[] getBytes() {\n        return bytes;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/atlassian/httpclient/apache/httpcomponents/Headers.java",
    "content": "package com.atlassian.httpclient.apache.httpcomponents;\n\nimport com.atlassian.httpclient.api.Buildable;\nimport java.nio.charset.Charset;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\nimport org.apache.http.protocol.HTTP;\n\npublic class Headers {\n    private final Map<String, String> headers;\n    private final String contentCharset;\n    private final String contentType;\n\n    private Headers(Map<String, String> headers, String contentCharset, String contentType) {\n        this.headers = headers;\n        this.contentCharset = contentCharset;\n        this.contentType = contentType;\n    }\n\n    public String getContentCharset() {\n        return contentCharset;\n    }\n\n    public String getContentType() {\n        return contentType;\n    }\n\n    public Map<String, String> getHeaders() {\n        Map<String, String> headers = new HashMap<>(this.headers);\n        if (contentType != null) {\n            headers.put(Names.CONTENT_TYPE, buildContentType());\n        }\n        return Collections.unmodifiableMap(headers);\n    }\n\n    public String getHeader(final String name) {\n        String value;\n        if (name.equalsIgnoreCase(Names.CONTENT_TYPE)) {\n            value = buildContentType();\n        } else {\n            value = headers.get(name);\n        }\n        return value;\n    }\n\n    private String buildContentType() {\n        String value = contentType != null ? contentType : \"text/plain\";\n        if (contentCharset != null) {\n            value += \"; charset=\" + contentCharset;\n        }\n        return value;\n    }\n\n    public static class Builder implements Buildable<Headers> {\n        private final Map<String, String> headers = new HashMap<>();\n        private String contentType;\n        private String contentCharset;\n\n        public Builder setHeaders(Map<String, String> headers) {\n            this.headers.clear();\n            for (Map.Entry<String, String> entry : headers.entrySet()) {\n                setHeader(entry.getKey(), entry.getValue());\n            }\n            return this;\n        }\n\n        public Builder setHeader(String name, String value) {\n            if (name.equalsIgnoreCase(\"Content-Type\")) {\n                parseContentType(value);\n            } else {\n                headers.put(name, value);\n            }\n            return this;\n        }\n\n        public Builder setContentLength(long contentLength) {\n            if (contentLength < 0) {\n                throw new IllegalArgumentException(\"Content-Length must be greater than or equal to 0\");\n            }\n            setHeader(Names.CONTENT_LENGTH, Long.toString(contentLength));\n            return this;\n        }\n\n        public Builder setContentCharset(String contentCharset) {\n            this.contentCharset =\n                    contentCharset != null ? Charset.forName(contentCharset).name() : null;\n            return this;\n        }\n\n        public Builder setContentType(String contentType) {\n            parseContentType(contentType);\n            return this;\n        }\n\n        private void parseContentType(String value) {\n            if (value != null) {\n                String[] parts = value.split(\";\");\n                if (parts.length >= 1) {\n                    contentType = parts[0].trim();\n                }\n                if (parts.length >= 2) {\n                    String subtype = parts[1].trim();\n                    if (subtype.startsWith(\"charset=\")) {\n                        setContentCharset(subtype.substring(8));\n                    } else if (subtype.startsWith(\"boundary=\")) {\n                        contentType = contentType.concat(';' + subtype);\n                    }\n                }\n            } else {\n                contentType = null;\n            }\n        }\n\n        @Override\n        public Headers build() {\n            return new Headers(headers, contentCharset, contentType);\n        }\n    }\n\n    public static class Names {\n        public static final String CONTENT_LENGTH = HTTP.CONTENT_LEN;\n        public static final String CONTENT_TYPE = HTTP.CONTENT_TYPE;\n\n        private Names() {}\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/atlassian/httpclient/apache/httpcomponents/MavenUtils.java",
    "content": "package com.atlassian.httpclient.apache.httpcomponents;\n\nimport static java.lang.String.format;\n\nimport java.io.InputStream;\nimport java.util.Properties;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nfinal class MavenUtils {\n    private static final Logger logger = LoggerFactory.getLogger(MavenUtils.class);\n\n    private static final String UNKNOWN_VERSION = \"unknown\";\n\n    static String getVersion(String groupId, String artifactId) {\n        final Properties props = new Properties();\n\n        try (InputStream is = Thread.currentThread()\n                .getContextClassLoader()\n                .getResourceAsStream(getPomFilePath(groupId, artifactId))) {\n            props.load(is);\n            return props.getProperty(\"version\", UNKNOWN_VERSION);\n        } catch (Exception e) {\n            logger.debug(\"Could not find version for maven artifact {}:{}\", groupId, artifactId);\n            logger.debug(\"Got the following exception:\", e);\n            return UNKNOWN_VERSION;\n        }\n    }\n\n    private static String getPomFilePath(String groupId, String artifactId) {\n        return format(\"META-INF/maven/%s/%s/pom.properties\", groupId, artifactId);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/atlassian/httpclient/apache/httpcomponents/PromiseHttpAsyncClient.java",
    "content": "package com.atlassian.httpclient.apache.httpcomponents;\n\nimport io.atlassian.util.concurrent.Promise;\nimport org.apache.http.HttpResponse;\nimport org.apache.http.client.methods.HttpUriRequest;\nimport org.apache.http.protocol.HttpContext;\n\ninterface PromiseHttpAsyncClient {\n    Promise<HttpResponse> execute(HttpUriRequest request, HttpContext context);\n}\n"
  },
  {
    "path": "src/main/java/com/atlassian/httpclient/apache/httpcomponents/RedirectStrategy.java",
    "content": "package com.atlassian.httpclient.apache.httpcomponents;\n\nimport java.net.URI;\nimport org.apache.http.HttpEntityEnclosingRequest;\nimport org.apache.http.HttpRequest;\nimport org.apache.http.HttpResponse;\nimport org.apache.http.ProtocolException;\nimport org.apache.http.client.methods.HttpDelete;\nimport org.apache.http.client.methods.HttpGet;\nimport org.apache.http.client.methods.HttpHead;\nimport org.apache.http.client.methods.HttpPatch;\nimport org.apache.http.client.methods.HttpPost;\nimport org.apache.http.client.methods.HttpPut;\nimport org.apache.http.client.methods.HttpUriRequest;\nimport org.apache.http.impl.client.DefaultRedirectStrategy;\nimport org.apache.http.protocol.HttpContext;\n\npublic class RedirectStrategy extends DefaultRedirectStrategy {\n    final String[] REDIRECT_METHODS = {\n        HttpHead.METHOD_NAME,\n        HttpGet.METHOD_NAME,\n        HttpPost.METHOD_NAME,\n        HttpPut.METHOD_NAME,\n        HttpDelete.METHOD_NAME,\n        HttpPatch.METHOD_NAME\n    };\n\n    @Override\n    public boolean isRedirectable(String method) {\n        for (String m : REDIRECT_METHODS) {\n            if (m.equalsIgnoreCase(method)) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    @Override\n    public HttpUriRequest getRedirect(final HttpRequest request, final HttpResponse response, final HttpContext context)\n            throws ProtocolException {\n        URI uri = getLocationURI(request, response, context);\n        String method = request.getRequestLine().getMethod();\n        if (method.equalsIgnoreCase(HttpHead.METHOD_NAME)) {\n            return new HttpHead(uri);\n        } else if (method.equalsIgnoreCase(HttpGet.METHOD_NAME)) {\n            return new HttpGet(uri);\n        } else if (method.equalsIgnoreCase(HttpPost.METHOD_NAME)) {\n            final HttpPost post = new HttpPost(uri);\n            if (request instanceof HttpEntityEnclosingRequest) {\n                post.setEntity(((HttpEntityEnclosingRequest) request).getEntity());\n            }\n            return post;\n        } else if (method.equalsIgnoreCase(HttpPut.METHOD_NAME)) {\n            return new HttpPut(uri);\n        } else if (method.equalsIgnoreCase(HttpDelete.METHOD_NAME)) {\n            return new HttpDelete(uri);\n        } else if (method.equalsIgnoreCase(HttpPatch.METHOD_NAME)) {\n            return new HttpPatch(uri);\n        } else {\n            return new HttpGet(uri);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/atlassian/httpclient/apache/httpcomponents/RequestEntityEffect.java",
    "content": "package com.atlassian.httpclient.apache.httpcomponents;\n\nimport com.atlassian.httpclient.api.Request;\nimport io.atlassian.fugue.Effect;\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport org.apache.commons.io.IOUtils;\nimport org.apache.http.HttpEntity;\nimport org.apache.http.client.methods.HttpEntityEnclosingRequestBase;\nimport org.apache.http.client.methods.HttpRequestBase;\nimport org.apache.http.entity.ByteArrayEntity;\nimport org.apache.http.entity.InputStreamEntity;\n\npublic class RequestEntityEffect implements Effect<HttpRequestBase> {\n    private final Request request;\n\n    public RequestEntityEffect(final Request request) {\n        this.request = request;\n    }\n\n    @Override\n    public void apply(final HttpRequestBase httpRequestBase) {\n        if (httpRequestBase instanceof HttpEntityEnclosingRequestBase) {\n            ((HttpEntityEnclosingRequestBase) httpRequestBase).setEntity(getHttpEntity(request));\n        } else {\n            throw new UnsupportedOperationException(\n                    \"HTTP method \" + request.getMethod() + \" does not support sending an entity\");\n        }\n    }\n\n    private HttpEntity getHttpEntity(final Request request) {\n        HttpEntity entity = null;\n        if (request.hasEntity()) {\n            InputStream entityStream = request.getEntityStream();\n            if (entityStream instanceof ByteArrayInputStream) {\n                byte[] bytes;\n                if (entityStream instanceof EntityByteArrayInputStream) {\n                    bytes = ((EntityByteArrayInputStream) entityStream).getBytes();\n                } else {\n                    try {\n                        bytes = IOUtils.toByteArray(entityStream);\n                    } catch (IOException e) {\n                        throw new RuntimeException(e);\n                    }\n                }\n                entity = new ByteArrayEntity(bytes);\n            } else {\n                long contentLength = request.getContentLength().getOrElse(-1L);\n                entity = new InputStreamEntity(entityStream, contentLength);\n            }\n        }\n        return entity;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/atlassian/httpclient/base/event/AbstractHttpRequestEvent.java",
    "content": "package com.atlassian.httpclient.base.event;\n\nimport java.util.Map;\n\nabstract class AbstractHttpRequestEvent {\n    private final String url;\n    private final String httpMethod;\n    private final long requestDuration;\n    private final Map<String, String> properties;\n\n    private int statusCode;\n    private String error;\n\n    public AbstractHttpRequestEvent(\n            String url, String httpMethod, int statusCode, long requestDuration, Map<String, String> properties) {\n        this.url = url;\n        this.httpMethod = httpMethod;\n        this.statusCode = statusCode;\n        this.requestDuration = requestDuration;\n        this.properties = properties;\n    }\n\n    public AbstractHttpRequestEvent(\n            String url, String httpMethod, String error, long requestDuration, Map<String, String> properties) {\n        this.url = url;\n        this.httpMethod = httpMethod;\n        this.error = error;\n        this.requestDuration = requestDuration;\n        this.properties = properties;\n    }\n\n    public String getUrl() {\n        return url;\n    }\n\n    public int getStatusCode() {\n        return statusCode;\n    }\n\n    public String getError() {\n        return error;\n    }\n\n    public long getRequestDuration() {\n        return requestDuration;\n    }\n\n    public Map<String, String> getProperties() {\n        return properties;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/atlassian/httpclient/base/event/HttpRequestCompletedEvent.java",
    "content": "package com.atlassian.httpclient.base.event;\n\nimport java.util.Map;\n\npublic final class HttpRequestCompletedEvent extends AbstractHttpRequestEvent {\n    public HttpRequestCompletedEvent(\n            String url, String httpMethod, int statusCode, long requestDuration, Map<String, String> properties) {\n        super(url, httpMethod, statusCode, requestDuration, properties);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/atlassian/httpclient/base/event/HttpRequestFailedEvent.java",
    "content": "package com.atlassian.httpclient.base.event;\n\nimport java.util.Map;\n\npublic final class HttpRequestFailedEvent extends AbstractHttpRequestEvent {\n    public HttpRequestFailedEvent(\n            String url, String httpMethod, int statusCode, long elapsed, Map<String, String> properties) {\n        super(url, httpMethod, statusCode, elapsed, properties);\n    }\n\n    public HttpRequestFailedEvent(\n            String url, String httpMethod, String error, long elapsed, Map<String, String> properties) {\n        super(url, httpMethod, error, elapsed, properties);\n    }\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/CredentialsHelper.java",
    "content": "package hudson.plugins.jira;\n\nimport com.cloudbees.plugins.credentials.CredentialsMatchers;\nimport com.cloudbees.plugins.credentials.CredentialsProvider;\nimport com.cloudbees.plugins.credentials.CredentialsScope;\nimport com.cloudbees.plugins.credentials.SystemCredentialsProvider;\nimport com.cloudbees.plugins.credentials.common.StandardCredentials;\nimport com.cloudbees.plugins.credentials.common.StandardListBoxModel;\nimport com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials;\nimport com.cloudbees.plugins.credentials.common.UsernamePasswordCredentials;\nimport com.cloudbees.plugins.credentials.domains.URIRequirementBuilder;\nimport com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl;\nimport edu.umd.cs.findbugs.annotations.CheckForNull;\nimport edu.umd.cs.findbugs.annotations.NonNull;\nimport hudson.model.Descriptor.FormException;\nimport hudson.model.Item;\nimport hudson.model.Queue;\nimport hudson.model.queue.Tasks;\nimport hudson.security.ACL;\nimport hudson.util.FormValidation;\nimport hudson.util.ListBoxModel;\nimport hudson.util.Secret;\nimport java.io.IOException;\nimport java.net.URL;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\nimport jenkins.model.Jenkins;\nimport org.apache.commons.lang3.StringUtils;\n\n/**\n * Helper class for vary credentials operations.\n *\n * @author Zhenlei Huang\n */\npublic class CredentialsHelper {\n    private static final Logger LOGGER = Logger.getLogger(CredentialsHelper.class.getName());\n\n    @CheckForNull\n    protected static StandardUsernamePasswordCredentials lookupSystemCredentials(\n            @CheckForNull String credentialsId, @CheckForNull URL url) {\n        if (credentialsId == null) {\n            return null;\n        }\n        return CredentialsMatchers.firstOrNull(\n                CredentialsProvider.lookupCredentials(\n                        StandardUsernamePasswordCredentials.class,\n                        Jenkins.get(),\n                        ACL.SYSTEM,\n                        URIRequirementBuilder.fromUri(url != null ? url.toExternalForm() : null)\n                                .build()),\n                CredentialsMatchers.withId(credentialsId));\n    }\n\n    protected static StandardUsernamePasswordCredentials migrateCredentials(\n            @NonNull String username, String password, @CheckForNull URL url) throws FormException {\n        List<StandardUsernamePasswordCredentials> credentials = CredentialsMatchers.filter(\n                CredentialsProvider.lookupCredentials(\n                        StandardUsernamePasswordCredentials.class,\n                        Jenkins.get(),\n                        ACL.SYSTEM,\n                        URIRequirementBuilder.fromUri(url != null ? url.toExternalForm() : null)\n                                .build()),\n                CredentialsMatchers.withUsername(username));\n        for (StandardUsernamePasswordCredentials c : credentials) {\n            if (StringUtils.equals(password, Secret.toString(c.getPassword()))) {\n                return c; // found\n            }\n        }\n\n        // Create new credentials with the principal and secret if we couldn't find any existing credentials\n        StandardUsernamePasswordCredentials newCredentials = new UsernamePasswordCredentialsImpl(\n                CredentialsScope.SYSTEM, null, \"Migrated by Jira Plugin\", username, password);\n        SystemCredentialsProvider.getInstance().getCredentials().add(newCredentials);\n        try {\n            SystemCredentialsProvider.getInstance().save();\n            LOGGER.log(\n                    Level.INFO,\n                    \"Provided username and password were successfully migrated and stored as {0}\",\n                    newCredentials.getId());\n        } catch (IOException e) {\n            LOGGER.log(Level.WARNING, \"Unable to store migrated credentials\", e);\n        }\n\n        return newCredentials;\n    }\n\n    protected static ListBoxModel doFillCredentialsIdItems(Item item, String credentialsId, String uri) {\n        StandardListBoxModel result = new StandardListBoxModel();\n        if (item == null) {\n            if (!Jenkins.get().hasPermission(Jenkins.ADMINISTER)) {\n                return result.includeCurrentValue(credentialsId);\n            }\n        } else {\n            if (!item.hasPermission(Item.EXTENDED_READ) && !item.hasPermission(CredentialsProvider.USE_ITEM)) {\n                return result.includeCurrentValue(credentialsId);\n            }\n        }\n        return result.includeEmptyValue()\n                .includeMatchingAs(\n                        item instanceof Queue.Task ? Tasks.getAuthenticationOf((Queue.Task) item) : ACL.SYSTEM,\n                        item,\n                        StandardCredentials.class,\n                        URIRequirementBuilder.fromUri(uri).build(),\n                        CredentialsMatchers.anyOf(\n                                CredentialsMatchers.instanceOf(StandardUsernamePasswordCredentials.class),\n                                CredentialsMatchers.instanceOf(UsernamePasswordCredentials.class)))\n                .includeCurrentValue(credentialsId);\n    }\n\n    protected static FormValidation doCheckFillCredentialsId(Item item, String credentialsId, String uri) {\n        if (item == null) {\n            if (!Jenkins.get().hasPermission(Jenkins.ADMINISTER)) {\n                return FormValidation.ok();\n            }\n        } else {\n            if (!item.hasPermission(Item.EXTENDED_READ) && !item.hasPermission(CredentialsProvider.USE_ITEM)) {\n                return FormValidation.ok();\n            }\n        }\n        if (StringUtils.isEmpty(credentialsId)) {\n            return FormValidation.ok();\n        }\n        if (!(findCredentials(item, credentialsId, uri).isPresent())) {\n            return FormValidation.error(\"Cannot find currently selected credentials\");\n        }\n        return FormValidation.ok();\n    }\n\n    protected static Optional<StandardUsernamePasswordCredentials> findCredentials(\n            Item item, String credentialsId, String uri) {\n        return Optional.ofNullable(CredentialsMatchers.firstOrNull(\n                CredentialsProvider.lookupCredentials(\n                        StandardUsernamePasswordCredentials.class,\n                        item,\n                        item instanceof Queue.Task ? Tasks.getAuthenticationOf((Queue.Task) item) : ACL.SYSTEM,\n                        URIRequirementBuilder.fromUri(uri).build()),\n                CredentialsMatchers.withId(credentialsId)));\n    }\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/EmptyFriendlyURLConverter.java",
    "content": "package hudson.plugins.jira;\n\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\nimport org.apache.commons.beanutils.Converter;\nimport org.kohsuke.accmod.Restricted;\nimport org.kohsuke.accmod.restrictions.NoExternalUse;\n\n/**\n * It's little hackish.\n */\n@Restricted(NoExternalUse.class)\npublic class EmptyFriendlyURLConverter implements Converter {\n    private static final Logger LOGGER = Logger.getLogger(JiraProjectProperty.class.getName());\n\n    @Override\n    public Object convert(Class aClass, Object o) {\n        if (o == null || \"\".equals(o) || \"null\".equals(o)) {\n            return null;\n        }\n        try {\n            return new URL(o.toString());\n        } catch (MalformedURLException e) {\n            LOGGER.log(Level.WARNING, \"{0} is not a valid URL\", o);\n            return null;\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/EnvironmentExpander.java",
    "content": "package hudson.plugins.jira;\n\nimport hudson.EnvVars;\nimport hudson.model.Run;\nimport hudson.model.TaskListener;\nimport java.io.IOException;\n\npublic class EnvironmentExpander {\n    public static EnvVars getEnvVars(Run<?, ?> run, TaskListener listener) {\n        if (run == null || listener == null) {\n            return null;\n        }\n\n        try {\n            return run.getEnvironment(listener);\n        } catch (IOException | InterruptedException e) {\n            return null;\n        }\n    }\n\n    public static String expandVariable(String variable, Run<?, ?> run, TaskListener listener) {\n        EnvVars envVars = getEnvVars(run, listener);\n\n        return expandVariable(variable, envVars);\n    }\n\n    public static String expandVariable(String variable, EnvVars envVars) {\n        if (envVars == null) {\n            return variable;\n        }\n\n        return envVars.expand(variable);\n    }\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/JiraBuildAction.java",
    "content": "package hudson.plugins.jira;\n\nimport edu.umd.cs.findbugs.annotations.NonNull;\nimport hudson.model.Run;\nimport hudson.plugins.jira.model.JiraIssue;\nimport java.net.URL;\nimport java.util.HashSet;\nimport java.util.Set;\nimport jenkins.model.RunAction2;\nimport org.kohsuke.stapler.export.Exported;\nimport org.kohsuke.stapler.export.ExportedBean;\n\n/**\n * Jira issues related to the build.\n *\n * @author Kohsuke Kawaguchi\n */\n@ExportedBean\npublic class JiraBuildAction implements RunAction2 {\n\n    private final HashSet<JiraIssue> issues;\n    private transient Run<?, ?> owner;\n\n    public JiraBuildAction(@NonNull Set<JiraIssue> issues) {\n        this.issues = new HashSet<>(issues);\n    }\n\n    // Leave it in place for binary compatibility.\n    /**\n     * @param owner the owner of this action\n     * @param issues the Jira issues\n     *\n     * @deprecated use {@link #JiraBuildAction(java.util.Set)} instead\n     */\n    @Deprecated\n    public JiraBuildAction(Run<?, ?> owner, @NonNull Set<JiraIssue> issues) {\n        this(issues);\n        // the owner will be set by #onAttached(hudson.model.Run)\n    }\n\n    @Override\n    public void onAttached(Run<?, ?> r) {\n        this.owner = r;\n    }\n\n    @Override\n    public void onLoad(Run<?, ?> r) {\n        this.owner = r;\n    }\n\n    @Override\n    public String getIconFileName() {\n        return null;\n    }\n\n    @Override\n    public String getDisplayName() {\n        return Messages.JiraBuildAction_DisplayName();\n    }\n\n    @Override\n    public String getUrlName() {\n        return \"jira\";\n    }\n\n    public Run<?, ?> getOwner() {\n        return owner;\n    }\n\n    @Exported(inline = true)\n    public Set<JiraIssue> getIssues() {\n        return issues;\n    }\n\n    @Exported\n    public String getServerURL() {\n        JiraSite jiraSite = JiraSite.get(owner.getParent());\n        URL url = jiraSite != null ? jiraSite.getUrl() : null;\n        return url != null ? url.toString() : null;\n    }\n\n    /**\n     * Finds {@link JiraIssue} whose ID matches the given one.\n     *\n     * @param issueID e.g. JENKINS-1234\n     * @return JIRAIssue representing the issueID\n     */\n    public JiraIssue getIssue(String issueID) {\n        for (JiraIssue issue : issues) {\n            if (issue.getKey().equals(issueID)) {\n                return issue;\n            }\n        }\n        return null;\n    }\n\n    public void addIssues(Set<JiraIssue> issuesToBeSaved) {\n        this.issues.addAll(issuesToBeSaved);\n    }\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/JiraCarryOverAction.java",
    "content": "package hudson.plugins.jira;\n\nimport hudson.Util;\nimport hudson.model.InvisibleAction;\nimport hudson.plugins.jira.model.JiraIssue;\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.Set;\nimport org.kohsuke.stapler.export.Exported;\nimport org.kohsuke.stapler.export.ExportedBean;\n\n/**\n * Remembers Jira IDs that need to be updated later,\n * when we get a successful build.\n *\n * @author Kohsuke Kawaguchi\n */\n@ExportedBean\npublic class JiraCarryOverAction extends InvisibleAction {\n    /**\n     * ','-separate IDs, for compact persistence.\n     */\n    private final String ids;\n\n    public JiraCarryOverAction(Set<JiraIssue> issues) {\n        StringBuilder buf = new StringBuilder();\n        boolean first = true;\n        for (JiraIssue issue : issues) {\n            if (first) {\n                first = false;\n            } else {\n                buf.append(\",\");\n            }\n            buf.append(issue.getKey());\n        }\n        this.ids = buf.toString();\n    }\n\n    @Exported\n    public Collection<String> getIDs() {\n        return Arrays.asList(Util.tokenize(ids, \",\"));\n    }\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/JiraChangeLogAnnotator.java",
    "content": "package hudson.plugins.jira;\n\nimport hudson.Extension;\nimport hudson.MarkupText;\nimport hudson.Util;\nimport hudson.model.Job;\nimport hudson.model.Run;\nimport hudson.plugins.jira.model.JiraIssue;\nimport hudson.scm.ChangeLogAnnotator;\nimport hudson.scm.ChangeLogSet.Entry;\nimport java.io.IOException;\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport java.util.LinkedHashSet;\nimport java.util.Set;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\nimport org.apache.commons.lang3.StringUtils;\n\n/**\n * {@link ChangeLogAnnotator} that picks up Jira issue IDs.\n *\n * @author Kohsuke Kawaguchi\n */\n@Extension\npublic class JiraChangeLogAnnotator extends ChangeLogAnnotator {\n\n    private static final Logger LOGGER = Logger.getLogger(JiraChangeLogAnnotator.class.getName());\n\n    public JiraChangeLogAnnotator() {\n        LOGGER.fine(\"JiraChangeLogAnnotator created\");\n    }\n\n    @Override\n    public void annotate(Run<?, ?> run, Entry change, MarkupText text) {\n        JiraSite site = getSiteForProject(run.getParent());\n\n        if (site == null) {\n            LOGGER.fine(\"not configured with Jira site\");\n            return; // not configured with Jira\n        }\n\n        if (site.getDisableChangelogAnnotations()) {\n            LOGGER.info(\"ChangeLog annotations are disabled.\\n Due to this also Related Issues won't be visible.\");\n            return;\n        }\n\n        LOGGER.log(Level.FINE, \"Using site: {0}\", site.getUrl());\n\n        // if there's any recorded detail information, try to use that, too.\n        JiraBuildAction a = run.getAction(JiraBuildAction.class);\n\n        Set<JiraIssue> issuesToBeSaved = new LinkedHashSet<>();\n\n        Pattern pattern = site.getIssuePattern();\n\n        if (LOGGER.isLoggable(Level.FINE)) {\n            LOGGER.fine(\"Using issue pattern: \" + pattern);\n        }\n\n        String plainText = text.getText();\n\n        Matcher m = pattern.matcher(plainText);\n\n        while (m.find()) {\n            if (m.groupCount() >= 1) {\n\n                String id = m.group(1);\n\n                if (StringUtils.isNotBlank(site.credentialsId) && !hasProjectForIssue(id, site, run)) {\n                    LOGGER.log(Level.INFO, \"No known Jira project corresponding to id: ''{0}''\", id);\n                    continue;\n                }\n\n                LOGGER.log(Level.FINE, \"Annotating Jira id: ''{0}''\", id);\n\n                URL url, alternativeUrl;\n                try {\n                    url = site.getUrl(id);\n                } catch (MalformedURLException e) {\n                    throw new AssertionError(e); // impossible\n                }\n\n                try {\n                    alternativeUrl = site.getAlternativeUrl(id);\n                    if (alternativeUrl != null) {\n                        url = alternativeUrl;\n                    }\n                } catch (MalformedURLException e) {\n                    LOGGER.log(Level.WARNING, \"Failed to construct alternative URL for Jira link. \" + e.getMessage());\n                    // This should not fail, since we already have an URL object. Exceptions would happen elsewhere.\n                    throw new AssertionError(e);\n                }\n\n                JiraIssue issue = null;\n                if (a != null) {\n                    issue = a.getIssue(id);\n                }\n\n                if (issue == null) {\n                    try {\n                        issue = site.getIssue(id);\n                        if (issue != null) {\n                            issuesToBeSaved.add(issue);\n                        }\n                    } catch (Exception e) {\n                        LOGGER.log(Level.FINE, \"Error getting remote issue \" + id, e);\n                    }\n                }\n\n                if (issue == null) {\n                    text.addMarkup(m.start(1), m.end(1), \"<a href='\" + url + \"'>\", \"</a>\");\n                } else {\n                    text.addMarkup(\n                            m.start(1),\n                            m.end(1),\n                            String.format(\"<a href='%s' tooltip='%s'>\", url, Util.escape(issue.getSummary())),\n                            \"</a>\");\n                }\n\n            } else {\n                LOGGER.log(Level.WARNING, \"The Jira pattern ''{0}'' doesn't define a capturing group!\", pattern);\n            }\n        }\n\n        if (!issuesToBeSaved.isEmpty()) {\n            saveIssues(run, a, issuesToBeSaved);\n        }\n    }\n\n    /**\n     * Checks if the given Jira id will be likely to exist in this issue tracker.\n     * This method checks whether the key portion is a valid key (except that\n     * it can potentially use stale data). Number portion is not checked at all.\n     *\n     * @param id String like MNG-1234\n     */\n    protected boolean hasProjectForIssue(String id, JiraSite site, Run run) {\n        int idx = id.indexOf('-');\n        if (idx == -1) {\n            return false;\n        }\n\n        Set<String> keys = site.getProjectKeys(run.getParent());\n        return keys.contains(id.substring(0, idx).toUpperCase());\n    }\n\n    private void saveIssues(Run<?, ?> build, JiraBuildAction a, Set<JiraIssue> issuesToBeSaved) {\n        if (a != null) {\n            a.addIssues(issuesToBeSaved);\n        } else {\n            JiraBuildAction action = new JiraBuildAction(issuesToBeSaved);\n            build.addAction(action);\n        }\n\n        try {\n            build.save();\n        } catch (final IOException e) {\n            LOGGER.log(Level.WARNING, \"Error saving updated build\", e);\n        }\n    }\n\n    JiraSite getSiteForProject(Job<?, ?> project) {\n        return JiraSite.get(project);\n    }\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/JiraCreateIssueNotifier.java",
    "content": "package hudson.plugins.jira;\n\nimport static hudson.plugins.jira.JiraRestService.BUG_ISSUE_TYPE_ID;\n\nimport com.atlassian.jira.rest.client.api.RestClientException;\nimport com.atlassian.jira.rest.client.api.StatusCategory;\nimport com.atlassian.jira.rest.client.api.domain.Issue;\nimport com.atlassian.jira.rest.client.api.domain.IssueType;\nimport com.atlassian.jira.rest.client.api.domain.Priority;\nimport com.atlassian.jira.rest.client.api.domain.Status;\nimport hudson.EnvVars;\nimport hudson.Extension;\nimport hudson.Launcher;\nimport hudson.model.AbstractBuild;\nimport hudson.model.AbstractProject;\nimport hudson.model.BuildListener;\nimport hudson.model.Item;\nimport hudson.model.Result;\nimport hudson.model.TaskListener;\nimport hudson.tasks.BuildStepDescriptor;\nimport hudson.tasks.BuildStepMonitor;\nimport hudson.tasks.Notifier;\nimport hudson.tasks.Publisher;\nimport hudson.util.FormValidation;\nimport hudson.util.ListBoxModel;\nimport java.io.BufferedReader;\nimport java.io.BufferedWriter;\nimport java.io.File;\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.logging.Logger;\nimport java.util.stream.Collectors;\nimport net.sf.json.JSONObject;\nimport org.apache.commons.lang3.StringUtils;\nimport org.kohsuke.stapler.AncestorInPath;\nimport org.kohsuke.stapler.DataBoundConstructor;\nimport org.kohsuke.stapler.QueryParameter;\nimport org.kohsuke.stapler.StaplerRequest2;\n\n/**\n * When a build fails it creates jira issues.\n * Repeated failures does not create a new issue but update the existing issue\n * until the issue is closed.\n *\n * @author Rupali Behera rupali@vertisinfotech.com\n */\npublic class JiraCreateIssueNotifier extends Notifier {\n\n    private static final Logger LOG = Logger.getLogger(JiraCreateIssueNotifier.class.getName());\n\n    private String projectKey;\n    private String testDescription;\n    private String assignee;\n    private String component;\n    private Long typeId;\n    private Long priorityId;\n    private Integer actionIdOnSuccess;\n\n    enum finishedStatuses {\n        Closed,\n        Done,\n        Resolved\n    }\n\n    @DataBoundConstructor\n    public JiraCreateIssueNotifier(\n            String projectKey,\n            String testDescription,\n            String assignee,\n            String component,\n            Long typeId,\n            Long priorityId,\n            Integer actionIdOnSuccess) {\n        if (projectKey == null) {\n            throw new IllegalArgumentException(\"Project key cannot be null\");\n        }\n        this.projectKey = projectKey;\n\n        this.testDescription = testDescription;\n        this.assignee = assignee;\n        this.component = component;\n        this.typeId = typeId;\n        this.priorityId = priorityId;\n        this.actionIdOnSuccess = actionIdOnSuccess;\n    }\n\n    public String getProjectKey() {\n        return projectKey;\n    }\n\n    public void setProjectKey(String projectKey) {\n        this.projectKey = projectKey;\n    }\n\n    public String getTestDescription() {\n        return testDescription;\n    }\n\n    public void setTestDescription(String testDescription) {\n        this.testDescription = testDescription;\n    }\n\n    public String getAssignee() {\n        return assignee;\n    }\n\n    public void setAssignee(String assignee) {\n        this.assignee = assignee;\n    }\n\n    public String getComponent() {\n        return component;\n    }\n\n    public void setComponent(String component) {\n        this.component = component;\n    }\n\n    public Long getTypeId() {\n        return typeId;\n    }\n\n    public Long getPriorityId() {\n        return priorityId;\n    }\n\n    public Integer getActionIdOnSuccess() {\n        return actionIdOnSuccess;\n    }\n\n    @Override\n    public BuildStepDescriptor<Publisher> getDescriptor() {\n        return DESCRIPTOR;\n    }\n\n    @Extension\n    public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl();\n\n    @Override\n    public BuildStepMonitor getRequiredMonitorService() {\n        return BuildStepMonitor.NONE;\n    }\n\n    @Override\n    public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener)\n            throws IOException, InterruptedException {\n\n        String jobDirPath = build.getProject().getBuildDir().getPath();\n        String filename = jobDirPath + File.separator + \"issue.txt\";\n\n        EnvVars vars = build.getEnvironment(TaskListener.NULL);\n\n        Result currentBuildResult = build.getResult();\n\n        Result previousBuildResult = null;\n        AbstractBuild<?, ?> previousBuild = build.getPreviousCompletedBuild();\n\n        if (previousBuild != null) {\n            previousBuildResult = previousBuild.getResult();\n        }\n\n        if (currentBuildResult != Result.ABORTED && previousBuild != null) {\n            try {\n                if (currentBuildResult == Result.FAILURE) {\n                    currentBuildResultFailure(build, listener, previousBuildResult, filename, vars);\n                }\n\n                if (currentBuildResult == Result.SUCCESS) {\n                    currentBuildResultSuccess(build, listener, previousBuildResult, filename, vars);\n                }\n            } catch (RestClientException e) {\n                listener.getLogger().println(e.getMessage());\n            }\n        }\n        return true;\n    }\n\n    /**\n     * It creates a issue in the given project, with the given description,\n     * assignee,components and summary.\n     * The created issue ID is saved to the file at \"filename\".\n     *\n     * @param build build\n     * @param filename filename\n     * @return issue id\n     * @throws IOException if IO fails\n     * @throws InterruptedException if thread is interrupted\n     */\n    private Issue createJiraIssue(AbstractBuild<?, ?> build, String filename) throws IOException, InterruptedException {\n\n        EnvVars vars = build.getEnvironment(TaskListener.NULL);\n        JiraSession session = getJiraSession(build);\n\n        String buildName = getBuildName(vars);\n        String summary = String.format(\"Build %s failed\", buildName);\n        String description = String.format(\n                \"%s%n%nThe build %s has failed.%nFirst failed run: %s\",\n                (this.testDescription.equals(\"\")) ? \"No description is provided\" : vars.expand(this.testDescription),\n                buildName,\n                getBuildDetailsString(vars));\n        Iterable<String> components = Arrays.stream(component.split(\",\"))\n                .filter(s -> !StringUtils.isEmpty(s))\n                .map(StringUtils::trim)\n                .collect(Collectors.toList());\n\n        Long type = typeId;\n        if (type == null || type == 0) { // zero is default / invalid selection\n            LOG.info(\"Returning default issue type id \" + BUG_ISSUE_TYPE_ID);\n            type = BUG_ISSUE_TYPE_ID;\n        }\n        Long priority = priorityId;\n        if (priority != null && priority == 0) {\n            priority = null; // remove invalid priority selection\n        }\n\n        Issue issue = session.createIssue(projectKey, description, assignee, components, summary, type, priority);\n\n        writeInFile(filename, issue);\n        return issue;\n    }\n\n    /**\n     * Returns the status of the issue.\n     *\n     * @param build build\n     * @param id issue key\n     * @return Status of the issue\n     * @throws IOException if IO fails\n     */\n    private Status getStatus(AbstractBuild<?, ?> build, String id) throws IOException {\n\n        JiraSession session = getJiraSession(build);\n        Issue issue = session.getIssueByKey(id);\n        return issue.getStatus();\n    }\n\n    /**\n     * Adds a comment to the existing issue.\n     *\n     * @param build build\n     * @param listener listener\n     * @param id issue key\n     * @param comment comment text\n     * @throws IOException if IO fails\n     */\n    private void addComment(AbstractBuild<?, ?> build, BuildListener listener, String id, String comment)\n            throws IOException {\n        JiraSession session = getJiraSession(build);\n        session.addCommentWithoutConstrains(id, comment);\n        listener.getLogger().println(String.format(\"[%s] Commented issue\", id));\n    }\n\n    /**\n     * Returns the issue id\n     *\n     * @param filename file from which to read the issue name\n     * @return issue ID\n     * @throws IOException if IO fails\n     * @throws InterruptedException if thread is interrupted\n     */\n    private String getIssue(String filename) throws IOException, InterruptedException {\n        String issueId = \"\";\n        Path path = Paths.get(filename);\n        if (Files.notExists(path)) {\n            return null;\n        }\n        try (BufferedReader br = Files.newBufferedReader(path)) {\n            String issue;\n\n            while ((issue = br.readLine()) != null) {\n                issueId = issue;\n            }\n            return StringUtils.trimToNull(issueId);\n        } catch (FileNotFoundException e) {\n            return null;\n        }\n    }\n\n    JiraSite getSiteForProject(AbstractProject<?, ?> project) {\n        return JiraSite.get(project);\n    }\n\n    /**\n     * Returns the jira session.\n     *\n     * @param build build\n     * @return JiraSession\n     * @throws IOException if IO fails\n     */\n    private JiraSession getJiraSession(AbstractBuild<?, ?> build) throws IOException {\n\n        JiraSite site = getSiteForProject(build.getProject());\n\n        if (site == null) {\n            throw new IllegalStateException(\n                    \"Jira site needs to be configured in the project \" + build.getFullDisplayName());\n        }\n\n        JiraSession session = site.getSession(build.getProject());\n        if (session == null) {\n            throw new IllegalStateException(\"Remote access for Jira isn't configured in Jenkins\");\n        }\n\n        return session;\n    }\n\n    /**\n     * @param filename filename\n     */\n    private void deleteFile(String filename) {\n        File file = new File(filename);\n        if (file.exists() && !file.delete()) {\n            LOG.warning(\"WARNING: couldn't delete file: \" + filename);\n        }\n    }\n\n    /**\n     * Writes the issue id in the file, which is stored in the Job's directory.\n     *\n     * @param filename filenam\n     * @param issue issue\n     * @throws FileNotFoundException if file not found\n     */\n    private void writeInFile(String filename, Issue issue) throws IOException {\n        // olamy really weird to write an empty file especially with null\n        // but backward compat and unit tests assert that.....\n        // can't believe such stuff has been merged......\n        try (BufferedWriter bw = Files.newBufferedWriter(Paths.get(filename))) {\n            bw.write(issue.getKey() == null ? \"null\" : issue.getKey());\n        }\n    }\n\n    /**\n     * when the current build fails it checks for the previous build's result,\n     * creates jira issue if the result was \"success\" and adds comment if the result\n     * was \"fail\".\n     * It adds comment until the previously created issue is closed.\n     */\n    private void currentBuildResultFailure(\n            AbstractBuild<?, ?> build,\n            BuildListener listener,\n            Result previousBuildResult,\n            String filename,\n            EnvVars vars)\n            throws InterruptedException, IOException, RestClientException {\n\n        if (previousBuildResult == Result.FAILURE) {\n            String comment = String.format(\"Build is still failing.%nFailed run: %s\", getBuildDetailsString(vars));\n\n            // Get the issue-id which was filed when the previous built failed\n            String issueId = getIssue(filename);\n            if (issueId != null) {\n                try {\n                    // The status of the issue which was filed when the previous build failed\n                    Status status = getStatus(build, issueId);\n\n                    // Issue Closed, need to open new one\n                    if (isDone(status)) {\n\n                        listener.getLogger().println(\"The previous build also failed but the issue is closed\");\n                        deleteFile(filename);\n                        Issue issue = createJiraIssue(build, filename);\n                        LOG.info(String.format(\"[%s] created.\", issue.getKey()));\n                        listener.getLogger().println(\"Build failed, created Jira issue \" + issue.getKey());\n                    } else {\n                        addComment(build, listener, issueId, comment);\n                        LOG.info(String.format(\"[%s] The previous build also failed, comment added.\", issueId));\n                    }\n                } catch (IOException e) {\n                    LOG.warning(String.format(\"[%s] - error processing Jira change: %s\", issueId, e.getMessage()));\n                }\n            }\n        }\n\n        if (previousBuildResult == Result.SUCCESS || previousBuildResult == Result.ABORTED) {\n            try {\n                Issue issue = createJiraIssue(build, filename);\n                LOG.info(String.format(\"[%s] created.\", issue.getKey()));\n                listener.getLogger().println(\"Build failed, created Jira issue \" + issue.getKey());\n            } catch (IOException e) {\n                listener.error(\"Error creating Jira issue : \" + e.getMessage());\n                LOG.warning(\"Error creating Jira issue\\n\" + e.getMessage());\n            }\n        }\n    }\n\n    /**\n     * when the current build's result is \"success\",\n     * it checks for the previous build's result and adds comment until the\n     * previously created issue is closed.\n     *\n     * @param build build\n     * @param previousBuildResult previous build result\n     * @param filename filename\n     * @param vars variables\n     * @throws InterruptedException if thread isinterrupted\n     * @throws IOException if IO fails\n     */\n    private void currentBuildResultSuccess(\n            AbstractBuild<?, ?> build,\n            BuildListener listener,\n            Result previousBuildResult,\n            String filename,\n            EnvVars vars)\n            throws InterruptedException, IOException {\n\n        if (previousBuildResult == Result.FAILURE || previousBuildResult == Result.SUCCESS) {\n            String comment =\n                    String.format(\"Previously failing build now is OK.%n Passed run: %s\", getBuildDetailsString(vars));\n            String issueId = getIssue(filename);\n\n            // if issue exists it will check the status and comment or delete the file\n            // accordingly\n            if (issueId != null) {\n                try {\n                    Status status = getStatus(build, issueId);\n\n                    // if issue is in closed status\n                    if (isDone(status)) {\n                        LOG.info(String.format(\"%s is closed\", issueId));\n                        deleteFile(filename);\n                    } else {\n                        LOG.info(String.format(\"%s is not closed, comment was added.\", issueId));\n                        addComment(build, listener, issueId, comment);\n                        if (actionIdOnSuccess != null && actionIdOnSuccess > 0) {\n                            progressWorkflowAction(build, issueId, actionIdOnSuccess);\n                        }\n                    }\n\n                } catch (IOException e) {\n                    listener.error(\"Error updating Jira issue \" + issueId + \" : \" + e.getMessage());\n                    LOG.warning(\"Error updating Jira issue \" + issueId + \"\\n\" + e);\n                }\n            }\n        }\n    }\n\n    static boolean isDone(Status status) {\n        if (status.getName().equalsIgnoreCase(finishedStatuses.Closed.toString())\n                || status.getName().equalsIgnoreCase(finishedStatuses.Resolved.toString())\n                || status.getName().equalsIgnoreCase(finishedStatuses.Done.toString())) {\n            return true;\n        }\n\n        StatusCategory category = status.getStatusCategory();\n        if (category == null) {\n            return false;\n        }\n        return \"done\".equals(category.getKey());\n    }\n\n    private void progressWorkflowAction(AbstractBuild<?, ?> build, String issueId, Integer actionId)\n            throws IOException {\n        JiraSession session = getJiraSession(build);\n        session.progressWorkflowAction(issueId, actionId);\n    }\n\n    /**\n     * Returns build details string in wiki format, with hyperlinks.\n     *\n     * @param vars environment variables\n     * @return build details string\n     */\n    private String getBuildDetailsString(EnvVars vars) {\n        final String buildURL = vars.get(\"BUILD_URL\");\n        return String.format(\"[%s|%s] [console log|%s]\", getBuildName(vars), buildURL, buildURL.concat(\"console\"));\n    }\n\n    /**\n     * Returns build name in format BUILD#10\n     *\n     * @param vars environment variables\n     * @return String build name\n     */\n    private String getBuildName(EnvVars vars) {\n        final String jobName = vars.get(\"JOB_NAME\");\n        final String buildNumber = vars.get(\"BUILD_NUMBER\");\n        return String.format(\"%s #%s\", jobName, buildNumber);\n    }\n\n    public static class DescriptorImpl extends BuildStepDescriptor<Publisher> {\n\n        public DescriptorImpl() {\n            super(JiraCreateIssueNotifier.class);\n        }\n\n        public FormValidation doCheckProjectKey(@QueryParameter String value) throws IOException {\n            if (value.length() == 0) {\n                return FormValidation.error(\"Please set the project key\");\n            }\n            return FormValidation.ok();\n        }\n\n        public ListBoxModel doFillPriorityIdItems(@AncestorInPath final Item item) {\n            ListBoxModel items = new ListBoxModel().add(\"\"); // optional field\n            List<JiraSite> sites = JiraSite.getJiraSites(item);\n            for (JiraSite site : sites) {\n                JiraSession session = site.getSession(item);\n                if (session != null) {\n                    for (Priority priority : session.getPriorities()) {\n                        items.add(\"[\" + site.getName() + \"] \" + priority.getName(), String.valueOf(priority.getId()));\n                    }\n                }\n            }\n            return items;\n        }\n\n        public ListBoxModel doFillTypeIdItems(@AncestorInPath final Item item) {\n            ListBoxModel items = new ListBoxModel().add(\"\"); // optional field\n            List<JiraSite> sites = JiraSite.getJiraSites(item);\n            for (JiraSite site : sites) {\n                JiraSession session = site.getSession(item);\n                if (session != null) {\n                    for (IssueType type : session.getIssueTypes()) {\n                        items.add(\"[\" + site.getName() + \"] \" + type.getName(), String.valueOf(type.getId()));\n                    }\n                }\n            }\n            return items;\n        }\n\n        @Override\n        public JiraCreateIssueNotifier newInstance(StaplerRequest2 req, JSONObject formData) throws FormException {\n            return req.bindJSON(JiraCreateIssueNotifier.class, formData);\n        }\n\n        @Override\n        public boolean isApplicable(Class<? extends AbstractProject> jobType) {\n            return true;\n        }\n\n        @Override\n        public String getDisplayName() {\n            return Messages.JiraCreateIssueNotifier_DisplayName();\n        }\n\n        @Override\n        public String getHelpFile() {\n            return \"/plugin/jira/help-jira-create-issue.html\";\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/JiraCreateReleaseNotes.java",
    "content": "package hudson.plugins.jira;\n\nimport static org.apache.commons.lang3.StringUtils.defaultIfEmpty;\nimport static org.apache.commons.lang3.StringUtils.isEmpty;\n\nimport hudson.EnvVars;\nimport hudson.Extension;\nimport hudson.FilePath;\nimport hudson.Launcher;\nimport hudson.model.AbstractProject;\nimport hudson.model.BuildListener;\nimport hudson.model.Job;\nimport hudson.model.Result;\nimport hudson.model.Run;\nimport hudson.model.TaskListener;\nimport hudson.tasks.BuildStepMonitor;\nimport hudson.tasks.BuildWrapperDescriptor;\nimport java.io.IOException;\nimport java.util.HashMap;\nimport java.util.Map;\nimport jenkins.tasks.SimpleBuildWrapper;\nimport org.jenkinsci.Symbol;\nimport org.kohsuke.stapler.DataBoundConstructor;\n\npublic class JiraCreateReleaseNotes extends SimpleBuildWrapper {\n\n    @Extension\n    @Symbol(\"jiraCreateReleaseNotes\")\n    public static final class Descriptor extends BuildWrapperDescriptor {\n\n        @Override\n        public String getDisplayName() {\n            return \"Generate Release Notes\";\n        }\n\n        @Override\n        public boolean isApplicable(final AbstractProject<?, ?> item) {\n            return true;\n        }\n    }\n\n    public static final String DEFAULT_FILTER = \"status in (Resolved, Closed)\";\n    public static final String DEFAULT_ENVVAR_NAME = \"RELEASE_NOTES\";\n\n    private String jiraEnvironmentVariable;\n    private String jiraProjectKey;\n    private String jiraRelease;\n    private String jiraFilter;\n\n    // not in use anymore, as it always resets the given filter with the DEFAULT_FILTER\n    public JiraCreateReleaseNotes(\n            final String jiraProjectKey, final String jiraRelease, final String jiraEnvironmentVariable) {\n        this(jiraProjectKey, jiraRelease, jiraEnvironmentVariable, DEFAULT_FILTER);\n    }\n\n    @DataBoundConstructor\n    public JiraCreateReleaseNotes(\n            final String jiraProjectKey,\n            final String jiraRelease,\n            final String jiraEnvironmentVariable,\n            final String jiraFilter) {\n        this.jiraRelease = jiraRelease;\n        this.jiraProjectKey = jiraProjectKey;\n        this.jiraEnvironmentVariable = defaultIfEmpty(jiraEnvironmentVariable, DEFAULT_ENVVAR_NAME);\n        this.jiraFilter = defaultIfEmpty(jiraFilter, DEFAULT_FILTER);\n    }\n\n    public String getJiraEnvironmentVariable() {\n        return jiraEnvironmentVariable;\n    }\n\n    public String getJiraFilter() {\n        return jiraFilter;\n    }\n\n    public String getJiraProjectKey() {\n        return jiraProjectKey;\n    }\n\n    public String getJiraRelease() {\n        return jiraRelease;\n    }\n\n    public BuildStepMonitor getRequiredMonitorService() {\n        return BuildStepMonitor.NONE;\n    }\n\n    public void setJiraEnvironmentVariable(final String jiraEnvironmentVariable) {\n        this.jiraEnvironmentVariable = jiraEnvironmentVariable;\n    }\n\n    public void setJiraFilter(final String jiraFilter) {\n        this.jiraFilter = jiraFilter;\n    }\n\n    public void setJiraProjectKey(final String jiraProjectKey) {\n        this.jiraProjectKey = jiraProjectKey;\n    }\n\n    public void setJiraRelease(final String jiraRelease) {\n        this.jiraRelease = jiraRelease;\n    }\n\n    JiraSite getSiteForProject(Job<?, ?> project) {\n        return JiraSite.get(project);\n    }\n\n    @Override\n    public void setUp(\n            Context context,\n            Run<?, ?> run,\n            FilePath workspace,\n            Launcher launcher,\n            TaskListener listener,\n            EnvVars initialEnvironment)\n            throws IOException, InterruptedException {\n\n        final JiraSite site = getSiteForProject(run.getParent());\n\n        String realRelease = null;\n        String realProjectKey = null;\n        String releaseNotes = \"No Release Notes\";\n        String realFilter = DEFAULT_FILTER;\n\n        try {\n            realRelease = run.getEnvironment(listener).expand(jiraRelease);\n            realProjectKey = run.getEnvironment(listener).expand(jiraProjectKey);\n            realFilter = run.getEnvironment(listener).expand(jiraFilter);\n\n            if (isEmpty(realRelease)) {\n                throw new IllegalArgumentException(\"No version specified\");\n            }\n            if (isEmpty(realProjectKey)) {\n                throw new IllegalArgumentException(\"No project specified\");\n            }\n\n            if ((realRelease != null) && !realRelease.isEmpty()) {\n                releaseNotes = site.getReleaseNotesForFixVersion(realProjectKey, realRelease, realFilter);\n            } else {\n                listener.getLogger().printf(\"No release version found, skipping Release Notes generation%n\");\n            }\n\n        } catch (Exception e) {\n            e.printStackTrace(listener.fatalError(\n                    \"Unable to generate release notes for Jira version %s/%s: %s\", realRelease, realProjectKey, e));\n            if (listener instanceof BuildListener) {\n                ((BuildListener) listener).finished(Result.FAILURE);\n            }\n        }\n\n        final Map<String, String> envMap = new HashMap<>();\n        envMap.put(jiraEnvironmentVariable, releaseNotes);\n        context.getEnv().putAll(envMap);\n    }\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/JiraEnvironmentContributingAction.java",
    "content": "package hudson.plugins.jira;\n\nimport edu.umd.cs.findbugs.annotations.Nullable;\nimport hudson.EnvVars;\nimport hudson.model.AbstractBuild;\nimport hudson.model.EnvironmentContributingAction;\nimport hudson.model.InvisibleAction;\n\n/*\n * JiraEnvironmentVariableBuilder adds an instance of this class to the build to provide the environment variables\n *\n */\npublic class JiraEnvironmentContributingAction extends InvisibleAction implements EnvironmentContributingAction {\n\n    public static final String ISSUES_VARIABLE_NAME = \"JIRA_ISSUES\";\n    public static final String JIRA_URL_VARIABLE_NAME = \"JIRA_URL\";\n    public static final String ISSUES_SIZE_VARIABLE_NAME = \"JIRA_ISSUES_SIZE\";\n\n    private final String issuesList;\n\n    private final Integer issuesSize;\n\n    private final String jiraUrl;\n\n    @Nullable\n    public String getIssuesList() {\n        return issuesList;\n    }\n\n    public Integer getNumberOfIssues() {\n        return issuesSize == null ? Integer.valueOf(0) : issuesSize;\n    }\n\n    @Nullable\n    public String getJiraUrl() {\n        return jiraUrl;\n    }\n\n    public JiraEnvironmentContributingAction(String issuesList, Integer issuesSize, String jiraUrl) {\n        this.issuesList = issuesList;\n        this.issuesSize = issuesSize;\n        this.jiraUrl = jiraUrl;\n    }\n\n    @Override\n    public void buildEnvVars(AbstractBuild<?, ?> ab, EnvVars ev) {\n        if (ev != null) {\n            ev.put(ISSUES_VARIABLE_NAME, getIssuesList());\n            ev.put(ISSUES_SIZE_VARIABLE_NAME, getNumberOfIssues().toString());\n            ev.put(JIRA_URL_VARIABLE_NAME, getJiraUrl());\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/JiraEnvironmentVariableBuilder.java",
    "content": "package hudson.plugins.jira;\n\nimport com.atlassian.jira.rest.client.api.RestClientException;\nimport hudson.Extension;\nimport hudson.Launcher;\nimport hudson.model.AbstractBuild;\nimport hudson.model.AbstractProject;\nimport hudson.model.BuildListener;\nimport hudson.plugins.jira.selector.AbstractIssueSelector;\nimport hudson.plugins.jira.selector.DefaultIssueSelector;\nimport hudson.tasks.BuildStepDescriptor;\nimport hudson.tasks.Builder;\nimport java.io.IOException;\nimport java.util.Set;\nimport jenkins.model.Jenkins;\nimport org.apache.commons.lang3.StringUtils;\nimport org.kohsuke.stapler.DataBoundConstructor;\n\n/**\n * Adds Jira related environment variables to the build\n */\npublic class JiraEnvironmentVariableBuilder extends Builder {\n\n    private AbstractIssueSelector issueSelector;\n\n    @DataBoundConstructor\n    public JiraEnvironmentVariableBuilder(AbstractIssueSelector issueSelector) {\n        this.issueSelector = issueSelector;\n    }\n\n    public AbstractIssueSelector getIssueSelector() {\n        AbstractIssueSelector uis = this.issueSelector;\n        if (uis == null) {\n            uis = new DefaultIssueSelector();\n        }\n        return (this.issueSelector = uis);\n    }\n\n    JiraSite getSiteForProject(AbstractProject<?, ?> project) {\n        return JiraSite.get(project);\n    }\n\n    @Override\n    public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener)\n            throws InterruptedException, IOException {\n\n        JiraSite site = getSiteForProject(build.getProject());\n\n        if (site == null) {\n            listener.getLogger().println(Messages.JiraEnvironmentVariableBuilder_NoJiraSite());\n            return false;\n        }\n\n        Set<String> ids;\n        try {\n            ids = getIssueSelector().findIssueIds(build, site, listener);\n        } catch (RestClientException e) {\n            listener.getLogger().println(e.getMessage());\n            return false;\n        }\n\n        String idList = StringUtils.join(ids, \",\");\n        Integer idListSize = ids.size();\n\n        listener.getLogger()\n                .println(Messages.JiraEnvironmentVariableBuilder_Updating(\n                        JiraEnvironmentContributingAction.ISSUES_VARIABLE_NAME, idList));\n        listener.getLogger()\n                .println(Messages.JiraEnvironmentVariableBuilder_Updating(\n                        JiraEnvironmentContributingAction.ISSUES_SIZE_VARIABLE_NAME, idListSize));\n\n        build.addAction(new JiraEnvironmentContributingAction(idList, idListSize, site.getName()));\n\n        return true;\n    }\n\n    /**\n     * Descriptor for {@link JiraEnvironmentVariableBuilder}.\n     */\n    @Extension\n    public static final class DescriptorImpl extends BuildStepDescriptor<Builder> {\n\n        @Override\n        public boolean isApplicable(Class<? extends AbstractProject> klass) {\n            return true;\n        }\n\n        @Override\n        public String getDisplayName() {\n            return Messages.JiraEnvironmentVariableBuilder_DisplayName();\n        }\n\n        public boolean hasIssueSelectors() {\n            return Jenkins.get().getDescriptorList(AbstractIssueSelector.class).size() > 0;\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/JiraFolderProperty.java",
    "content": "package hudson.plugins.jira;\n\nimport com.cloudbees.hudson.plugins.folder.AbstractFolder;\nimport com.cloudbees.hudson.plugins.folder.AbstractFolderProperty;\nimport com.cloudbees.hudson.plugins.folder.AbstractFolderPropertyDescriptor;\nimport edu.umd.cs.findbugs.annotations.NonNull;\nimport hudson.Extension;\nimport hudson.model.ItemGroup;\nimport java.util.Collections;\nimport java.util.List;\nimport org.kohsuke.stapler.DataBoundConstructor;\nimport org.kohsuke.stapler.DataBoundSetter;\n\n/**\n * Provides folder level Jira configuration.\n */\npublic class JiraFolderProperty extends AbstractFolderProperty<AbstractFolder<?>> {\n    /**\n     * Hold the Jira sites configuration.\n     */\n    private List<JiraSite> sites = Collections.emptyList();\n\n    /**\n     * Constructor.\n     */\n    @DataBoundConstructor\n    public JiraFolderProperty() {}\n\n    /**\n     * Return the Jira sites.\n     *\n     * @return the Jira sites\n     */\n    public JiraSite[] getSites() {\n        return sites.toArray(new JiraSite[0]);\n    }\n\n    /**\n     * @param site the Jira site\n     * @deprecated use {@link #setSites(List)} instead\n     */\n    @Deprecated\n    public void setSites(JiraSite site) {\n        sites.add(site);\n    }\n\n    @DataBoundSetter\n    public void setSites(List<JiraSite> sites) {\n        this.sites = sites;\n    }\n\n    /**\n     * @deprecated use {@link JiraSite#getSitesFromFolders(ItemGroup)}\n     */\n    @Deprecated\n    public static List<JiraSite> getSitesFromFolders(ItemGroup itemGroup) {\n        return JiraSite.getSitesFromFolders(itemGroup);\n    }\n\n    /**\n     * Descriptor class.\n     */\n    @Extension\n    public static class DescriptorImpl extends AbstractFolderPropertyDescriptor {\n\n        @NonNull\n        @Override\n        public String getDisplayName() {\n            return Messages.JiraFolderProperty_DisplayName();\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/JiraGlobalConfiguration.java",
    "content": "package hudson.plugins.jira;\n\nimport edu.umd.cs.findbugs.annotations.NonNull;\nimport edu.umd.cs.findbugs.annotations.SuppressFBWarnings;\nimport hudson.Extension;\nimport hudson.util.PersistedList;\nimport java.util.List;\nimport jenkins.model.GlobalConfiguration;\nimport jenkins.model.Jenkins;\nimport org.kohsuke.stapler.DataBoundSetter;\n\n@Extension\npublic class JiraGlobalConfiguration extends GlobalConfiguration {\n\n    @NonNull\n    public static JiraGlobalConfiguration get() {\n        return (JiraGlobalConfiguration) Jenkins.get().getDescriptorOrDie(JiraGlobalConfiguration.class);\n    }\n\n    @SuppressFBWarnings(value = \"PA_PUBLIC_PRIMITIVE_ATTRIBUTE\", justification = \"Backwards compatibility\")\n    public List<JiraSite> sites = new PersistedList<>(this);\n\n    public JiraGlobalConfiguration() {\n        load();\n    }\n\n    public List<JiraSite> getSites() {\n        return sites;\n    }\n\n    @DataBoundSetter\n    public void setSites(List<JiraSite> sites) {\n        this.sites = sites;\n        save();\n    }\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/JiraIssueMigrator.java",
    "content": "package hudson.plugins.jira;\n\nimport hudson.Extension;\nimport hudson.Launcher;\nimport hudson.model.AbstractBuild;\nimport hudson.model.AbstractProject;\nimport hudson.model.BuildListener;\nimport hudson.model.Result;\nimport hudson.tasks.BuildStepDescriptor;\nimport hudson.tasks.BuildStepMonitor;\nimport hudson.tasks.Notifier;\nimport hudson.tasks.Publisher;\nimport net.sf.json.JSONObject;\nimport org.kohsuke.stapler.DataBoundConstructor;\nimport org.kohsuke.stapler.StaplerRequest2;\n\npublic class JiraIssueMigrator extends Notifier {\n\n    private static final long serialVersionUID = 6909671291180081586L;\n\n    private String jiraProjectKey;\n    private String jiraRelease;\n    private String jiraReplaceVersion;\n    private String jiraQuery;\n    private boolean addRelease;\n\n    @DataBoundConstructor\n    public JiraIssueMigrator(\n            String jiraProjectKey,\n            String jiraRelease,\n            String jiraQuery,\n            String jiraReplaceVersion,\n            boolean addRelease) {\n        this.jiraRelease = jiraRelease;\n        this.jiraProjectKey = jiraProjectKey;\n        this.jiraQuery = jiraQuery;\n        this.jiraReplaceVersion = jiraReplaceVersion;\n        this.addRelease = addRelease;\n    }\n\n    public String getJiraRelease() {\n        return jiraRelease;\n    }\n\n    public void setJiraRelease(String jiraRelease) {\n        this.jiraRelease = jiraRelease;\n    }\n\n    public String getJiraProjectKey() {\n        return jiraProjectKey;\n    }\n\n    public void setJiraProjectKey(String jiraProjectKey) {\n        this.jiraProjectKey = jiraProjectKey;\n    }\n\n    public String getJiraQuery() {\n        return jiraQuery;\n    }\n\n    public void setJiraQuery(String jiraQuery) {\n        this.jiraQuery = jiraQuery;\n    }\n\n    public String getJiraReplaceVersion() {\n        return jiraReplaceVersion;\n    }\n\n    public void setJiraReplaceVersion(String jiraReplaceVersion) {\n        this.jiraReplaceVersion = jiraReplaceVersion;\n    }\n\n    public boolean isAddRelease() {\n        return addRelease;\n    }\n\n    public void setAddRelease(boolean addRelease) {\n        this.addRelease = addRelease;\n    }\n\n    @Override\n    public BuildStepDescriptor<Publisher> getDescriptor() {\n        return DESCRIPTOR;\n    }\n\n    @Extension\n    public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl();\n\n    @Override\n    public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) {\n        String realRelease = null;\n        String realReplace = null;\n        String realQuery = \"\";\n        String realProjectKey = null;\n        try {\n            realRelease = build.getEnvironment(listener).expand(jiraRelease);\n            realReplace = build.getEnvironment(listener).expand(jiraReplaceVersion);\n            realProjectKey = build.getEnvironment(listener).expand(jiraProjectKey);\n\n            if (realRelease == null || realRelease.isEmpty()) {\n                throw new IllegalArgumentException(\"Release is Empty\");\n            }\n\n            if (realProjectKey == null || realProjectKey.isEmpty()) {\n                throw new IllegalArgumentException(\"No project specified\");\n            }\n\n            realQuery = build.getEnvironment(listener).expand(jiraQuery);\n            if (realQuery == null || realQuery.isEmpty()) {\n                throw new IllegalArgumentException(\"JQL query is Empty\");\n            }\n\n            JiraSite site = getJiraSiteForProject(build.getProject());\n\n            if (addRelease) {\n                site.addFixVersionToIssue(realProjectKey, realRelease, realQuery);\n            } else {\n                if (realReplace == null || realReplace.isEmpty()) {\n                    site.migrateIssuesToFixVersion(realProjectKey, realRelease, realQuery);\n                } else {\n                    site.replaceFixVersion(realProjectKey, realReplace, realRelease, realQuery);\n                }\n            }\n        } catch (Exception e) {\n            e.printStackTrace(\n                    listener.fatalError(\"Unable to release jira version %s/%s: %s\", realRelease, realProjectKey, e));\n            listener.finished(Result.FAILURE);\n            return false;\n        }\n        return true;\n    }\n\n    JiraSite getJiraSiteForProject(AbstractProject<?, ?> project) {\n        return JiraSite.get(project);\n    }\n\n    @Override\n    public BuildStepMonitor getRequiredMonitorService() {\n        return BuildStepMonitor.NONE;\n    }\n\n    public static class DescriptorImpl extends BuildStepDescriptor<Publisher> {\n\n        public DescriptorImpl() {\n            super(JiraIssueMigrator.class);\n        }\n\n        @Override\n        public JiraIssueMigrator newInstance(StaplerRequest2 req, JSONObject formData) throws FormException {\n            return req.bindJSON(JiraIssueMigrator.class, formData);\n        }\n\n        @Override\n        public boolean isApplicable(Class<? extends AbstractProject> jobType) {\n            return true;\n        }\n\n        @Override\n        public String getDisplayName() {\n            return Messages.JiraReleaseVersionMigrator_DisplayName();\n        }\n\n        @Override\n        public String getHelpFile() {\n            return \"/plugin/jira/help-release-migrate.html\";\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/JiraIssueUpdateBuilder.java",
    "content": "/*\n * Copyright 2012 MeetMe, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage hudson.plugins.jira;\n\nimport com.atlassian.jira.rest.client.api.RestClientException;\nimport hudson.EnvVars;\nimport hudson.Extension;\nimport hudson.Util;\nimport hudson.model.*;\nimport hudson.tasks.BuildStepDescriptor;\nimport hudson.tasks.Builder;\nimport hudson.util.FormValidation;\nimport java.io.IOException;\nimport jenkins.tasks.SimpleBuildStep;\nimport org.apache.commons.lang3.StringUtils;\nimport org.jenkinsci.Symbol;\nimport org.kohsuke.stapler.DataBoundConstructor;\nimport org.kohsuke.stapler.QueryParameter;\n\n/**\n * Build step that will mass-update all issues matching a JQL query, using the specified workflow\n * action name (e.g., \"Resolve Issue\", \"Close Issue\").\n *\n * @author Joe Hansche jhansche@myyearbook.com\n */\npublic class JiraIssueUpdateBuilder extends Builder implements SimpleBuildStep {\n    private final String jqlSearch;\n    private final String workflowActionName;\n    private final String comment;\n\n    @DataBoundConstructor\n    public JiraIssueUpdateBuilder(String jqlSearch, String workflowActionName, String comment) {\n        this.jqlSearch = Util.fixEmptyAndTrim(jqlSearch);\n        this.workflowActionName = Util.fixEmptyAndTrim(workflowActionName);\n        this.comment = Util.fixEmptyAndTrim(comment);\n    }\n\n    /**\n     * @return the jql\n     */\n    public String getJqlSearch() {\n        return jqlSearch;\n    }\n\n    /**\n     * @return the workflowActionName\n     */\n    public String getWorkflowActionName() {\n        return workflowActionName;\n    }\n\n    /**\n     * @return the comment\n     */\n    public String getComment() {\n        return comment;\n    }\n\n    JiraSite getSiteForJob(Job<?, ?> job) {\n        return JiraSite.get(job);\n    }\n\n    /**\n     * Performs the actual update based on job configuration.\n     */\n    @Override\n    public void perform(Run<?, ?> run, EnvVars env, TaskListener listener) throws InterruptedException, IOException {\n        String realComment = Util.fixEmptyAndTrim(env.expand(comment));\n        String realJql = Util.fixEmptyAndTrim(env.expand(jqlSearch));\n        String realWorkflowActionName = Util.fixEmptyAndTrim(env.expand(workflowActionName));\n\n        JiraSite site = getSiteForJob(run.getParent());\n\n        if (site == null) {\n            listener.getLogger().println(Messages.NoJiraSite());\n            run.setResult(Result.FAILURE);\n            return;\n        }\n\n        if (StringUtils.isNotEmpty(realWorkflowActionName)) {\n            listener.getLogger().println(Messages.JiraIssueUpdateBuilder_UpdatingWithAction(realWorkflowActionName));\n        }\n\n        listener.getLogger().println(\"[Jira] JQL: \" + realJql);\n\n        try {\n            if (!site.progressMatchingIssues(realJql, realWorkflowActionName, realComment, listener.getLogger())) {\n                listener.getLogger().println(Messages.JiraIssueUpdateBuilder_SomeIssuesFailed());\n                run.setResult(Result.UNSTABLE);\n            }\n        } catch (RestClientException e) {\n            listener.getLogger().println(e.getMessage());\n            run.setResult(Result.FAILURE);\n        }\n    }\n\n    @Override\n    public boolean requiresWorkspace() {\n        return false;\n    }\n\n    @Override\n    public DescriptorImpl getDescriptor() {\n        return (DescriptorImpl) super.getDescriptor();\n    }\n\n    /**\n     * Descriptor for {@link JiraIssueUpdateBuilder}.\n     */\n    @Extension\n    @Symbol(\"jiraExecuteWorkflow\")\n    public static final class DescriptorImpl extends BuildStepDescriptor<Builder> {\n        /**\n         * Performs on-the-fly validation of the form field 'Jql'.\n         *\n         * @param value This parameter receives the value that the user has typed.\n         * @return Indicates the outcome of the validation. This is sent to the browser.\n         */\n        public FormValidation doCheckJqlSearch(@QueryParameter String value) {\n            if (value.length() == 0) {\n                return FormValidation.error(Messages.JiraIssueUpdateBuilder_NoJqlSearch());\n            }\n\n            return FormValidation.ok();\n        }\n\n        public FormValidation doCheckWorkflowActionName(@QueryParameter String value) {\n            if (Util.fixNull(value).trim().length() == 0) {\n                return FormValidation.warning(Messages.JiraIssueUpdateBuilder_NoWorkflowAction());\n            }\n\n            return FormValidation.ok();\n        }\n\n        @Override\n        public boolean isApplicable(Class<? extends AbstractProject> klass) {\n            return true;\n        }\n\n        /**\n         * This human readable name is used in the configuration screen.\n         */\n        @Override\n        public String getDisplayName() {\n            return Messages.JiraIssueUpdateBuilder_DisplayName();\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/JiraIssueUpdater.java",
    "content": "package hudson.plugins.jira;\n\nimport hudson.EnvVars;\nimport hudson.Extension;\nimport hudson.Launcher;\nimport hudson.matrix.MatrixAggregatable;\nimport hudson.matrix.MatrixAggregator;\nimport hudson.matrix.MatrixBuild;\nimport hudson.matrix.MatrixRun;\nimport hudson.model.AbstractBuild;\nimport hudson.model.AbstractProject;\nimport hudson.model.BuildListener;\nimport hudson.model.Run;\nimport hudson.model.TaskListener;\nimport hudson.plugins.jira.selector.AbstractIssueSelector;\nimport hudson.plugins.jira.selector.DefaultIssueSelector;\nimport hudson.scm.SCM;\nimport hudson.tasks.BuildStepDescriptor;\nimport hudson.tasks.BuildStepMonitor;\nimport hudson.tasks.Publisher;\nimport hudson.tasks.Recorder;\nimport java.io.IOException;\nimport java.io.PrintStream;\nimport java.util.ArrayList;\nimport java.util.List;\nimport jenkins.model.Jenkins;\nimport jenkins.tasks.SimpleBuildStep;\nimport org.jenkinsci.Symbol;\nimport org.kohsuke.stapler.DataBoundConstructor;\n\n/**\n * Parses build changelog for Jira issue IDs and then\n * updates Jira issues accordingly.\n *\n * @author Kohsuke Kawaguchi\n */\npublic class JiraIssueUpdater extends Recorder implements MatrixAggregatable, SimpleBuildStep {\n\n    private AbstractIssueSelector issueSelector;\n    private SCM scm;\n    private List<String> labels;\n\n    @DataBoundConstructor\n    public JiraIssueUpdater(AbstractIssueSelector issueSelector, SCM scm, List<String> labels) {\n        this.issueSelector = issueSelector;\n        this.scm = scm;\n        if (labels != null) {\n            this.labels = labels;\n        } else {\n            this.labels = new ArrayList();\n        }\n    }\n\n    @Override\n    public void perform(Run<?, ?> run, EnvVars env, TaskListener listener) throws InterruptedException, IOException {\n        // Don't do anything for individual matrix runs.\n        if (run instanceof MatrixRun) {\n            return;\n        } else if (run instanceof AbstractBuild) {\n            AbstractBuild<?, ?> abstractBuild = (AbstractBuild<?, ?>) run;\n            Updater updater = new Updater(abstractBuild.getParent().getScm(), labels);\n            updater.perform(run, listener, getIssueSelector());\n        } else if (scm != null) {\n            Updater updater = new Updater(scm, labels);\n            updater.perform(run, listener, getIssueSelector());\n        } else {\n            throw new IllegalArgumentException(\n                    \"Unsupported run type \" + run.getClass().getName());\n        }\n    }\n\n    @Override\n    public boolean requiresWorkspace() {\n        return false;\n    }\n\n    @Override\n    public BuildStepMonitor getRequiredMonitorService() {\n        return BuildStepMonitor.NONE;\n    }\n\n    @Override\n    public DescriptorImpl getDescriptor() {\n        return DESCRIPTOR;\n    }\n\n    public AbstractIssueSelector getIssueSelector() {\n        AbstractIssueSelector uis = this.issueSelector;\n        if (uis == null) {\n            uis = new DefaultIssueSelector();\n        }\n        return (this.issueSelector = uis);\n    }\n\n    public SCM getScm() {\n        return scm;\n    }\n\n    public List<String> getLabels() {\n        return labels;\n    }\n\n    @Extension\n    public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl();\n\n    @Override\n    public MatrixAggregator createAggregator(MatrixBuild build, Launcher launcher, BuildListener listener) {\n        return new MatrixAggregator(build, launcher, listener) {\n            @Override\n            public boolean endBuild() throws InterruptedException, IOException {\n                PrintStream logger = listener.getLogger();\n                logger.println(\"End of Matrix Build. Updating Jira.\");\n                Updater updater = new Updater(this.build.getParent().getScm(), labels);\n                return updater.perform(this.build, this.listener, getIssueSelector());\n            }\n        };\n    }\n\n    @Symbol(\"jiraCommentIssues\")\n    public static final class DescriptorImpl extends BuildStepDescriptor<Publisher> {\n        private DescriptorImpl() {\n            super(JiraIssueUpdater.class);\n        }\n\n        @Override\n        public String getDisplayName() {\n            // Displayed in the publisher section\n            return Messages.JiraIssueUpdater_DisplayName();\n        }\n\n        @Override\n        public String getHelpFile() {\n            return \"/plugin/jira/help.html\";\n        }\n\n        @Override\n        @SuppressWarnings(\"unchecked\")\n        public boolean isApplicable(Class<? extends AbstractProject> jobType) {\n            return true;\n        }\n\n        public boolean hasIssueSelectors() {\n            return Jenkins.getInstance()\n                            .getDescriptorList(AbstractIssueSelector.class)\n                            .size()\n                    > 1;\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/JiraJobAction.java",
    "content": "package hudson.plugins.jira;\n\nimport com.atlassian.jira.rest.client.api.RestClientException;\nimport edu.umd.cs.findbugs.annotations.NonNull;\nimport edu.umd.cs.findbugs.annotations.Nullable;\nimport hudson.Extension;\nimport hudson.model.Action;\nimport hudson.model.Job;\nimport hudson.model.TaskListener;\nimport hudson.model.listeners.RunListener;\nimport hudson.plugins.jira.model.JiraIssue;\nimport java.io.IOException;\nimport java.net.URL;\nimport java.net.URLDecoder;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\nimport jenkins.branch.MultiBranchProject;\nimport org.jenkinsci.plugins.workflow.job.WorkflowJob;\nimport org.jenkinsci.plugins.workflow.job.WorkflowRun;\nimport org.kohsuke.stapler.DataBoundConstructor;\nimport org.kohsuke.stapler.export.Exported;\nimport org.kohsuke.stapler.export.ExportedBean;\n\n/**\n * JiraJobAction is to store a reference to the {@link JiraIssue} that represents work\n * being done for a {@link WorkflowJob} (branch or PR) belonging to a {@link MultiBranchProject}\n *\n * Any branches with the whole key in the name or after a prefix will have this action attached.\n * e.g. \"JENKINS-1234\" and \"feature/JENKINS-1234\" will have this action with the issue JENKINS-1234 referenced\n */\n@ExportedBean\npublic class JiraJobAction implements Action {\n\n    private static final Logger LOGGER = Logger.getLogger(JiraJobAction.class.getName());\n\n    private final Job<?, ?> owner;\n    private final JiraIssue issue;\n\n    @DataBoundConstructor\n    public JiraJobAction(Job<?, ?> owner, JiraIssue issue) {\n        this.owner = owner;\n        this.issue = issue;\n    }\n\n    /**\n     * @return issue representing the job\n     */\n    @Exported\n    public JiraIssue getIssue() {\n        return issue;\n    }\n\n    /**\n     * @return url of the Jira server\n     */\n    @Exported\n    @Nullable\n    public String getServerURL() {\n        JiraSite jiraSite = JiraSite.get(owner);\n        URL url = jiraSite != null ? jiraSite.getUrl() : null;\n        return url != null ? url.toString() : null;\n    }\n\n    /**\n     * Adds a {@link JiraJobAction} to a {@link WorkflowJob} if it belongs to a {@link MultiBranchProject}\n     * and its name contains an Jira issue key\n     * @param job to add the property to\n     * @param site to fetch issue data\n     * @throws IOException if something goes wrong fetching the Jira issue\n     */\n    public static void setAction(@NonNull Job job, @NonNull JiraSite site) throws IOException {\n        // If there is already a action set then skip\n        if (job.getAction(JiraJobAction.class) != null) {\n            return;\n        }\n\n        // Exclude all non-multibranch workflow jobs\n        if (!(job.getParent() instanceof MultiBranchProject)) {\n            return;\n        }\n\n        // Find the first Jira issue key in the branch name\n        // If it exists, create the action and set it\n        Pattern pattern = site.getIssuePattern();\n        // Pipeline will URL encode job names if the branch or PR name contains a '/' or other non-URL safe characters\n        String decodedJobName = URLDecoder.decode(job.getName(), \"UTF-8\");\n        String issueKey = null;\n        Matcher m = pattern.matcher(decodedJobName);\n        while (m.find()) {\n            if (m.groupCount() >= 1) {\n                issueKey = m.group(1);\n                break;\n            }\n        }\n\n        if (issueKey != null) {\n            JiraIssue issue = site.getIssue(issueKey);\n            if (issue != null) {\n                job.addAction(new JiraJobAction(job, issue));\n                job.save();\n            }\n        }\n    }\n\n    @Override\n    public String getIconFileName() {\n        return null;\n    }\n\n    @Override\n    public String getDisplayName() {\n        return \"Jira\";\n    }\n\n    @Override\n    public String getUrlName() {\n        return \"jira\";\n    }\n\n    @Extension\n    public static final class RunListenerImpl extends RunListener<WorkflowRun> {\n        @Override\n        public void onStarted(WorkflowRun workflowRun, TaskListener listener) {\n            WorkflowJob parent = workflowRun.getParent();\n            JiraSite site = JiraSite.get(parent);\n            if (site != null) {\n                try {\n                    setAction(parent, site);\n                } catch (IOException | RestClientException e) {\n                    LOGGER.log(Level.WARNING, \"Could not set JiraJobAction for <\" + parent.getFullName() + \">\", e);\n                    listener.getLogger().println(e.getMessage());\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/JiraMailAddressResolver.java",
    "content": "package hudson.plugins.jira;\n\nimport hudson.Extension;\nimport hudson.model.Job;\nimport hudson.model.User;\nimport hudson.tasks.MailAddressResolver;\nimport java.util.List;\nimport java.util.logging.Logger;\nimport java.util.regex.Pattern;\nimport org.kohsuke.stapler.Stapler;\nimport org.kohsuke.stapler.StaplerRequest2;\n\n/**\n * Resolve user email by searching his userId as username in Jira.\n *\n * @author Honza Brázdil jbrazdil@redhat.com\n */\n@Extension\npublic class JiraMailAddressResolver extends MailAddressResolver {\n    private static final Logger LOGGER = Logger.getLogger(JiraMailAddressResolver.class.getName());\n\n    /**\n     * Boolean to disable the Jira mail address resolver.\n     * <p>\n     * To disable set the System property \"-Dhudson.plugins.jira.JiraMailAddressResolver.disabled=true\"\n     */\n    public static boolean disabled = Boolean.getBoolean(JiraMailAddressResolver.class.getName() + \".disabled\");\n\n    @Override\n    public String findMailAddressFor(User u) {\n        if (disabled) {\n            return null;\n        }\n        String username = u.getId();\n\n        Job<?, ?> job = null;\n\n        StaplerRequest2 req = Stapler.getCurrentRequest2();\n        if (req != null) {\n            job = req.findAncestorObject(Job.class);\n        }\n\n        List<JiraSite> sites = job == null ? JiraGlobalConfiguration.get().getSites() : JiraSite.getJiraSites(job);\n\n        for (JiraSite site : sites) {\n            JiraSession session = site.getSession(job);\n            if (session == null) {\n                continue;\n            }\n\n            com.atlassian.jira.rest.client.api.domain.User user = session.service.getUser(username);\n            if (user != null) {\n                String email = user.getEmailAddress();\n                if (email != null) {\n                    email = unmaskEmail(email);\n                    return email;\n                }\n            }\n        }\n        return null;\n    }\n\n    private static final String PRE = \"[( \\\\[<_{\\\"=]+\";\n    private static final String POST = \"[) \\\\]>_}\\\"=]+\";\n    private static final Pattern AT = Pattern.compile(PRE + \"[aA][tT]\" + POST);\n    private static final Pattern DOT = Pattern.compile(PRE + \"[dD][oO0][tT]\" + POST);\n\n    // unmask emails like \"john dot doe at example dot com\" to john.doe@example.com\n    static String unmaskEmail(String email) {\n        email = AT.matcher(email).replaceAll(\"@\");\n        email = DOT.matcher(email).replaceAll(\".\");\n        return email;\n    }\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/JiraProjectProperty.java",
    "content": "package hudson.plugins.jira;\n\nimport com.cloudbees.hudson.plugins.folder.AbstractFolder;\nimport edu.umd.cs.findbugs.annotations.Nullable;\nimport hudson.Extension;\nimport hudson.Util;\nimport hudson.init.InitMilestone;\nimport hudson.init.Initializer;\nimport hudson.model.Job;\nimport hudson.model.JobProperty;\nimport hudson.model.JobPropertyDescriptor;\nimport hudson.util.ListBoxModel;\nimport java.util.List;\nimport java.util.stream.Stream;\nimport jenkins.model.Jenkins;\nimport org.kohsuke.stapler.AncestorInPath;\nimport org.kohsuke.stapler.DataBoundConstructor;\n\n/**\n * Associates {@link Job} with {@link JiraSite}.\n *\n * @author Kohsuke Kawaguchi\n */\npublic class JiraProjectProperty extends JobProperty<Job<?, ?>> {\n\n    /**\n     * Used to find {@link JiraSite}. Matches {@link JiraSite#getName()}. Always\n     * non-null (but beware that this value might become stale if the system\n     * config is changed.)\n     */\n    public final String siteName;\n\n    @DataBoundConstructor\n    public JiraProjectProperty(String siteName) {\n        siteName = Util.fixEmptyAndTrim(siteName);\n        if (siteName == null) {\n            // defaults to the first one\n            List<JiraSite> sites = JiraGlobalConfiguration.get().getSites();\n            if (!sites.isEmpty()) {\n                siteName = sites.get(0).getName();\n            }\n        }\n        this.siteName = siteName;\n    }\n\n    /**\n     * Gets the {@link JiraSite} that this project belongs to.\n     *\n     * @return null if the configuration becomes out of sync.\n     */\n    @Nullable\n    public JiraSite getSite() {\n        List<JiraSite> sites = JiraGlobalConfiguration.get().getSites();\n\n        if (siteName == null && sites.size() > 0) {\n            // default\n            return sites.get(0);\n        }\n\n        Stream<JiraSite> streams = sites.stream();\n        if (owner != null) {\n            Stream<JiraSite> stream2 = JiraFolderProperty.getSitesFromFolders(owner.getParent()).stream();\n            streams = Stream.concat(streams, stream2).parallel();\n        }\n\n        return streams.filter(jiraSite -> jiraSite.getName().equals(siteName))\n                .findFirst()\n                .orElse(null);\n    }\n\n    @Extension\n    public static final class DescriptorImpl extends JobPropertyDescriptor {\n        @Deprecated\n        protected transient List<JiraSite> sites;\n\n        @Override\n        @SuppressWarnings(\"unchecked\")\n        public boolean isApplicable(Class<? extends Job> jobType) {\n            return Job.class.isAssignableFrom(jobType);\n        }\n\n        @Override\n        public String getDisplayName() {\n            return Messages.JiraProjectProperty_DisplayName();\n        }\n\n        /**\n         * @param site the Jira site\n         *\n         * @deprecated use {@link JiraGlobalConfiguration#setSites(List)} instead\n         */\n        @Deprecated\n        public void setSites(JiraSite site) {\n            JiraGlobalConfiguration.get().getSites().add(site);\n        }\n\n        /**\n         * @return array of sites\n         *\n         * @deprecated use {@link JiraGlobalConfiguration#getSites()} instead\n         */\n        @Deprecated\n        public JiraSite[] getSites() {\n            return JiraGlobalConfiguration.get().getSites().toArray(new JiraSite[0]);\n        }\n\n        @SuppressWarnings(\"unused\") // Used by stapler\n        public ListBoxModel doFillSiteNameItems(@AncestorInPath AbstractFolder<?> folder) {\n            ListBoxModel items = new ListBoxModel();\n            for (JiraSite site : JiraGlobalConfiguration.get().getSites()) {\n                items.add(site.getName());\n            }\n            if (folder != null) {\n                List<JiraSite> sitesFromFolder = JiraFolderProperty.getSitesFromFolders(folder);\n                sitesFromFolder.stream().map(JiraSite::getName).forEach(items::add);\n            }\n            return items;\n        }\n\n        @SuppressWarnings(\"unused\") // Used to start migration after all extensions are loaded\n        @Initializer(after = InitMilestone.EXTENSIONS_AUGMENTED)\n        public void migrate() {\n            DescriptorImpl descriptor = (DescriptorImpl) Jenkins.getInstance().getDescriptor(JiraProjectProperty.class);\n            if (descriptor != null) {\n                descriptor.load(); // force readResolve without registering descriptor as configurable\n            }\n        }\n\n        @SuppressWarnings(\"deprecation\") // Migrate configuration\n        protected Object readResolve() {\n            if (sites != null) {\n                JiraGlobalConfiguration jiraGlobalConfiguration = (JiraGlobalConfiguration)\n                        Jenkins.getInstance().getDescriptorOrDie(JiraGlobalConfiguration.class);\n                jiraGlobalConfiguration.load();\n                jiraGlobalConfiguration.getSites().addAll(sites);\n                jiraGlobalConfiguration.save();\n                sites = null;\n                DescriptorImpl oldDescriptor =\n                        (DescriptorImpl) Jenkins.getInstance().getDescriptor(JiraProjectProperty.class);\n                if (oldDescriptor != null) {\n                    oldDescriptor.save();\n                }\n            }\n            return this;\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/JiraReleaseVersionUpdater.java",
    "content": "package hudson.plugins.jira;\n\nimport hudson.Extension;\nimport hudson.Launcher;\nimport hudson.model.AbstractBuild;\nimport hudson.model.AbstractProject;\nimport hudson.model.BuildListener;\nimport hudson.tasks.BuildStepDescriptor;\nimport hudson.tasks.BuildStepMonitor;\nimport hudson.tasks.Notifier;\nimport hudson.tasks.Publisher;\nimport net.sf.json.JSONObject;\nimport org.kohsuke.stapler.DataBoundConstructor;\nimport org.kohsuke.stapler.StaplerRequest2;\n\n/**\n * Task which releases the jira version specified in the parameters when the build completes.\n *\n * @author Justen Walker justen.walker@gmail.com\n * @deprecated Replaced by {@link JiraReleaseVersionUpdaterBuilder} which can be used as a PostBuild step with conditional triggering.<br>\n *     Kept for backward compatibility.\n */\n@Deprecated\npublic class JiraReleaseVersionUpdater extends Notifier {\n    private static final long serialVersionUID = 699563338312232811L;\n\n    private String jiraProjectKey;\n    private String jiraRelease;\n    private String jiraDescription;\n\n    @Deprecated\n    public JiraReleaseVersionUpdater(String jiraProjectKey, String jiraRelease) {\n        this.jiraRelease = jiraRelease;\n        this.jiraProjectKey = jiraProjectKey;\n    }\n\n    @DataBoundConstructor\n    public JiraReleaseVersionUpdater(String jiraProjectKey, String jiraRelease, String jiraDescription) {\n        this.jiraRelease = jiraRelease;\n        this.jiraProjectKey = jiraProjectKey;\n        this.jiraDescription = jiraDescription;\n    }\n\n    public String getJiraRelease() {\n        return jiraRelease;\n    }\n\n    public void setJiraRelease(String jiraRelease) {\n        this.jiraRelease = jiraRelease;\n    }\n\n    public String getJiraProjectKey() {\n        return jiraProjectKey;\n    }\n\n    public void setJiraProjectKey(String jiraProjectKey) {\n        this.jiraProjectKey = jiraProjectKey;\n    }\n\n    public String getJiraDescription() {\n        return jiraDescription;\n    }\n\n    public void setJiraDescription(String jiraDescription) {\n        this.jiraDescription = jiraDescription;\n    }\n\n    @Override\n    public BuildStepDescriptor<Publisher> getDescriptor() {\n        return DESCRIPTOR;\n    }\n\n    @Extension\n    public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl();\n\n    @Override\n    public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) {\n        return new VersionReleaser()\n                .perform(build.getProject(), jiraProjectKey, jiraRelease, jiraDescription, build, listener);\n    }\n\n    @Override\n    public BuildStepMonitor getRequiredMonitorService() {\n        return BuildStepMonitor.NONE;\n    }\n\n    public static class DescriptorImpl extends BuildStepDescriptor<Publisher> {\n\n        public DescriptorImpl() {\n            super(JiraReleaseVersionUpdater.class);\n        }\n\n        @Override\n        public JiraReleaseVersionUpdater newInstance(StaplerRequest2 req, JSONObject formData) throws FormException {\n            return req.bindJSON(JiraReleaseVersionUpdater.class, formData);\n        }\n\n        @Override\n        public boolean isApplicable(Class<? extends AbstractProject> jobType) {\n            return true;\n        }\n\n        @Override\n        public String getDisplayName() {\n            return Messages.JiraReleaseVersionBuilder_DisplayName();\n        }\n\n        @Override\n        public String getHelpFile() {\n            return \"/plugin/jira/help-release.html\";\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/JiraReleaseVersionUpdaterBuilder.java",
    "content": "package hudson.plugins.jira;\n\nimport hudson.EnvVars;\nimport hudson.Extension;\nimport hudson.model.AbstractProject;\nimport hudson.model.Descriptor;\nimport hudson.model.Run;\nimport hudson.model.TaskListener;\nimport hudson.tasks.BuildStepDescriptor;\nimport hudson.tasks.Builder;\nimport jenkins.tasks.SimpleBuildStep;\nimport net.sf.json.JSONObject;\nimport org.jenkinsci.Symbol;\nimport org.kohsuke.stapler.DataBoundConstructor;\nimport org.kohsuke.stapler.StaplerRequest2;\n\n/**\n * Created by Reda on 18/12/2014.\n */\npublic class JiraReleaseVersionUpdaterBuilder extends Builder implements SimpleBuildStep {\n\n    private String jiraProjectKey;\n    private String jiraRelease;\n    private String jiraDescription;\n\n    @Extension\n    public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl();\n\n    @Deprecated\n    public JiraReleaseVersionUpdaterBuilder(String jiraProjectKey, String jiraRelease) {\n        this.jiraRelease = jiraRelease;\n        this.jiraProjectKey = jiraProjectKey;\n    }\n\n    @DataBoundConstructor\n    public JiraReleaseVersionUpdaterBuilder(String jiraProjectKey, String jiraRelease, String jiraDescription) {\n        this.jiraRelease = jiraRelease;\n        this.jiraProjectKey = jiraProjectKey;\n        this.jiraDescription = jiraDescription;\n    }\n\n    public String getJiraRelease() {\n        return jiraRelease;\n    }\n\n    public void setJiraRelease(String jiraRelease) {\n        this.jiraRelease = jiraRelease;\n    }\n\n    public String getJiraProjectKey() {\n        return jiraProjectKey;\n    }\n\n    public void setJiraProjectKey(String jiraProjectKey) {\n        this.jiraProjectKey = jiraProjectKey;\n    }\n\n    public String getJiraDescription() {\n        return jiraDescription;\n    }\n\n    public void setJiraDescription(String jiraDescription) {\n        this.jiraDescription = jiraDescription;\n    }\n\n    @Override\n    public void perform(Run<?, ?> run, EnvVars env, TaskListener listener) {\n        new VersionReleaser().perform(run.getParent(), jiraProjectKey, jiraRelease, jiraDescription, run, listener);\n    }\n\n    @Override\n    public boolean requiresWorkspace() {\n        return false;\n    }\n\n    @Override\n    public Descriptor<Builder> getDescriptor() {\n        return DESCRIPTOR;\n    }\n\n    @Symbol(\"jiraMarkVersionReleased\")\n    public static final class DescriptorImpl extends BuildStepDescriptor<Builder> {\n\n        private DescriptorImpl() {\n            super(JiraReleaseVersionUpdaterBuilder.class);\n        }\n\n        @Override\n        public boolean isApplicable(Class<? extends AbstractProject> jobType) {\n            return true;\n        }\n\n        @Override\n        public String getDisplayName() {\n            // Placed in the build settings section\n            return Messages.JiraReleaseVersionBuilder_DisplayName();\n        }\n\n        @Override\n        public String getHelpFile() {\n            return \"/plugin/jira/help.html\";\n        }\n\n        @Override\n        public JiraReleaseVersionUpdaterBuilder newInstance(StaplerRequest2 req, JSONObject formData)\n                throws FormException {\n            return req.bindJSON(JiraReleaseVersionUpdaterBuilder.class, formData);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/JiraRestService.java",
    "content": "/*\n * Copyright 2015 Hao Cheng Lee\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage hudson.plugins.jira;\n\nimport static java.util.logging.Level.FINE;\nimport static java.util.logging.Level.INFO;\nimport static java.util.logging.Level.WARNING;\n\nimport com.atlassian.jira.rest.client.api.RestClientException;\nimport com.atlassian.jira.rest.client.api.domain.BasicIssue;\nimport com.atlassian.jira.rest.client.api.domain.BasicProject;\nimport com.atlassian.jira.rest.client.api.domain.BasicUser;\nimport com.atlassian.jira.rest.client.api.domain.Comment;\nimport com.atlassian.jira.rest.client.api.domain.Component;\nimport com.atlassian.jira.rest.client.api.domain.Issue;\nimport com.atlassian.jira.rest.client.api.domain.IssueFieldId;\nimport com.atlassian.jira.rest.client.api.domain.IssueType;\nimport com.atlassian.jira.rest.client.api.domain.Permissions;\nimport com.atlassian.jira.rest.client.api.domain.Priority;\nimport com.atlassian.jira.rest.client.api.domain.SearchResult;\nimport com.atlassian.jira.rest.client.api.domain.Status;\nimport com.atlassian.jira.rest.client.api.domain.Transition;\nimport com.atlassian.jira.rest.client.api.domain.User;\nimport com.atlassian.jira.rest.client.api.domain.Version;\nimport com.atlassian.jira.rest.client.api.domain.input.ComplexIssueInputFieldValue;\nimport com.atlassian.jira.rest.client.api.domain.input.FieldInput;\nimport com.atlassian.jira.rest.client.api.domain.input.IssueInput;\nimport com.atlassian.jira.rest.client.api.domain.input.IssueInputBuilder;\nimport com.atlassian.jira.rest.client.api.domain.input.TransitionInput;\nimport com.fasterxml.jackson.core.type.TypeReference;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport edu.umd.cs.findbugs.annotations.NonNull;\nimport edu.umd.cs.findbugs.annotations.Nullable;\nimport hudson.ProxyConfiguration;\nimport hudson.plugins.jira.extension.ExtendedJiraRestClient;\nimport hudson.plugins.jira.extension.ExtendedVersion;\nimport hudson.plugins.jira.extension.ExtendedVersionInput;\nimport hudson.plugins.jira.model.JiraIssueField;\nimport java.io.IOException;\nimport java.io.UnsupportedEncodingException;\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.CancellationException;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\nimport java.util.logging.Logger;\nimport java.util.stream.Collectors;\nimport java.util.stream.StreamSupport;\nimport jenkins.model.Jenkins;\nimport org.apache.commons.codec.binary.Base64;\nimport org.apache.commons.lang3.StringUtils;\nimport org.apache.http.HttpHost;\nimport org.apache.http.client.fluent.Content;\nimport org.apache.http.client.fluent.Request;\nimport org.apache.http.client.utils.URIBuilder;\nimport org.joda.time.DateTime;\nimport org.joda.time.format.DateTimeFormat;\nimport org.joda.time.format.DateTimeFormatter;\n\npublic class JiraRestService {\n\n    private static final Logger LOGGER = Logger.getLogger(JiraRestService.class.getName());\n\n    public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormat.forPattern(\"yyyy-MM-dd\");\n\n    /**\n     * Base URI path for a REST API call. It must be relative to site's base\n     * URI.\n     */\n    public static final String BASE_API_PATH = \"rest/api/2\";\n\n    static final long BUG_ISSUE_TYPE_ID = 1L;\n\n    private final URI uri;\n\n    private final ExtendedJiraRestClient jiraRestClient;\n\n    private final ObjectMapper objectMapper;\n\n    private final String authHeader;\n\n    private final String baseApiPath;\n\n    private final int timeout;\n\n    @Deprecated\n    public JiraRestService(URI uri, ExtendedJiraRestClient jiraRestClient, String username, String password) {\n        this(uri, jiraRestClient, username, password, JiraSite.DEFAULT_TIMEOUT);\n    }\n\n    public JiraRestService(\n            URI uri, ExtendedJiraRestClient jiraRestClient, String username, String password, int timeout) {\n        this.uri = uri;\n        this.objectMapper = new ObjectMapper();\n        this.timeout = timeout;\n        final String login = username + \":\" + password;\n        try {\n            byte[] encodeBase64 = Base64.encodeBase64(login.getBytes(\"UTF-8\"));\n            this.authHeader = \"Basic \" + new String(encodeBase64, \"UTF-8\");\n        } catch (UnsupportedEncodingException e) {\n            LOGGER.warning(\"Jira REST encode username:password error. cause: \" + e.getMessage());\n            throw new RuntimeException(\"failed to encode username:password using Base64\");\n        }\n        this.jiraRestClient = jiraRestClient;\n        baseApiPath = buildBaseApiPath(uri);\n    }\n\n    public JiraRestService(URI uri, ExtendedJiraRestClient jiraRestClient, String token, int timeout) {\n        this.uri = uri;\n        this.objectMapper = new ObjectMapper();\n        this.timeout = timeout;\n        this.authHeader = \"Bearer \" + token;\n        this.jiraRestClient = jiraRestClient;\n        baseApiPath = buildBaseApiPath(uri);\n    }\n\n    private String buildBaseApiPath(URI uri) {\n        final StringBuilder builder = new StringBuilder();\n        if (uri.getPath() != null) {\n            builder.append(uri.getPath());\n            if (!uri.getPath().endsWith(\"/\")) {\n                builder.append('/');\n            }\n        } else {\n            builder.append('/');\n        }\n        builder.append(BASE_API_PATH);\n        return builder.toString();\n    }\n\n    public void addComment(String issueId, String commentBody, String groupVisibility, String roleVisibility) {\n        final URIBuilder builder =\n                new URIBuilder(uri).setPath(String.format(\"%s/issue/%s/comment\", baseApiPath, issueId));\n\n        final Comment comment;\n        if (StringUtils.isNotBlank(groupVisibility)) {\n            comment = Comment.createWithGroupLevel(commentBody, groupVisibility);\n        } else if (StringUtils.isNotBlank(roleVisibility)) {\n            comment = Comment.createWithRoleLevel(commentBody, roleVisibility);\n        } else {\n            comment = Comment.valueOf(commentBody);\n        }\n\n        try {\n            jiraRestClient.getIssueClient().addComment(builder.build(), comment).get(timeout, TimeUnit.SECONDS);\n        } catch (RestClientException\n                | URISyntaxException\n                | InterruptedException\n                | ExecutionException\n                | TimeoutException e) {\n            LOGGER.log(WARNING, \"Jira REST client add comment error. cause: \" + e.getMessage(), e);\n            throw new RestClientException(\n                    \"[Jira] Jira REST client add comment error. cause: \" + e.getMessage(), e.getCause());\n        }\n    }\n\n    public Issue getIssue(String issueKey) {\n        LOGGER.log(FINE, \"[Jira] Fetching issue {0}\", issueKey);\n        try {\n            return jiraRestClient.getIssueClient().getIssue(issueKey).get(timeout, TimeUnit.SECONDS);\n        } catch (RestClientException | InterruptedException | ExecutionException | TimeoutException e) {\n            if (e.getCause() != null\n                    && e.getCause() instanceof RestClientException\n                    && ((RestClientException) e.getCause()).getStatusCode().isPresent()\n                    && ((RestClientException) e.getCause()).getStatusCode().get() == 404) {\n                LOGGER.log(INFO, \"Issue '\" + issueKey + \"' not found in Jira.\");\n                throw new RestClientException(\"[Jira] Issue '\" + issueKey + \"' not found in Jira.\", e.getCause());\n            } else {\n                LOGGER.log(WARNING, \"Jira REST client get issue error. cause: \" + e.getMessage(), e);\n                throw new RestClientException(\n                        \"[Jira] Jira REST client get issue error. cause: \" + e.getMessage(), e.getCause());\n            }\n        }\n    }\n\n    public List<IssueType> getIssueTypes() {\n        try {\n            return StreamSupport.stream(\n                            jiraRestClient\n                                    .getMetadataClient()\n                                    .getIssueTypes()\n                                    .get(timeout, TimeUnit.SECONDS)\n                                    .spliterator(),\n                            false)\n                    .collect(Collectors.toList());\n        } catch (RestClientException | InterruptedException | ExecutionException | TimeoutException e) {\n            LOGGER.log(WARNING, \"Jira REST client get issue types error. cause: \" + e.getMessage(), e);\n            throw new RestClientException(\n                    \"[Jira] Jira REST client get issue types error. cause: \" + e.getMessage(), e.getCause());\n        }\n    }\n\n    public List<Priority> getPriorities() {\n        try {\n            return StreamSupport.stream(\n                            jiraRestClient\n                                    .getMetadataClient()\n                                    .getPriorities()\n                                    .get(timeout, TimeUnit.SECONDS)\n                                    .spliterator(),\n                            false)\n                    .collect(Collectors.toList());\n        } catch (RestClientException | InterruptedException | ExecutionException | TimeoutException e) {\n            LOGGER.log(WARNING, \"Jira REST client get priorities error. cause: \" + e.getMessage(), e);\n            throw new RestClientException(\n                    \"[Jira] Jira REST client get priorities error. cause: \" + e.getMessage(), e.getCause());\n        }\n    }\n\n    public List<String> getProjectsKeys() {\n        Iterable<BasicProject> projects = Collections.emptyList();\n        try {\n            projects = jiraRestClient.getProjectClient().getAllProjects().get(timeout, TimeUnit.SECONDS);\n        } catch (RestClientException | InterruptedException | ExecutionException | TimeoutException e) {\n            LOGGER.log(WARNING, \"Jira REST client get project keys error. cause: \" + e.getMessage(), e);\n            throw new RestClientException(\n                    \"[Jira] Jira REST client get project keys error. cause: \" + e.getMessage(), e.getCause());\n        }\n        final List<String> keys = new ArrayList<>();\n        for (BasicProject project : projects) {\n            keys.add(project.getKey());\n        }\n        return keys;\n    }\n\n    public List<Issue> getIssuesFromJqlSearch(String jqlSearch, Integer maxResults) throws RestClientException {\n        LOGGER.log(FINE, \"[Jira] Executing JQL: {0}\", jqlSearch);\n        try {\n            Set<String> neededFields = new HashSet<>(\n                    Arrays.asList(\"summary\", \"issuetype\", \"created\", \"updated\", \"project\", \"status\", \"fixVersions\"));\n\n            final SearchResult searchResult = jiraRestClient\n                    .getSearchClient()\n                    .searchJql(jqlSearch, maxResults, 0, neededFields)\n                    .get(timeout, TimeUnit.SECONDS);\n            return StreamSupport.stream(searchResult.getIssues().spliterator(), false)\n                    .collect(Collectors.toList());\n        } catch (RestClientException\n                | TimeoutException\n                | CancellationException\n                | ExecutionException\n                | InterruptedException e) {\n            LOGGER.log(WARNING, \"Jira REST client get issue from jql search error. cause: \" + e.getMessage(), e);\n            throw new RestClientException(\n                    \"[Jira] Jira REST client get issue from jql search error. cause: \" + e.getMessage(), e.getCause());\n        }\n    }\n\n    public List<ExtendedVersion> getVersions(String projectKey) {\n        final URIBuilder builder =\n                new URIBuilder(uri).setPath(String.format(\"%s/project/%s/versions\", baseApiPath, projectKey));\n\n        List<Map<String, Object>> decoded = Collections.emptyList();\n        try {\n            URI uri = builder.build();\n            final Content content = buildGetRequest(uri).execute().returnContent();\n\n            decoded = objectMapper.readValue(content.asString(), new TypeReference<List<Map<String, Object>>>() {});\n        } catch (URISyntaxException | IOException e) {\n            LOGGER.log(WARNING, \"Jira REST client get versions error. cause: \" + e.getMessage(), e);\n            throw new RestClientException(\n                    \"[Jira] Jira REST client get versions error. cause: \" + e.getMessage(), e.getCause());\n        }\n\n        return decoded.stream()\n                .map(decodedVersion -> {\n                    final DateTime startDate = decodedVersion.containsKey(\"startDate\")\n                            ? DATE_TIME_FORMATTER.parseDateTime((String) decodedVersion.get(\"startDate\"))\n                            : null;\n                    final DateTime releaseDate = decodedVersion.containsKey(\"releaseDate\")\n                            ? DATE_TIME_FORMATTER.parseDateTime((String) decodedVersion.get(\"releaseDate\"))\n                            : null;\n                    return new ExtendedVersion(\n                            URI.create((String) decodedVersion.get(\"self\")),\n                            Long.parseLong((String) decodedVersion.get(\"id\")),\n                            (String) decodedVersion.get(\"name\"),\n                            (String) decodedVersion.get(\"description\"),\n                            (Boolean) decodedVersion.get(\"archived\"),\n                            (Boolean) decodedVersion.get(\"released\"),\n                            startDate,\n                            releaseDate);\n                })\n                .collect(Collectors.toList());\n    }\n\n    public Version addVersion(String projectKey, String versionName) {\n        final ExtendedVersionInput versionInput =\n                new ExtendedVersionInput(projectKey, versionName, null, DateTime.now(), null, false, false);\n        try {\n            return jiraRestClient\n                    .getExtendedVersionRestClient()\n                    .createExtendedVersion(versionInput)\n                    .get(timeout, TimeUnit.SECONDS);\n        } catch (RestClientException | InterruptedException | ExecutionException | TimeoutException e) {\n            LOGGER.log(WARNING, \"Jira REST client add version error. cause: \" + e.getMessage(), e);\n            throw new RestClientException(\n                    \"[Jira] Jira REST client add version error. cause: \" + e.getMessage(), e.getCause());\n        }\n    }\n\n    public void releaseVersion(String projectKey, ExtendedVersion version) {\n        final URIBuilder builder =\n                new URIBuilder(uri).setPath(String.format(\"%s/version/%s\", baseApiPath, version.getId()));\n\n        final ExtendedVersionInput versionInput = new ExtendedVersionInput(\n                projectKey,\n                version.getName(),\n                version.getDescription(),\n                version.getStartDate(),\n                version.getReleaseDate(),\n                version.isArchived(),\n                version.isReleased());\n\n        try {\n            jiraRestClient\n                    .getExtendedVersionRestClient()\n                    .updateExtendedVersion(builder.build(), versionInput)\n                    .get(timeout, TimeUnit.SECONDS);\n        } catch (RestClientException\n                | URISyntaxException\n                | InterruptedException\n                | ExecutionException\n                | TimeoutException e) {\n            LOGGER.log(WARNING, \"Jira REST client release version error. cause: \" + e.getMessage(), e);\n            throw new RestClientException(\n                    \"[Jira] Jira REST client release version error. cause: \" + e.getMessage(), e.getCause());\n        }\n    }\n\n    @Deprecated\n    public BasicIssue createIssue(\n            String projectKey, String description, String assignee, Iterable<String> components, String summary) {\n        return createIssue(projectKey, description, assignee, components, summary, BUG_ISSUE_TYPE_ID, null);\n    }\n\n    public BasicIssue createIssue(\n            String projectKey,\n            String description,\n            String assignee,\n            Iterable<String> components,\n            String summary,\n            @NonNull Long issueTypeId,\n            @Nullable Long priorityId) {\n        IssueInputBuilder builder = new IssueInputBuilder();\n        builder.setProjectKey(projectKey)\n                .setDescription(description)\n                .setIssueTypeId(issueTypeId)\n                .setSummary(summary);\n\n        if (priorityId != null) {\n            builder.setPriorityId(priorityId);\n        }\n\n        if (StringUtils.isNotBlank(assignee)) {\n            final Map<String, Object> valuesMap = new HashMap<>(2);\n            valuesMap.put(\"name\", assignee); // server\n            valuesMap.put(\"accountId\", assignee); // cloud\n            // Need to use \"accountId\" as specified here:\n            //\n            // https://developer.atlassian.com/cloud/jira/platform/deprecation-notice-user-privacy-api-migration-guide/\n            //\n            // See upstream fix for setAssigneeName:\n            //\n            // https://bitbucket.org/atlassian/jira-rest-java-client/pull-requests/104/change-field-name-from-name-to-id-for/diff\n            builder.setFieldInput(\n                    new FieldInput(IssueFieldId.ASSIGNEE_FIELD, new ComplexIssueInputFieldValue(valuesMap)));\n        }\n\n        if (StreamSupport.stream(components.spliterator(), false).count() > 0) {\n            builder.setComponentsNames(components);\n        }\n\n        final IssueInput issueInput = builder.build();\n\n        try {\n            return jiraRestClient.getIssueClient().createIssue(issueInput).get(timeout, TimeUnit.SECONDS);\n        } catch (RestClientException | InterruptedException | ExecutionException | TimeoutException e) {\n            LOGGER.log(WARNING, \"Jira REST createIssue error: \" + e.getMessage(), e);\n            throw new RestClientException(\"[Jira] Jira REST createIssue error. cause: \" + e.getMessage(), e.getCause());\n        }\n    }\n\n    public User getUser(String username) {\n        try {\n            return jiraRestClient.getUserClient().getUser(username).get(timeout, TimeUnit.SECONDS);\n        } catch (RestClientException | InterruptedException | ExecutionException | TimeoutException e) {\n            if (e.getCause() != null\n                    && e.getCause() instanceof RestClientException\n                    && ((RestClientException) e.getCause()).getStatusCode().isPresent()\n                    && ((RestClientException) e.getCause()).getStatusCode().get() == 404) {\n                LOGGER.log(INFO, \"User '\" + username + \"' not found in Jira.\");\n                throw new RestClientException(\"[Jira] User '\" + username + \"' not found in Jira.\", e.getCause());\n            } else {\n                LOGGER.log(WARNING, \"Jira REST client get user error. cause: \" + e.getMessage(), e);\n                throw new RestClientException(\n                        \"[Jira] Jira REST client get user error. cause: \" + e.getMessage(), e.getCause());\n            }\n        }\n    }\n\n    public void updateIssue(String issueKey, List<Version> fixVersions) {\n        final IssueInput issueInput =\n                new IssueInputBuilder().setFixVersions(fixVersions).build();\n        try {\n            jiraRestClient.getIssueClient().updateIssue(issueKey, issueInput).get(timeout, TimeUnit.SECONDS);\n        } catch (RestClientException | InterruptedException | ExecutionException | TimeoutException e) {\n            LOGGER.log(WARNING, \"Jira REST client update issue error. cause: \" + e.getMessage(), e);\n            throw new RestClientException(\n                    \"[Jira] Jira REST client update issue error. cause: \" + e.getMessage(), e.getCause());\n        }\n    }\n\n    public void setIssueLabels(String issueKey, List<String> labels) {\n        final IssueInput issueInput = new IssueInputBuilder()\n                .setFieldValue(IssueFieldId.LABELS_FIELD.id, labels)\n                .build();\n        try {\n            jiraRestClient.getIssueClient().updateIssue(issueKey, issueInput).get(timeout, TimeUnit.SECONDS);\n        } catch (RestClientException | InterruptedException | ExecutionException | TimeoutException e) {\n            LOGGER.log(WARNING, \"Jira REST client update labels error for issue \" + issueKey, e);\n            throw new RestClientException(\n                    \"[Jira] Jira REST client update labels error for issue: \" + issueKey + \". cause: \" + e.getMessage(),\n                    e.getCause());\n        }\n    }\n\n    public void setIssueFields(String issueKey, List<JiraIssueField> fields) {\n        IssueInputBuilder builder = new IssueInputBuilder();\n        for (JiraIssueField field : fields) {\n            builder.setFieldValue(field.getId(), field.getValue());\n        }\n        final IssueInput issueInput = builder.build();\n\n        try {\n            jiraRestClient.getIssueClient().updateIssue(issueKey, issueInput).get(timeout, TimeUnit.SECONDS);\n        } catch (RestClientException | InterruptedException | ExecutionException | TimeoutException e) {\n            LOGGER.log(WARNING, \"Jira REST client update fields error for issue \" + issueKey, e);\n            throw new RestClientException(\n                    \"[Jira] Jira REST client update fields error for issue: \" + issueKey + \". cause: \" + e.getMessage(),\n                    e.getCause());\n        }\n    }\n\n    public Issue progressWorkflowAction(String issueKey, Integer actionId) {\n        final TransitionInput transitionInput = new TransitionInput(actionId);\n\n        final Issue issue = getIssue(issueKey);\n\n        try {\n            jiraRestClient.getIssueClient().transition(issue, transitionInput).get(timeout, TimeUnit.SECONDS);\n        } catch (RestClientException | InterruptedException | ExecutionException | TimeoutException e) {\n            LOGGER.log(WARNING, \"Jira REST client process workflow action error. cause: \" + e.getMessage(), e);\n            throw new RestClientException(\n                    \"[Jira] Jira REST client process workflow action error. cause: \" + e.getMessage(), e.getCause());\n        }\n        return issue;\n    }\n\n    public List<Transition> getAvailableActions(String issueKey) {\n        final Issue issue = getIssue(issueKey);\n\n        try {\n            final Iterable<Transition> transitions =\n                    jiraRestClient.getIssueClient().getTransitions(issue).get(timeout, TimeUnit.SECONDS);\n            return StreamSupport.stream(transitions.spliterator(), false).collect(Collectors.toList());\n        } catch (RestClientException | InterruptedException | ExecutionException | TimeoutException e) {\n            LOGGER.log(WARNING, \"Jira REST client get available actions error. cause: \" + e.getMessage(), e);\n            throw new RestClientException(\n                    \"[Jira] Jira REST client get available actions error. cause: \" + e.getMessage(), e.getCause());\n        }\n    }\n\n    public List<Status> getStatuses() {\n        try {\n            final Iterable<Status> statuses =\n                    jiraRestClient.getMetadataClient().getStatuses().get(timeout, TimeUnit.SECONDS);\n            return StreamSupport.stream(statuses.spliterator(), false).collect(Collectors.toList());\n        } catch (RestClientException | InterruptedException | ExecutionException | TimeoutException e) {\n            LOGGER.log(WARNING, \"Jira REST client get statuses error. cause: \" + e.getMessage(), e);\n            throw new RestClientException(\n                    \"[Jira] Jira REST client get statuses error. cause: \" + e.getMessage(), e.getCause());\n        }\n    }\n\n    public List<Component> getComponents(String projectKey) {\n        final URIBuilder builder =\n                new URIBuilder(uri).setPath(String.format(\"%s/project/%s/components\", baseApiPath, projectKey));\n\n        try {\n            final Content content = buildGetRequest(builder.build()).execute().returnContent();\n            final List<Map<String, Object>> decoded =\n                    objectMapper.readValue(content.asString(), new TypeReference<List<Map<String, Object>>>() {});\n\n            final List<Component> components = new ArrayList<>();\n            for (final Map<String, Object> decodeComponent : decoded) {\n                BasicUser lead = null;\n                if (decodeComponent.containsKey(\"lead\")) {\n                    final Map<String, Object> decodedLead = (Map<String, Object>) decodeComponent.get(\"lead\");\n                    lead = new BasicUser(\n                            URI.create((String) decodedLead.get(\"self\")),\n                            (String) decodedLead.get(\"name\"),\n                            (String) decodedLead.get(\"displayName\"),\n                            (String) decodedLead.get(\"accountId\"));\n                }\n                final Component component = new Component(\n                        URI.create((String) decodeComponent.get(\"self\")),\n                        Long.parseLong((String) decodeComponent.get(\"id\")),\n                        (String) decodeComponent.get(\"name\"),\n                        (String) decodeComponent.get(\"description\"),\n                        lead);\n                components.add(component);\n            }\n\n            return components;\n        } catch (URISyntaxException | IOException e) {\n            LOGGER.log(WARNING, \"Jira REST client process workflow action error. cause: \" + e.getMessage(), e);\n            throw new RestClientException(\n                    \"[Jira] Jira REST client process workflow action error. cause: \" + e.getMessage(), e.getCause());\n        }\n    }\n\n    private Request buildGetRequest(URI uri) {\n        Request request = Request.Get(uri);\n        ProxyConfiguration proxyConfiguration = Jenkins.get().proxy;\n        if (proxyConfiguration != null) {\n            final HttpHost proxyHost = new HttpHost(proxyConfiguration.name, proxyConfiguration.port);\n\n            boolean shouldByPassProxy = proxyConfiguration.getNoProxyHostPatterns().stream()\n                    .anyMatch(it -> it.matcher(uri.getHost()).matches());\n\n            if (!shouldByPassProxy) {\n                request.viaProxy(proxyHost);\n            }\n        }\n\n        return request.connectTimeout(timeoutInMilliseconds())\n                .socketTimeout(timeoutInMilliseconds())\n                .addHeader(\"Authorization\", authHeader)\n                .addHeader(\"Content-Type\", \"application/json\");\n    }\n\n    protected int timeoutInMilliseconds() {\n        return (int) TimeUnit.SECONDS.toMillis(timeout);\n    }\n\n    public String getBaseApiPath() {\n        return baseApiPath;\n    }\n\n    /**\n     * Get User's permissions\n     */\n    public Permissions getMyPermissions() {\n        return jiraRestClient\n                .getExtendedMyPermissionsRestClient()\n                .getMyPermissions()\n                .claim();\n    }\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/JiraSession.java",
    "content": "package hudson.plugins.jira;\n\nimport static org.apache.commons.lang3.StringUtils.isNotEmpty;\n\nimport com.atlassian.jira.rest.client.api.RestClientException;\nimport com.atlassian.jira.rest.client.api.domain.BasicIssue;\nimport com.atlassian.jira.rest.client.api.domain.Component;\nimport com.atlassian.jira.rest.client.api.domain.Issue;\nimport com.atlassian.jira.rest.client.api.domain.IssueType;\nimport com.atlassian.jira.rest.client.api.domain.Permissions;\nimport com.atlassian.jira.rest.client.api.domain.Priority;\nimport com.atlassian.jira.rest.client.api.domain.Status;\nimport com.atlassian.jira.rest.client.api.domain.Transition;\nimport com.atlassian.jira.rest.client.api.domain.Version;\nimport edu.umd.cs.findbugs.annotations.NonNull;\nimport edu.umd.cs.findbugs.annotations.Nullable;\nimport hudson.plugins.jira.extension.ExtendedVersion;\nimport hudson.plugins.jira.model.JiraIssueField;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.concurrent.TimeoutException;\nimport java.util.logging.Logger;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\nimport org.apache.commons.lang3.StringUtils;\n\n/**\n * Connection to Jira.\n * Jira has a built-in timeout for a session, so after some inactive period the\n * session will become invalid. The caller must make sure that this doesn't\n * happen.\n *\n * @author Kohsuke Kawaguchi\n */\npublic class JiraSession {\n    private static final Logger LOGGER = Logger.getLogger(JiraSession.class.getName());\n\n    public final JiraRestService service;\n\n    /**\n     * Lazily computed list of project keys.\n     */\n    private Set<String> projectKeys;\n\n    private final String jiraSiteName;\n\n    private final Integer maxIssuesFromJqlSearch;\n\n    /* package */ JiraSession(JiraSite site, JiraRestService jiraRestService) {\n        this.service = jiraRestService;\n        this.jiraSiteName = site.getName();\n        this.maxIssuesFromJqlSearch = site.getMaxIssuesFromJqlSearch();\n    }\n\n    /**\n     * Returns the set of project keys (like MNG, JENKINS, etc) that are\n     * available in this Jira.\n     * Guarantees to return all project keys in upper case.\n     */\n    public Set<String> getProjectKeys() {\n        if (projectKeys == null) {\n            LOGGER.fine(\"Fetching remote project key list from \" + jiraSiteName);\n            final List<String> keys = service.getProjectsKeys();\n            projectKeys = new HashSet<>(keys);\n            LOGGER.fine(\"Project list=\" + projectKeys);\n        }\n        return projectKeys;\n    }\n\n    /**\n     * Adds a comment to the existing issue. Constrains the visibility of the\n     * comment the the supplied groupVisibility.\n     */\n    public void addComment(String issueId, String comment, String groupVisibility, String roleVisibility) {\n        service.addComment(issueId, comment, groupVisibility, roleVisibility);\n    }\n\n    /**\n     * Adds new labels to the existing issue.\n     * Old labels remains untouched.\n     *\n     * @param issueId Jira issue ID like \"MNG-1235\".\n     * @param labels New labels to add.\n     */\n    public void addLabels(String issueId, List<String> labels) {\n        List<String> newLabels = new ArrayList();\n        Issue existingIssue = service.getIssue(issueId);\n        if (existingIssue.getLabels() != null) {\n            newLabels.addAll(existingIssue.getLabels());\n        }\n        boolean changed = false;\n        for (String label : labels) {\n            if (!newLabels.contains(label)) {\n                newLabels.add(label);\n                changed = true;\n            }\n        }\n        if (changed) {\n            service.setIssueLabels(issueId, newLabels);\n        }\n    }\n\n    /**\n     * Adds new to or updates existing fields of the issue.\n     * Can add or update custom fields.\n     *\n     * @param issueId Jira issue ID like \"PRJ-123\"\n     * @param fields Fields to add or update\n     */\n    public void addFields(String issueId, List<JiraIssueField> fields) {\n        service.setIssueFields(issueId, fields);\n    }\n\n    /**\n     * Gets the details of one issue.\n     *\n     * @param id Issue ID like \"MNG-1235\".\n     * @return null if no such issue exists.\n     */\n    public Issue getIssue(String id) {\n        return service.getIssue(id);\n    }\n\n    /**\n     * Gets all issues that match the given JQL filter\n     *\n     * @param jqlSearch JQL query string to execute\n     * @return issues matching the JQL query\n     */\n    public List<Issue> getIssuesFromJqlSearch(final String jqlSearch) throws RestClientException {\n        return service.getIssuesFromJqlSearch(jqlSearch, maxIssuesFromJqlSearch);\n    }\n\n    /**\n     * Get all versions from the given project\n     *\n     * @param projectKey The key for the project\n     * @return An array of versions\n     */\n    public List<ExtendedVersion> getVersions(String projectKey) {\n        LOGGER.fine(\"Fetching versions from project: \" + projectKey);\n        return service.getVersions(projectKey);\n    }\n\n    /**\n     * Get a version by its name\n     *\n     * @param projectKey The key for the project\n     * @param name       The version name\n     * @return A RemoteVersion, or null if not found\n     */\n    public ExtendedVersion getVersionByName(String projectKey, String name) {\n        LOGGER.fine(\"Fetching versions from project: \" + projectKey);\n        List<ExtendedVersion> versions = getVersions(projectKey);\n        if (versions == null) {\n            return null;\n        }\n        for (ExtendedVersion version : versions) {\n            if (version.getName().equals(name)) {\n                return version;\n            }\n        }\n        return null;\n    }\n\n    public List<Issue> getIssuesWithFixVersion(String projectKey, String version) throws TimeoutException {\n        return getIssuesWithFixVersion(projectKey, version, \"\");\n    }\n\n    public List<Issue> getIssuesWithFixVersion(String projectKey, String version, String filter)\n            throws TimeoutException {\n        LOGGER.fine(\"Fetching versions from project: \" + projectKey + \" with fixVersion:\" + version);\n        if (isNotEmpty(filter)) {\n            return service.getIssuesFromJqlSearch(\n                    String.format(\"project = \\\"%s\\\" and fixVersion = \\\"%s\\\" and \" + filter, projectKey, version),\n                    maxIssuesFromJqlSearch);\n        }\n        return service.getIssuesFromJqlSearch(\n                String.format(\"project = \\\"%s\\\" and fixVersion = \\\"%s\\\"\", projectKey, version), maxIssuesFromJqlSearch);\n    }\n\n    /**\n     * Get all issue types\n     *\n     * @return An array of issue types\n     */\n    public List<IssueType> getIssueTypes() {\n        LOGGER.fine(\"Fetching issue types\");\n        return service.getIssueTypes();\n    }\n\n    /**\n     * Get all priorities\n     *\n     * @return An array of priorities\n     */\n    public List<Priority> getPriorities() {\n        LOGGER.fine(\"Fetching priorities\");\n        return service.getPriorities();\n    }\n\n    /**\n     * Release given version in given project\n     */\n    public void releaseVersion(String projectKey, ExtendedVersion version) {\n        LOGGER.fine(\"Releasing version: \" + version.getName());\n        service.releaseVersion(projectKey, version);\n    }\n\n    /**\n     * Replaces the fix version list of all issues matching the JQL Query with the version specified.\n     *\n     * @param projectKey The Jira Project key\n     * @param version    The replacement version\n     * @param query      The JQL Query\n     */\n    public void migrateIssuesToFixVersion(String projectKey, String version, String query) throws TimeoutException {\n\n        Version newVersion = getVersionByName(projectKey, version);\n        if (newVersion == null) {\n            LOGGER.warning(\"Version \" + version + \" was not found\");\n            return;\n        }\n\n        LOGGER.fine(\"Fetching versions with JQL:\" + query);\n        List<Issue> issues = service.getIssuesFromJqlSearch(query, maxIssuesFromJqlSearch);\n        if (issues == null || issues.isEmpty()) {\n            return;\n        }\n        LOGGER.fine(\"Found issues: \" + issues.size());\n\n        issues.stream().forEach(issue -> {\n            LOGGER.fine(\"Migrating issue: \" + issue.getKey());\n            service.updateIssue(issue.getKey(), Collections.singletonList(newVersion));\n        });\n    }\n\n    /**\n     * Replaces the given fromVersion with toVersion in all issues matching the JQL query.\n     *\n     * @param projectKey  The Jira Project\n     * @param fromVersion The name of the version to replace\n     * @param toVersion   The name of the replacement version\n     * @param query       The JQL Query\n     */\n    public void replaceFixVersion(String projectKey, String fromVersion, String toVersion, String query)\n            throws TimeoutException, RestClientException {\n\n        Version newVersion = getVersionByName(projectKey, toVersion);\n        if (newVersion == null) {\n            LOGGER.warning(\"Version \" + toVersion + \" was not found\");\n            return;\n        }\n\n        LOGGER.fine(\"Fetching versions with JQL:\" + query);\n        List<Issue> issues = service.getIssuesFromJqlSearch(query, maxIssuesFromJqlSearch);\n        if (issues == null) {\n            return;\n        }\n        LOGGER.fine(\"Found issues: \" + issues.size());\n\n        for (Issue issue : issues) {\n            Set<Version> newVersions = new HashSet<>();\n            newVersions.add(newVersion);\n\n            Iterable<Version> issueVersions =\n                    Optional.ofNullable(issue.getFixVersions()).orElse(Collections.emptyList());\n            LOGGER.fine(String.format(\"[%s] current versions: %s\", issue.getKey(), issueVersions));\n\n            if (StringUtils.startsWith(fromVersion, \"/\") && StringUtils.endsWith(fromVersion, \"/\")) {\n\n                String regEx = StringUtils.removeStart(fromVersion, \"/\");\n                regEx = StringUtils.removeEnd(regEx, \"/\");\n                Pattern fromVersionPattern = Pattern.compile(regEx);\n                LOGGER.fine(\"Using regular expression: \" + regEx);\n\n                for (Version currentVersion : issueVersions) {\n                    Matcher versionToRemove = fromVersionPattern.matcher(currentVersion.getName());\n                    if (!versionToRemove.matches()) {\n                        newVersions.add(currentVersion);\n                    }\n                }\n            } else {\n                for (Version currentVersion : issueVersions) {\n                    if (!currentVersion.getName().equals(fromVersion)) {\n                        newVersions.add(currentVersion);\n                    }\n                }\n            }\n\n            LOGGER.info(String.format(\"Moving issues matching JQL query: \\\"%s\\\" to version %s\", query, toVersion));\n            service.updateIssue(issue.getKey(), new ArrayList(newVersions));\n        }\n    }\n\n    /**\n     * Adds the specified version to the fix version list of all issues matching the JQL.\n     *\n     * @param projectKey The Jira Project\n     * @param version    The version to add\n     * @param query      The JQL Query\n     */\n    public void addFixVersion(String projectKey, String version, String query)\n            throws TimeoutException, RestClientException {\n\n        Version newVersion = getVersionByName(projectKey, version);\n        if (newVersion == null) {\n            LOGGER.warning(\"Version \" + version + \" was not found\");\n            return;\n        }\n\n        LOGGER.fine(\"Fetching issues with JQL:\" + query);\n        List<Issue> issues = service.getIssuesFromJqlSearch(query, maxIssuesFromJqlSearch);\n        if (issues == null || issues.isEmpty()) {\n            return;\n        }\n        LOGGER.fine(\"Found issues: \" + issues.size());\n\n        for (Issue issue : issues) {\n            LOGGER.fine(\"Adding version: \" + newVersion.getName() + \" to issue: \" + issue.getKey());\n            List<Version> fixVersions = new ArrayList<>();\n\n            Optional.ofNullable(issue.getFixVersions())\n                    .orElse(Collections.emptyList())\n                    .forEach(fixVersions::add);\n\n            fixVersions.add(newVersion);\n            service.updateIssue(issue.getKey(), fixVersions);\n        }\n    }\n\n    /**\n     * Progresses the issue's workflow by performing the specified action. The issue's new status is returned.\n     *\n     * @return The new status\n     */\n    public String progressWorkflowAction(String issueKey, Integer actionId) {\n        LOGGER.fine(\"Progressing issue \" + issueKey + \" with workflow action: \" + actionId);\n        final Issue issue = service.progressWorkflowAction(issueKey, actionId);\n        getStatusById(issue.getStatus().getId());\n        return getStatusById(issue.getStatus().getId());\n    }\n\n    /**\n     * Returns the matching action id for a given action name.\n     *\n     * @return The action id, or null if the action cannot be found.\n     */\n    public Integer getActionIdForIssue(String issueKey, String workflowAction) {\n        List<Transition> actions = service.getAvailableActions(issueKey);\n\n        if (actions != null) {\n            for (Transition action : actions) {\n                if (action.getName() != null && action.getName().equalsIgnoreCase(workflowAction)) {\n                    return action.getId();\n                }\n            }\n        }\n\n        return null;\n    }\n\n    /**\n     * Returns the status name by status id.\n     *\n     * @return status name\n     */\n    public String getStatusById(Long statusId) {\n        String status = getKnownStatuses().get(statusId);\n\n        if (status == null) {\n            LOGGER.warning(\"Jira status could not be found: \" + statusId + \". Checking Jira for new status types.\");\n            knownStatuses = null;\n            // Try again, just in case the admin has recently added a new status. This should be a rare condition.\n            status = getKnownStatuses().get(statusId);\n        }\n\n        return status;\n    }\n\n    private Map<Long, String> knownStatuses = null;\n\n    /**\n     * Returns all known statuses.\n     *\n     * @return Map with statusId and status name\n     */\n    private Map<Long, String> getKnownStatuses() {\n        if (knownStatuses == null) {\n            List<Status> statuses = service.getStatuses();\n            knownStatuses = new HashMap<>(statuses.size());\n            statuses.stream().forEach(status -> knownStatuses.put(status.getId(), status.getName()));\n        }\n        return knownStatuses;\n    }\n\n    /**\n     * Returns issue-id of the created issue\n     *\n     * @return The issue id\n     *\n     * @deprecated use {@link #createIssue(String, String, String, Iterable, String, Long, Long)}\n     */\n    @Deprecated\n    public Issue createIssue(\n            String projectKey, String description, String assignee, Iterable<String> components, String summary) {\n        return createIssue(projectKey, description, assignee, components, summary, null, null);\n    }\n\n    public Issue createIssue(\n            String projectKey,\n            String description,\n            String assignee,\n            Iterable<String> components,\n            String summary,\n            @NonNull Long issueTypeId,\n            @Nullable Long priorityId) {\n        final BasicIssue basicIssue =\n                service.createIssue(projectKey, description, assignee, components, summary, issueTypeId, priorityId);\n        return service.getIssue(basicIssue.getKey());\n    }\n\n    /**\n     * Adds a comment to the existing issue.There is no constrains to the visibility of the comment.\n     */\n    public void addCommentWithoutConstrains(String issueId, String comment) {\n        service.addComment(issueId, comment, null, null);\n    }\n\n    /**\n     * Returns information about the specific issue as identified by the issue id\n     *\n     * @return issue object\n     */\n    public Issue getIssueByKey(String issueId) {\n        return service.getIssue(issueId);\n    }\n\n    /**\n     * Returns all the components for the particular project\n     *\n     * @return An array of components\n     */\n    public List<Component> getComponents(String projectKey) {\n        return service.getComponents(projectKey);\n    }\n\n    /**\n     * Creates a new version and returns it\n     *\n     * @param version    version id to create\n     * @return created Version instance\n     *\n     */\n    public Version addVersion(String version, String projectKey) {\n        return service.addVersion(projectKey, version);\n    }\n\n    /**\n     * Get User's permissions\n     */\n    public Permissions getMyPermissions() {\n        return service.getMyPermissions();\n    }\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/JiraSessionFactory.java",
    "content": "package hudson.plugins.jira;\n\nimport com.atlassian.jira.rest.client.auth.BasicHttpAuthenticationHandler;\nimport com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials;\nimport hudson.plugins.jira.JiraSite.ExtendedAsynchronousJiraRestClientFactory;\nimport hudson.plugins.jira.auth.BearerHttpAuthenticationHandler;\nimport hudson.plugins.jira.extension.ExtendedJiraRestClient;\nimport java.net.URI;\n\n/**\n * Jira Session factory implementation\n *\n * @author Elia Bracci\n */\npublic class JiraSessionFactory {\n\n    /**\n     * This method takes as parameters the JiraSite class, the jira URI and\n     * credentials and returns a JiraSession with Basic authentication if\n     * useBearerAuth is set to false, otherwise it returns a JiraSession with Bearer\n     * authentication if useBearerAuth is set to true.\n     *\n     * @param jiraSite    jiraSite class\n     * @param uri         jira uri\n     * @param credentials Jenkins credentials\n     * @return JiraSession instance\n     */\n    public static JiraSession create(JiraSite jiraSite, URI uri, StandardUsernamePasswordCredentials credentials) {\n        ExtendedJiraRestClient jiraRestClient;\n        JiraRestService jiraRestService;\n\n        if (jiraSite.isUseBearerAuth()) {\n            BearerHttpAuthenticationHandler bearerHttpAuthenticationHandler = new BearerHttpAuthenticationHandler(\n                    credentials.getPassword().getPlainText());\n\n            jiraRestClient = new ExtendedAsynchronousJiraRestClientFactory()\n                    .create(uri, bearerHttpAuthenticationHandler, jiraSite.getHttpClientOptions());\n\n            jiraRestService = new JiraRestService(\n                    uri, jiraRestClient, credentials.getPassword().getPlainText(), jiraSite.getReadTimeout());\n        } else {\n            jiraRestClient = new ExtendedAsynchronousJiraRestClientFactory()\n                    .create(\n                            uri,\n                            new BasicHttpAuthenticationHandler(\n                                    credentials.getUsername(),\n                                    credentials.getPassword().getPlainText()),\n                            jiraSite.getHttpClientOptions());\n\n            jiraRestService = new JiraRestService(\n                    uri,\n                    jiraRestClient,\n                    credentials.getUsername(),\n                    credentials.getPassword().getPlainText(),\n                    jiraSite.getReadTimeout());\n        }\n\n        return new JiraSession(jiraSite, jiraRestService);\n    }\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/JiraSite.java",
    "content": "package hudson.plugins.jira;\n\nimport static org.apache.commons.lang3.StringUtils.isEmpty;\nimport static org.apache.commons.lang3.StringUtils.isNotEmpty;\n\nimport com.atlassian.event.api.EventPublisher;\nimport com.atlassian.httpclient.apache.httpcomponents.DefaultHttpClientFactory;\nimport com.atlassian.httpclient.api.HttpClient;\nimport com.atlassian.httpclient.api.factory.HttpClientOptions;\nimport com.atlassian.jira.rest.client.api.AuthenticationHandler;\nimport com.atlassian.jira.rest.client.api.JiraRestClientFactory;\nimport com.atlassian.jira.rest.client.api.RestClientException;\nimport com.atlassian.jira.rest.client.api.domain.Issue;\nimport com.atlassian.jira.rest.client.auth.BasicHttpAuthenticationHandler;\nimport com.atlassian.jira.rest.client.internal.async.AtlassianHttpClientDecorator;\nimport com.atlassian.jira.rest.client.internal.async.DisposableHttpClient;\nimport com.atlassian.sal.api.ApplicationProperties;\nimport com.atlassian.sal.api.UrlMode;\nimport com.atlassian.sal.api.executor.ThreadLocalContextManager;\nimport com.cloudbees.hudson.plugins.folder.AbstractFolder;\nimport com.cloudbees.hudson.plugins.folder.Folder;\nimport com.cloudbees.plugins.credentials.CredentialsMatchers;\nimport com.cloudbees.plugins.credentials.CredentialsProvider;\nimport com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials;\nimport com.cloudbees.plugins.credentials.domains.DomainRequirement;\nimport com.cloudbees.plugins.credentials.domains.URIRequirementBuilder;\nimport com.github.benmanes.caffeine.cache.Cache;\nimport com.github.benmanes.caffeine.cache.Caffeine;\nimport edu.umd.cs.findbugs.annotations.CheckForNull;\nimport edu.umd.cs.findbugs.annotations.NonNull;\nimport edu.umd.cs.findbugs.annotations.Nullable;\nimport edu.umd.cs.findbugs.annotations.SuppressFBWarnings;\nimport hudson.Extension;\nimport hudson.Util;\nimport hudson.model.*;\nimport hudson.model.Descriptor.FormException;\nimport hudson.plugins.jira.extension.ExtendedAsynchronousJiraRestClient;\nimport hudson.plugins.jira.extension.ExtendedJiraRestClient;\nimport hudson.plugins.jira.extension.ExtendedVersion;\nimport hudson.plugins.jira.model.JiraIssue;\nimport hudson.security.ACL;\nimport hudson.util.FormValidation;\nimport hudson.util.ListBoxModel;\nimport hudson.util.Secret;\nimport jakarta.servlet.ServletException;\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.PrintStream;\nimport java.net.MalformedURLException;\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.net.URL;\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.Date;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.ThreadFactory;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.concurrent.locks.Lock;\nimport java.util.concurrent.locks.ReentrantLock;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\nimport java.util.regex.Pattern;\nimport javax.annotation.PreDestroy;\nimport jenkins.model.Jenkins;\nimport org.kohsuke.stapler.AncestorInPath;\nimport org.kohsuke.stapler.DataBoundConstructor;\nimport org.kohsuke.stapler.DataBoundSetter;\nimport org.kohsuke.stapler.QueryParameter;\nimport org.kohsuke.stapler.interceptor.RequirePOST;\n\n/**\n * <b>You must get instance of this only by using the static {@link #get} or {@link #getSitesFromFolders(ItemGroup)} methods</b>\n * <b>The constructors are only used by Jenkins</b>\n * <p>\n * Represents an external Jira installation and configuration\n * needed to access this Jira.\n * </p>\n * <b>When adding new fields do not miss to look at readResolve method!!</b>\n *\n * @author Kohsuke Kawaguchi\n */\npublic class JiraSite extends AbstractDescribableImpl<JiraSite> {\n\n    private static final Logger LOGGER = Logger.getLogger(JiraSite.class.getName());\n\n    /**\n     * Regexp pattern that identifies Jira issue token.\n     * If this pattern changes help pages (help-issue-pattern_xy.html) must be updated\n     * First char must be a letter, then at least one letter, digit or underscore.\n     * See issue JENKINS-729, JENKINS-4092\n     */\n    public static final Pattern DEFAULT_ISSUE_PATTERN =\n            Pattern.compile(\"([a-zA-Z][a-zA-Z0-9_]+-[1-9][0-9]*)([^.]|\\\\.[^0-9]|\\\\.$|$)\");\n\n    /**\n     * Default rest api client calls timeout, in seconds\n     * See issue JENKINS-31113\n     */\n    public static final int DEFAULT_TIMEOUT = 10;\n\n    public static final int DEFAULT_READ_TIMEOUT = 30;\n\n    public static final int DEFAULT_THREAD_EXECUTOR_NUMBER = 10;\n\n    public static final Integer DEFAULT_ISSUES_FROM_JQL = 100;\n\n    public static final Integer MAX_ALLOWED_ISSUES_FROM_JQL = 5000;\n\n    /**\n     * URL of Jira for Jenkins access, like {@code http://jira.codehaus.org/}.\n     * Mandatory. Normalized to end with '/'\n     */\n    public final URL url;\n\n    /**\n     * URL of Jira for normal access, like {@code http://jira.codehaus.org/}.\n     * Mandatory. Normalized to end with '/'\n     */\n    @SuppressFBWarnings(value = \"PA_PUBLIC_PRIMITIVE_ATTRIBUTE\", justification = \"Backwards compatibility\")\n    public URL alternativeUrl;\n\n    /**\n     * Jira requires HTTP Authentication for login\n     */\n    @SuppressFBWarnings(value = \"PA_PUBLIC_PRIMITIVE_ATTRIBUTE\", justification = \"Backwards compatibility\")\n    public boolean useHTTPAuth;\n\n    /**\n     * The id of the credentials to use. Optional.\n     */\n    @SuppressFBWarnings(value = \"PA_PUBLIC_PRIMITIVE_ATTRIBUTE\", justification = \"Backwards compatibility\")\n    public String credentialsId;\n\n    /**\n     * Jira requires Bearer Authentication for login\n     */\n    @SuppressFBWarnings(value = \"PA_PUBLIC_PRIMITIVE_ATTRIBUTE\", justification = \"Backwards compatibility\")\n    public boolean useBearerAuth;\n\n    /**\n     * User name needed to login. Optional.\n     *\n     * @deprecated use credentialsId\n     */\n    @Deprecated\n    private transient String userName;\n\n    /**\n     * Password needed to login. Optional.\n     *\n     * @deprecated use credentialsId\n     */\n    @Deprecated\n    private transient Secret password;\n\n    /**\n     * Group visibility to constrain the visibility of the added comment. Optional.\n     */\n    @SuppressFBWarnings(value = \"PA_PUBLIC_PRIMITIVE_ATTRIBUTE\", justification = \"Backwards compatibility\")\n    public String groupVisibility;\n\n    /**\n     * Role visibility to constrain the visibility of the added comment. Optional.\n     */\n    @SuppressFBWarnings(value = \"PA_PUBLIC_PRIMITIVE_ATTRIBUTE\", justification = \"Backwards compatibility\")\n    public String roleVisibility;\n\n    /**\n     * True if this Jira is configured to allow Confluence-style Wiki comment.\n     */\n    @SuppressFBWarnings(value = \"PA_PUBLIC_PRIMITIVE_ATTRIBUTE\", justification = \"Backwards compatibility\")\n    public boolean supportsWikiStyleComment;\n\n    /**\n     * to record scm changes in jira issue\n     *\n     * @since 1.21\n     */\n    @SuppressFBWarnings(value = \"PA_PUBLIC_PRIMITIVE_ATTRIBUTE\", justification = \"Backwards compatibility\")\n    public boolean recordScmChanges;\n\n    /**\n     * Disable annotating the changelogs\n     *\n     * @since todo\n     */\n    @SuppressFBWarnings(value = \"PA_PUBLIC_PRIMITIVE_ATTRIBUTE\", justification = \"Backwards compatibility\")\n    public boolean disableChangelogAnnotations;\n\n    /**\n     * user defined pattern\n     *\n     * @since 1.22\n     */\n    private String userPattern;\n\n    private transient Pattern userPat;\n\n    /**\n     * updated jira issue for all status\n     *\n     * @since 1.22\n     */\n    @SuppressFBWarnings(value = \"PA_PUBLIC_PRIMITIVE_ATTRIBUTE\", justification = \"Backwards compatibility\")\n    public boolean updateJiraIssueForAllStatus;\n\n    /**\n     * connection timeout used when calling jira rest api, in seconds\n     */\n    @SuppressFBWarnings(value = \"PA_PUBLIC_PRIMITIVE_ATTRIBUTE\", justification = \"Backwards compatibility\")\n    public int timeout = DEFAULT_TIMEOUT;\n\n    /**\n     * response timeout for jira rest call\n     *\n     * @since 3.0.3\n     */\n    private int readTimeout = DEFAULT_READ_TIMEOUT;\n\n    /**\n     * thread pool number\n     *\n     * @since 3.0.3\n     */\n    private int threadExecutorNumber = DEFAULT_THREAD_EXECUTOR_NUMBER;\n\n    /**\n     * Configuration  for formatting (date -> text) in jira comments.\n     */\n    private String dateTimePattern;\n\n    /**\n     * To add scm entry change date and time in jira comments.\n     */\n    private boolean appendChangeTimestamp;\n\n    /**\n     * To allow configurable value of max issues from jql search via jira site global configuration.\n     */\n    private int maxIssuesFromJqlSearch = DEFAULT_ISSUES_FROM_JQL;\n\n    private int ioThreadCount = Integer.getInteger(JiraSite.class.getName() + \".httpclient.options.ioThreadCount\", 2);\n\n    /**\n     * List of project keys (i.e., \"MNG\" portion of \"MNG-512\"),\n     * last time we checked. Copy on write semantics.\n     */\n    // TODO: seems like this is never invalidated (never set to null)\n    // should we implement to invalidate this (say every hour)?\n    private transient volatile Set<String> projects;\n\n    private transient Cache<String, Optional<Issue>> issueCache = makeIssueCache();\n\n    /**\n     * Used to guard the computation of {@link #projects}\n     */\n    private transient Lock projectUpdateLock = new ReentrantLock();\n\n    private transient JiraSession jiraSession;\n\n    private static ExecutorService executorService;\n\n    // Deprecate the previous constructor but leave it in place for Java-level compatibility.\n    @Deprecated\n    public JiraSite(\n            URL url,\n            @CheckForNull URL alternativeUrl,\n            @CheckForNull String credentialsId,\n            boolean supportsWikiStyleComment,\n            boolean recordScmChanges,\n            @CheckForNull String userPattern,\n            boolean updateJiraIssueForAllStatus,\n            @CheckForNull String groupVisibility,\n            @CheckForNull String roleVisibility,\n            boolean useHTTPAuth) {\n        this(\n                url,\n                alternativeUrl,\n                credentialsId,\n                supportsWikiStyleComment,\n                recordScmChanges,\n                userPattern,\n                updateJiraIssueForAllStatus,\n                groupVisibility,\n                roleVisibility,\n                useHTTPAuth,\n                DEFAULT_TIMEOUT,\n                DEFAULT_READ_TIMEOUT,\n                DEFAULT_THREAD_EXECUTOR_NUMBER);\n    }\n\n    // Deprecate the previous constructor but leave it in place for Java-level compatibility.\n    @Deprecated\n    public JiraSite(\n            URL url,\n            @CheckForNull URL alternativeUrl,\n            String userName,\n            String password,\n            boolean supportsWikiStyleComment,\n            boolean recordScmChanges,\n            @CheckForNull String userPattern,\n            boolean updateJiraIssueForAllStatus,\n            @CheckForNull String groupVisibility,\n            @CheckForNull String roleVisibility,\n            boolean useHTTPAuth)\n            throws FormException {\n        this(\n                url,\n                alternativeUrl,\n                CredentialsHelper.migrateCredentials(userName, password, url),\n                supportsWikiStyleComment,\n                recordScmChanges,\n                userPattern,\n                updateJiraIssueForAllStatus,\n                groupVisibility,\n                roleVisibility,\n                useHTTPAuth);\n    }\n\n    // Deprecate the previous constructor but leave it in place for Java-level compatibility.\n    @Deprecated\n    public JiraSite(\n            URL url,\n            URL alternativeUrl,\n            StandardUsernamePasswordCredentials credentials,\n            boolean supportsWikiStyleComment,\n            boolean recordScmChanges,\n            String userPattern,\n            boolean updateJiraIssueForAllStatus,\n            String groupVisibility,\n            String roleVisibility,\n            boolean useHTTPAuth)\n            throws FormException {\n        this(\n                url,\n                alternativeUrl,\n                (String) null,\n                supportsWikiStyleComment,\n                recordScmChanges,\n                userPattern,\n                updateJiraIssueForAllStatus,\n                groupVisibility,\n                roleVisibility,\n                useHTTPAuth,\n                DEFAULT_TIMEOUT,\n                DEFAULT_READ_TIMEOUT,\n                DEFAULT_THREAD_EXECUTOR_NUMBER);\n        if (credentials != null) {\n            // we verify the credential really exists otherwise we migrate it\n            StandardUsernamePasswordCredentials standardUsernamePasswordCredentials =\n                    CredentialsHelper.lookupSystemCredentials(credentials.getId(), url);\n            if (standardUsernamePasswordCredentials == null) {\n                credentials = CredentialsHelper.migrateCredentials(\n                        credentials.getUsername(), credentials.getPassword().getPlainText(), url);\n            }\n        }\n        setCredentialsId(credentials == null ? null : credentials.getId());\n    }\n\n    // Deprecate the previous constructor but leave it in place for Java-level compatibility.\n    @Deprecated\n    public JiraSite(\n            URL url,\n            URL alternativeUrl,\n            String credentialsId,\n            boolean supportsWikiStyleComment,\n            boolean recordScmChanges,\n            String userPattern,\n            boolean updateJiraIssueForAllStatus,\n            String groupVisibility,\n            String roleVisibility,\n            boolean useHTTPAuth,\n            int timeout,\n            int readTimeout,\n            int threadExecutorNumber) {\n        if (url != null) {\n            url = toURL(url.toExternalForm());\n        }\n\n        if (alternativeUrl != null) {\n            alternativeUrl = toURL(alternativeUrl.toExternalForm());\n        }\n\n        this.url = url;\n        this.credentialsId = credentialsId;\n        this.timeout = timeout;\n        this.readTimeout = readTimeout;\n        this.threadExecutorNumber = threadExecutorNumber;\n        this.alternativeUrl = alternativeUrl;\n        this.supportsWikiStyleComment = supportsWikiStyleComment;\n        this.recordScmChanges = recordScmChanges;\n        setUserPattern(userPattern);\n        this.updateJiraIssueForAllStatus = updateJiraIssueForAllStatus;\n        setGroupVisibility(groupVisibility);\n        setRoleVisibility(roleVisibility);\n        this.useHTTPAuth = useHTTPAuth;\n        this.jiraSession = null;\n    }\n\n    @DataBoundConstructor\n    public JiraSite(String url) {\n        URL mainURL = toURL(url);\n        if (mainURL == null) {\n            throw new AssertionError(\"URL cannot be empty\");\n        }\n        this.url = mainURL;\n    }\n\n    // Deprecate the previous constructor but leave it in place for Java-level compatibility.\n    @Deprecated\n    public JiraSite(\n            URL url,\n            URL alternativeUrl,\n            StandardUsernamePasswordCredentials credentials,\n            boolean supportsWikiStyleComment,\n            boolean recordScmChanges,\n            String userPattern,\n            boolean updateJiraIssueForAllStatus,\n            String groupVisibility,\n            String roleVisibility,\n            boolean useHTTPAuth,\n            int timeout,\n            int readTimeout,\n            int threadExecutorNumber) {\n        this(\n                url,\n                alternativeUrl,\n                credentials == null ? null : credentials.getId(),\n                supportsWikiStyleComment,\n                recordScmChanges,\n                userPattern,\n                updateJiraIssueForAllStatus,\n                groupVisibility,\n                roleVisibility,\n                useHTTPAuth,\n                timeout,\n                readTimeout,\n                threadExecutorNumber);\n    }\n\n    // Deprecate the previous constructor but leave it in place for Java-level compatibility.\n    @Deprecated\n    public JiraSite(\n            URL url,\n            URL alternativeUrl,\n            StandardUsernamePasswordCredentials credentials,\n            boolean supportsWikiStyleComment,\n            boolean recordScmChanges,\n            String userPattern,\n            boolean updateJiraIssueForAllStatus,\n            String groupVisibility,\n            String roleVisibility,\n            boolean useHTTPAuth,\n            int timeout,\n            int readTimeout,\n            int threadExecutorNumber,\n            boolean useBearerAuth) {\n        this(\n                url,\n                alternativeUrl,\n                credentials == null ? null : credentials.getId(),\n                supportsWikiStyleComment,\n                recordScmChanges,\n                userPattern,\n                updateJiraIssueForAllStatus,\n                groupVisibility,\n                roleVisibility,\n                useHTTPAuth,\n                timeout,\n                readTimeout,\n                threadExecutorNumber);\n        this.useBearerAuth = useBearerAuth;\n    }\n\n    static URL toURL(String url) {\n        url = Util.fixEmptyAndTrim(url);\n        if (url == null) {\n            return null;\n        }\n        if (!url.endsWith(\"/\")) {\n            url = url + \"/\";\n        }\n        try {\n            return new URL(url);\n        } catch (MalformedURLException e) {\n            throw new AssertionError(e);\n        }\n    }\n\n    @DataBoundSetter\n    public void setDisableChangelogAnnotations(boolean disableChangelogAnnotations) {\n        this.disableChangelogAnnotations = disableChangelogAnnotations;\n    }\n\n    public boolean getDisableChangelogAnnotations() {\n        return disableChangelogAnnotations;\n    }\n\n    /**\n     * Sets connect timeout (in seconds).\n     * If not specified, a default timeout will be used.\n     *\n     * @param timeoutSec Timeout in seconds\n     */\n    @DataBoundSetter\n    public void setTimeout(int timeoutSec) {\n        this.timeout = timeoutSec;\n    }\n\n    public int getTimeout() {\n        return timeout;\n    }\n\n    /**\n     * Sets read timeout (in seconds).\n     * If not specified, a default timeout will be used.\n     *\n     * @param readTimeout Timeout in seconds\n     */\n    @DataBoundSetter\n    public void setReadTimeout(int readTimeout) {\n        this.readTimeout = readTimeout;\n    }\n\n    public int getReadTimeout() {\n        return readTimeout;\n    }\n\n    public String getCredentialsId() {\n        return credentialsId;\n    }\n\n    @DataBoundSetter\n    public void setCredentialsId(String credentialsId) {\n        this.credentialsId = Util.fixEmptyAndTrim(credentialsId);\n    }\n\n    @DataBoundSetter\n    public void setDateTimePattern(String dateTimePattern) {\n        this.dateTimePattern = Util.fixEmptyAndTrim(dateTimePattern);\n    }\n\n    @DataBoundSetter\n    public void setThreadExecutorNumber(int threadExecutorNumber) {\n        this.threadExecutorNumber = threadExecutorNumber;\n    }\n\n    public int getThreadExecutorNumber() {\n        return threadExecutorNumber;\n    }\n\n    @DataBoundSetter\n    public void setAppendChangeTimestamp(boolean appendChangeTimestamp) {\n        this.appendChangeTimestamp = appendChangeTimestamp;\n    }\n\n    public String getDateTimePattern() {\n        return dateTimePattern;\n    }\n\n    public boolean isAppendChangeTimestamp() {\n        return appendChangeTimestamp;\n    }\n\n    public URL getAlternativeUrl() {\n        return alternativeUrl;\n    }\n\n    public boolean isUseHTTPAuth() {\n        return useHTTPAuth;\n    }\n\n    public boolean isUseBearerAuth() {\n        return useBearerAuth;\n    }\n\n    public String getGroupVisibility() {\n        return groupVisibility;\n    }\n\n    public String getRoleVisibility() {\n        return roleVisibility;\n    }\n\n    public boolean isSupportsWikiStyleComment() {\n        return supportsWikiStyleComment;\n    }\n\n    public boolean isRecordScmChanges() {\n        return recordScmChanges;\n    }\n\n    public boolean isUpdateJiraIssueForAllStatus() {\n        return updateJiraIssueForAllStatus;\n    }\n\n    @DataBoundSetter\n    public void setAlternativeUrl(String alternativeUrl) {\n        this.alternativeUrl = toURL(alternativeUrl);\n    }\n\n    @DataBoundSetter\n    public void setUseHTTPAuth(boolean useHTTPAuth) {\n        this.useHTTPAuth = useHTTPAuth;\n    }\n\n    @DataBoundSetter\n    public void setUseBearerAuth(boolean useBearerAuth) {\n        this.useBearerAuth = useBearerAuth;\n    }\n\n    @DataBoundSetter\n    public void setGroupVisibility(String groupVisibility) {\n        this.groupVisibility = Util.fixEmptyAndTrim(groupVisibility);\n    }\n\n    @DataBoundSetter\n    public void setRoleVisibility(String roleVisibility) {\n        this.roleVisibility = Util.fixEmptyAndTrim(roleVisibility);\n    }\n\n    @DataBoundSetter\n    public void setSupportsWikiStyleComment(boolean supportsWikiStyleComment) {\n        this.supportsWikiStyleComment = supportsWikiStyleComment;\n    }\n\n    @DataBoundSetter\n    public void setRecordScmChanges(boolean recordScmChanges) {\n        this.recordScmChanges = recordScmChanges;\n    }\n\n    @DataBoundSetter\n    public void setUserPattern(String userPattern) {\n        this.userPattern = Util.fixEmptyAndTrim(userPattern);\n\n        if (this.userPattern == null) {\n            this.userPat = null;\n        } else {\n            this.userPat = Pattern.compile(this.userPattern);\n        }\n    }\n\n    @DataBoundSetter\n    public void setUpdateJiraIssueForAllStatus(boolean updateJiraIssueForAllStatus) {\n        this.updateJiraIssueForAllStatus = updateJiraIssueForAllStatus;\n    }\n\n    @DataBoundSetter\n    public void setMaxIssuesFromJqlSearch(int maxIssuesFromJqlSearch) {\n        this.maxIssuesFromJqlSearch = maxIssuesFromJqlSearch > MAX_ALLOWED_ISSUES_FROM_JQL\n                ? MAX_ALLOWED_ISSUES_FROM_JQL\n                : maxIssuesFromJqlSearch;\n    }\n\n    public int getMaxIssuesFromJqlSearch() {\n        return maxIssuesFromJqlSearch;\n    }\n\n    @SuppressWarnings(\"unused\")\n    protected Object readResolve() throws FormException {\n        JiraSite jiraSite;\n\n        if (credentialsId == null && userName != null && password != null) { // Migrate credentials\n            jiraSite = new JiraSite(\n                    url,\n                    alternativeUrl,\n                    userName,\n                    password.getPlainText(),\n                    supportsWikiStyleComment,\n                    recordScmChanges,\n                    userPattern,\n                    updateJiraIssueForAllStatus,\n                    groupVisibility,\n                    roleVisibility,\n                    useHTTPAuth);\n        } else {\n            jiraSite = new JiraSite(\n                    url,\n                    alternativeUrl,\n                    credentialsId,\n                    supportsWikiStyleComment,\n                    recordScmChanges,\n                    userPattern,\n                    updateJiraIssueForAllStatus,\n                    groupVisibility,\n                    roleVisibility,\n                    useHTTPAuth,\n                    timeout,\n                    readTimeout,\n                    threadExecutorNumber);\n        }\n        jiraSite.setAppendChangeTimestamp(appendChangeTimestamp);\n        jiraSite.setDisableChangelogAnnotations(disableChangelogAnnotations);\n        jiraSite.setDateTimePattern(dateTimePattern);\n        jiraSite.setUseBearerAuth(useBearerAuth);\n        if (this.maxIssuesFromJqlSearch <= 0) {\n            jiraSite.setMaxIssuesFromJqlSearch(DEFAULT_ISSUES_FROM_JQL);\n        } else {\n            jiraSite.setMaxIssuesFromJqlSearch(maxIssuesFromJqlSearch);\n        }\n        return jiraSite;\n    }\n\n    protected static Cache<String, Optional<Issue>> makeIssueCache() {\n        return Caffeine.newBuilder().expireAfterAccess(2, TimeUnit.MINUTES).build();\n    }\n\n    public String getName() {\n        return url.toExternalForm();\n    }\n\n    /**\n     * @deprecated should not be used\n     */\n    @Deprecated\n    public JiraSession getSession() {\n        return getSession(null);\n    }\n\n    /**\n     * Gets a remote access session to this Jira site (job-aware)\n     * Creates one if none exists already.\n     *\n     * @return null if remote access is not supported.\n     */\n    @Nullable\n    public JiraSession getSession(Item item) {\n        return getSession(item, false);\n    }\n\n    JiraSession getSession(Item item, boolean uiValidation) {\n        if (jiraSession == null) {\n            jiraSession = createSession(item, uiValidation);\n        }\n        return jiraSession;\n    }\n\n    JiraSession createSession(Item item) {\n        return createSession(item, false);\n    }\n\n    /**\n     * Creates a remote access session to this Jira.\n     *\n     * @return null if remote access is not supported.\n     */\n    JiraSession createSession(Item item, boolean uiValidation) {\n        ItemGroup itemGroup = map(item);\n        item = itemGroup instanceof Folder ? ((Folder) itemGroup) : item;\n\n        StandardUsernamePasswordCredentials credentials = resolveCredentials(item, uiValidation);\n\n        if (credentials == null) {\n            LOGGER.fine(\"no Jira credentials available for \" + item);\n            return null; // remote access not supported\n        }\n\n        URI uri;\n        try {\n            uri = url.toURI();\n        } catch (URISyntaxException e) {\n            LOGGER.warning(\"convert URL to URI error: \" + e.getMessage());\n            throw new RuntimeException(\"failed to create JiraSession due to convert URI error\");\n        }\n        LOGGER.fine(\"creating Jira Session: \" + uri);\n\n        return JiraSessionFactory.create(this, uri, credentials);\n    }\n\n    Lock getProjectUpdateLock() {\n        return projectUpdateLock;\n    }\n\n    /**\n     * This method only supports credential matching by credentialsId.\n     * Older methods are not and will not be supported as the credentials should have been migrated already.\n     *\n     * @param item         can be <code>null</code> if top level\n     * @param uiValidation if <code>true</code> and credentials not found at item level will not go up\n     */\n    private StandardUsernamePasswordCredentials resolveCredentials(Item item, boolean uiValidation) {\n        if (credentialsId == null) {\n            LOGGER.fine(\"credentialsId is null\");\n            return null; // remote access not supported\n        }\n\n        List<DomainRequirement> req = URIRequirementBuilder.fromUri(url != null ? url.toExternalForm() : null)\n                .build();\n\n        if (item != null) {\n            StandardUsernamePasswordCredentials credentials = CredentialsMatchers.firstOrNull(\n                    CredentialsProvider.lookupCredentials(\n                            StandardUsernamePasswordCredentials.class, item, ACL.SYSTEM, req),\n                    CredentialsMatchers.withId(credentialsId));\n            if (credentials != null) {\n                return credentials;\n            }\n            // during UI validation of the configuration we definitely don't want to expose\n            // global credentials\n            if (uiValidation) {\n                return null;\n            }\n        }\n        return CredentialsMatchers.firstOrNull(\n                CredentialsProvider.lookupCredentials(\n                        StandardUsernamePasswordCredentials.class, Jenkins.get(), ACL.SYSTEM, req),\n                CredentialsMatchers.withId(credentialsId));\n    }\n\n    protected HttpClientOptions getHttpClientOptions() {\n        final HttpClientOptions options = new HttpClientOptions();\n        options.setRequestTimeout(readTimeout, TimeUnit.SECONDS);\n        options.setSocketTimeout(timeout, TimeUnit.SECONDS);\n        options.setCallbackExecutor(getExecutorService());\n        options.setIoThreadCount(ioThreadCount);\n        return options;\n    }\n\n    private ExecutorService getExecutorService() {\n        if (executorService == null) {\n            synchronized (JiraSite.class) {\n                int nThreads = threadExecutorNumber;\n                if (nThreads < 1) {\n                    LOGGER.warning(\"nThreads \" + nThreads + \" cannot be lower than 1 so use default \"\n                            + DEFAULT_THREAD_EXECUTOR_NUMBER);\n                    nThreads = DEFAULT_THREAD_EXECUTOR_NUMBER;\n                }\n                executorService = Executors.newFixedThreadPool(nThreads, new ThreadFactory() {\n                    final AtomicInteger threadNumber = new AtomicInteger(0);\n\n                    @Override\n                    public Thread newThread(Runnable r) {\n                        return new Thread(r, \"jira-plugin-http-request-\" + threadNumber.getAndIncrement() + \"-thread\");\n                    }\n                });\n            }\n        }\n        return executorService;\n    }\n\n    // not really used but let's leave when it will be implemented\n    @PreDestroy\n    public void destroy() {\n        try {\n            this.jiraSession = null;\n        } catch (Exception e) {\n            LOGGER.log(Level.WARNING, \"skip error destroying JiraSite:\" + e.getMessage(), e);\n        }\n    }\n\n    // -----------------------------------------------------------------------------------\n    // internal classes we want to override\n    // -----------------------------------------------------------------------------------\n\n    public static class ExtendedAsynchronousJiraRestClientFactory implements JiraRestClientFactory {\n\n        public ExtendedJiraRestClient create(\n                final URI serverUri, final AuthenticationHandler authenticationHandler, HttpClientOptions options) {\n            final DisposableHttpClient httpClient = createClient(serverUri, authenticationHandler, options);\n            Thread t = Thread.currentThread();\n            ClassLoader orig = t.getContextClassLoader();\n            t.setContextClassLoader(JiraSite.class.getClassLoader());\n            try {\n                return new ExtendedAsynchronousJiraRestClient(serverUri, httpClient);\n            } finally {\n                t.setContextClassLoader(orig);\n            }\n        }\n\n        @Override\n        public ExtendedJiraRestClient create(final URI serverUri, final AuthenticationHandler authenticationHandler) {\n            final DisposableHttpClient httpClient =\n                    createClient(serverUri, authenticationHandler, new HttpClientOptions());\n            return new ExtendedAsynchronousJiraRestClient(serverUri, httpClient);\n        }\n\n        @Override\n        public ExtendedJiraRestClient createWithBasicHttpAuthentication(\n                final URI serverUri, final String username, final String password) {\n            return create(serverUri, new BasicHttpAuthenticationHandler(username, password));\n        }\n\n        @Override\n        public ExtendedJiraRestClient createWithAuthenticationHandler(\n                final URI serverUri, final AuthenticationHandler authenticationHandler) {\n            return create(serverUri, authenticationHandler);\n        }\n\n        @Override\n        public ExtendedJiraRestClient create(final URI serverUri, final HttpClient httpClient) {\n            final DisposableHttpClient disposableHttpClient = createClient(httpClient);\n            return new ExtendedAsynchronousJiraRestClient(serverUri, disposableHttpClient);\n        }\n    }\n\n    private static DisposableHttpClient createClient(\n            final URI serverUri, final AuthenticationHandler authenticationHandler, HttpClientOptions options) {\n\n        final DefaultHttpClientFactory defaultHttpClientFactory = new DefaultHttpClientFactory(\n                new NoOpEventPublisher(),\n                new RestClientApplicationProperties(serverUri),\n                new ThreadLocalContextManager() {\n                    @Override\n                    public Object getThreadLocalContext() {\n                        return null;\n                    }\n\n                    @Override\n                    public void setThreadLocalContext(Object context) {}\n\n                    @Override\n                    public void clearThreadLocalContext() {}\n                });\n\n        final HttpClient httpClient = defaultHttpClientFactory.create(options);\n\n        return new AtlassianHttpClientDecorator(httpClient, authenticationHandler) {\n            @Override\n            public void destroy() throws Exception {\n                defaultHttpClientFactory.dispose(httpClient);\n            }\n        };\n    }\n\n    private static DisposableHttpClient createClient(final HttpClient client) {\n        return new AtlassianHttpClientDecorator(client, null) {\n            @Override\n            public void destroy() throws Exception {\n                // This should never be implemented. This is simply creation of a wrapper\n                // for AtlassianHttpClient which is extended by a destroy method.\n                // Destroy method should never be called for AtlassianHttpClient coming from\n                // a client! Imagine you create a RestClient, pass your own HttpClient there\n                // and it gets destroy.\n            }\n        };\n    }\n\n    private static class NoOpEventPublisher implements EventPublisher {\n        @Override\n        public void publish(Object o) {}\n\n        @Override\n        public void register(Object o) {}\n\n        @Override\n        public void unregister(Object o) {}\n\n        @Override\n        public void unregisterAll() {}\n    }\n\n    @SuppressWarnings(\"deprecation\")\n    private static class RestClientApplicationProperties implements ApplicationProperties {\n\n        private final String baseUrl;\n\n        private RestClientApplicationProperties(URI jiraURI) {\n            this.baseUrl = jiraURI.getPath();\n        }\n\n        @Override\n        public String getBaseUrl() {\n            return baseUrl;\n        }\n\n        /**\n         * We'll always have an absolute URL as a client.\n         */\n        @NonNull\n        @Override\n        public String getBaseUrl(UrlMode urlMode) {\n            return baseUrl;\n        }\n\n        @NonNull\n        @Override\n        public String getDisplayName() {\n            return \"Atlassian Jira Rest Java Client\";\n        }\n\n        @NonNull\n        @Override\n        public String getPlatformId() {\n            return ApplicationProperties.PLATFORM_JIRA;\n        }\n\n        @NonNull\n        @Override\n        public String getVersion() {\n            return \"\";\n        }\n\n        @NonNull\n        @Override\n        public Date getBuildDate() {\n            throw new UnsupportedOperationException();\n        }\n\n        @NonNull\n        @Override\n        public String getBuildNumber() {\n            return String.valueOf(0);\n        }\n\n        @Override\n        public File getHomeDirectory() {\n            return new File(\".\");\n        }\n\n        @Override\n        public String getPropertyValue(final String s) {\n            throw new UnsupportedOperationException(\"Not implemented\");\n        }\n\n        @NonNull\n        @Override\n        public String getApplicationFileEncoding() {\n            return System.getProperty(\"file.encoding\");\n        }\n\n        @NonNull\n        @Override\n        public Optional<Path> getLocalHomeDirectory() {\n            return Optional.empty();\n        }\n\n        @NonNull\n        @Override\n        public Optional<Path> getSharedHomeDirectory() {\n            return Optional.empty();\n        }\n    }\n\n    // -----------------------------------------------------------------------------------\n    //\n    // -----------------------------------------------------------------------------------\n\n    /**\n     * @return the server URL\n     */\n    @Nullable\n    public URL getUrl() {\n        return this.url != null ? this.url : this.alternativeUrl;\n    }\n\n    /**\n     * Computes the URL to the given issue.\n     */\n    public URL getUrl(JiraIssue issue) throws IOException {\n        return getUrl(issue.getKey());\n    }\n\n    /**\n     * Computes the URL to the given issue.\n     */\n    public URL getUrl(String id) throws MalformedURLException {\n        return new URL(url, \"browse/\" + id.toUpperCase());\n    }\n\n    /**\n     * Computes the alternative link URL to the given issue.\n     */\n    public URL getAlternativeUrl(String id) throws MalformedURLException {\n        return alternativeUrl == null ? null : new URL(alternativeUrl, \"browse/\" + id.toUpperCase());\n    }\n\n    /**\n     * Gets the user-defined issue pattern if any.\n     *\n     * @return the pattern or null\n     */\n    public Pattern getUserPattern() {\n        if (userPattern == null) {\n            return null;\n        }\n\n        if (userPat == null) {\n            // We don't care about any thread race- or visibility issues here.\n            // The worst thing which could happen, is that the pattern\n            // is compiled multiple times.\n            userPat = Pattern.compile(userPattern);\n        }\n        return userPat;\n    }\n\n    public Pattern getIssuePattern() {\n        Pattern result = getUserPattern();\n        return result == null ? DEFAULT_ISSUE_PATTERN : result;\n    }\n\n    /**\n     * Gets the list of project IDs in this Jira.\n     * This information could be bit old, or it can be null.\n     */\n    public Set<String> getProjectKeys(Item item) {\n        // FIXME it means projects list will be never updated until Jenkins is restarted...\n        if (projects == null) {\n            try {\n                if (getProjectUpdateLock().tryLock(3, TimeUnit.SECONDS)) {\n                    try {\n                        JiraSession session = getSession(item);\n                        if (session != null) {\n                            projects = Collections.unmodifiableSet(session.getProjectKeys());\n                        }\n                    } finally {\n                        getProjectUpdateLock().unlock();\n                    }\n                }\n            } catch (InterruptedException e) {\n                Thread.currentThread().interrupt(); // process this interruption later\n            } catch (RestClientException e) {\n                return Collections.emptySet();\n            }\n        }\n        // fall back to empty if failed to talk to the server\n        if (projects == null) {\n            return Collections.emptySet();\n        }\n\n        return projects;\n    }\n\n    /**\n     * Returns the remote issue with the given id or <code>null</code> if it wasn't found.\n     */\n    @CheckForNull\n    public JiraIssue getIssue(final String id) throws IOException {\n        Optional<Issue> issue = issueCache.get(id, s -> {\n            if (this.jiraSession == null) {\n                return Optional.empty();\n            }\n            return Optional.ofNullable(this.jiraSession.getIssue(id));\n        });\n\n        if (issue == null || !issue.isPresent()) {\n            return null;\n        }\n        return new JiraIssue(issue.get());\n    }\n\n    @Deprecated\n    public boolean existsIssue(String id) {\n        try {\n            return getIssue(id) != null;\n        } catch (IOException | RestClientException e) { // restoring backward compat means even avoid exception\n            throw new RuntimeException(e.getMessage(), e);\n        }\n    }\n\n    /**\n     * Returns all versions for the given project key.\n     *\n     * @param projectKey Project Key\n     * @return A set of JiraVersions\n     * @deprecated use {@link JiraSession#getVersions(String)}\n     */\n    @Deprecated\n    public Set<ExtendedVersion> getVersions(String projectKey) {\n        if (this.jiraSession == null) {\n            LOGGER.warning(\"Jira session could not be established\");\n            return Collections.emptySet();\n        }\n        return new HashSet<>(this.jiraSession.getVersions(projectKey));\n    }\n\n    /**\n     * Generates release notes for a given version.\n     *\n     * @param projectKey  the project key\n     * @param versionName the version\n     * @param filter      Additional JQL Filter. Example: status in (Resolved,Closed)\n     * @return release notes\n     * @throws TimeoutException if too long\n     */\n    public String getReleaseNotesForFixVersion(String projectKey, String versionName, String filter)\n            throws TimeoutException {\n        if (this.jiraSession == null) {\n            LOGGER.warning(\"Jira session could not be established\");\n            return \"\";\n        }\n\n        List<Issue> issues = this.jiraSession.getIssuesWithFixVersion(projectKey, versionName, filter);\n\n        if (issues.isEmpty()) {\n            return \"\";\n        }\n\n        Map<String, Set<String>> releaseNotes = new HashMap<>();\n\n        for (Issue issue : issues) {\n            String key = issue.getKey();\n            String summary = issue.getSummary();\n            String status = issue.getStatus().getName();\n            String type = issue.getIssueType().getName();\n\n            Set<String> issueSet;\n            if (releaseNotes.containsKey(type)) {\n                issueSet = releaseNotes.get(type);\n            } else {\n                issueSet = new HashSet<>();\n                releaseNotes.put(type, issueSet);\n            }\n\n            issueSet.add(String.format(\" - [%s] %s (%s)\", key, summary, status));\n        }\n\n        StringBuilder sb = new StringBuilder();\n        for (Map.Entry<String, Set<String>> entry : releaseNotes.entrySet()) {\n            sb.append(String.format(\"# %s%n\", entry.getKey()));\n            for (String issue : entry.getValue()) {\n                sb.append(issue);\n                sb.append(\"\\n\");\n            }\n        }\n\n        return sb.toString();\n    }\n\n    /**\n     * Migrates issues matching the jql query provided to a new fix version.\n     *\n     * @param projectKey The project key\n     * @param toVersion  The new fixVersion\n     * @param query      A JQL Query\n     * @throws TimeoutException if too long\n     */\n    public void replaceFixVersion(String projectKey, String fromVersion, String toVersion, String query)\n            throws TimeoutException, RestClientException {\n        if (this.jiraSession == null) {\n            LOGGER.warning(\"Jira session could not be established\");\n            return;\n        }\n        this.jiraSession.replaceFixVersion(projectKey, fromVersion, toVersion, query);\n    }\n\n    /**\n     * Migrates issues matching the jql query provided to a new fix version.\n     *\n     * @param projectKey  The project key\n     * @param versionName The new fixVersion\n     * @param query       A JQL Query\n     * @throws TimeoutException if too long\n     */\n    public void migrateIssuesToFixVersion(String projectKey, String versionName, String query) throws TimeoutException {\n        if (this.jiraSession == null) {\n            LOGGER.warning(\"Jira session could not be established\");\n            return;\n        }\n        this.jiraSession.migrateIssuesToFixVersion(projectKey, versionName, query);\n    }\n\n    /**\n     * Adds new fix version to issues matching the jql.\n     *\n     * @param projectKey  the project key\n     * @param versionName the version\n     * @param query       the query\n     * @throws TimeoutException if too long\n     */\n    public void addFixVersionToIssue(String projectKey, String versionName, String query)\n            throws TimeoutException, RestClientException {\n        if (this.jiraSession == null) {\n            LOGGER.warning(\"Jira session could not be established\");\n            return;\n        }\n        this.jiraSession.addFixVersion(projectKey, versionName, query);\n    }\n\n    /**\n     * Progresses all issues matching the JQL search, using the given workflow action. Optionally\n     * adds a comment to the issue(s) at the same time.\n     *\n     * @param jqlSearch          the query\n     * @param workflowActionName the workflowActionName\n     * @param comment            the comment\n     * @param console            the console\n     */\n    public boolean progressMatchingIssues(\n            String jqlSearch, String workflowActionName, String comment, PrintStream console)\n            throws RestClientException {\n\n        if (this.jiraSession == null) {\n            LOGGER.warning(\"Jira session could not be established\");\n            console.println(Messages.FailedToConnect());\n            return false;\n        }\n\n        boolean success = true;\n        List<Issue> issues = this.jiraSession.getIssuesFromJqlSearch(jqlSearch);\n\n        if (isEmpty(workflowActionName)) {\n            console.println(\"[Jira] No workflow action was specified, \"\n                    + \"thus no status update will be made for any of the matching issues.\");\n        }\n\n        for (Issue issue : issues) {\n            String issueKey = issue.getKey();\n\n            if (isNotEmpty(comment)) {\n                this.jiraSession.addComment(issueKey, comment, null, null);\n            }\n\n            if (isEmpty(workflowActionName)) {\n                continue;\n            }\n\n            Integer actionId = this.jiraSession.getActionIdForIssue(issueKey, workflowActionName);\n\n            if (actionId == null) {\n                LOGGER.fine(String.format(\n                        \"Invalid workflow action %s for issue %s; issue status = %s\",\n                        workflowActionName, issueKey, issue.getStatus()));\n                console.println(Messages.JiraIssueUpdateBuilder_UnknownWorkflowAction(issueKey, workflowActionName));\n                success = false;\n                continue;\n            }\n\n            String newStatus = this.jiraSession.progressWorkflowAction(issueKey, actionId);\n\n            console.println(String.format(\n                    \"[Jira] Issue %s transitioned to \\\"%s\\\" due to action \\\"%s\\\".\",\n                    issueKey, newStatus, workflowActionName));\n        }\n\n        return success;\n    }\n\n    @Extension\n    public static class DescriptorImpl extends Descriptor<JiraSite> {\n        @Override\n        public String getDisplayName() {\n            return \"Jira Site\";\n        }\n\n        @SuppressWarnings(\"unused\") // used by stapler\n        public FormValidation doCheckUrl(@QueryParameter String value) throws IOException, ServletException {\n            return checkUrl(value);\n        }\n\n        @SuppressWarnings(\"unused\") // used by stapler\n        public FormValidation doCheckAlternativeUrl(@QueryParameter String value) throws IOException, ServletException {\n            return checkUrl(value);\n        }\n\n        private FormValidation checkUrl(String url) {\n            if (Util.fixEmptyAndTrim(url) == null) {\n                return FormValidation.ok();\n            }\n            try {\n                new URL(url);\n            } catch (MalformedURLException e) {\n                return FormValidation.error(String.format(\"Malformed URL (%s)\", url), e);\n            }\n            return FormValidation.ok();\n        }\n\n        /**\n         * Checks if the user name and password are valid.\n         */\n        @RequirePOST\n        public FormValidation doValidate(\n                @QueryParameter String url,\n                @QueryParameter String credentialsId,\n                @QueryParameter String groupVisibility,\n                @QueryParameter String roleVisibility,\n                @QueryParameter boolean useHTTPAuth,\n                @QueryParameter String alternativeUrl,\n                @QueryParameter int timeout,\n                @QueryParameter int readTimeout,\n                @QueryParameter int threadExecutorNumber,\n                @QueryParameter boolean useBearerAuth,\n                @AncestorInPath Item item) {\n\n            if (item == null) {\n                Jenkins.get().checkPermission(Jenkins.ADMINISTER);\n            } else {\n                item.checkPermission(Item.CONFIGURE);\n            }\n\n            url = Util.fixEmpty(url);\n            alternativeUrl = Util.fixEmpty(alternativeUrl);\n            URL mainURL, alternativeURL = null;\n\n            try {\n                if (url == null) {\n                    return FormValidation.error(\"No URL given\");\n                }\n                mainURL = new URL(url);\n            } catch (MalformedURLException e) {\n                return FormValidation.error(String.format(\"Malformed URL (%s)\", url), e);\n            }\n\n            try {\n                if (alternativeUrl != null) {\n                    alternativeURL = new URL(alternativeUrl);\n                }\n            } catch (MalformedURLException e) {\n                return FormValidation.error(String.format(\"Malformed alternative URL (%s)\", alternativeUrl), e);\n            }\n\n            credentialsId = Util.fixEmpty(credentialsId);\n            JiraSite site = getBuilder()\n                    .withMainURL(mainURL)\n                    .withAlternativeURL(alternativeURL)\n                    .withCredentialsId(credentialsId)\n                    .withGroupVisibility(groupVisibility)\n                    .withRoleVisibility(roleVisibility)\n                    .withUseHTTPAuth(useHTTPAuth)\n                    .build();\n\n            if (threadExecutorNumber < 1) {\n                return FormValidation.error(Messages.JiraSite_threadExecutorMinimunSize(\"1\"));\n            }\n            if (timeout < 0) {\n                return FormValidation.error(Messages.JiraSite_timeoutMinimunValue(\"1\"));\n            }\n            if (readTimeout < 0) {\n                return FormValidation.error(Messages.JiraSite_readTimeoutMinimunValue(\"1\"));\n            }\n\n            site.setTimeout(timeout);\n            site.setReadTimeout(readTimeout);\n            site.setThreadExecutorNumber(threadExecutorNumber);\n            site.setUseBearerAuth(useBearerAuth);\n            try {\n                JiraSession session = site.getSession(item, true);\n                if (session == null) {\n                    return FormValidation.error(\"Cannot validate configuration\");\n                }\n                session.getMyPermissions();\n                return FormValidation.ok(\"Success\");\n            } catch (RestClientException e) {\n                LOGGER.log(Level.WARNING, \"Failed to login to Jira at \" + url, e);\n            } finally {\n                if (site != null) {\n                    site.destroy();\n                }\n            }\n\n            return FormValidation.error(\"Failed to login to Jira\");\n        }\n\n        @SuppressWarnings(\"unused\") // Used by stapler\n        public ListBoxModel doFillCredentialsIdItems(\n                @AncestorInPath final Item item,\n                @QueryParameter final String credentialsId,\n                @QueryParameter final String url) {\n            return CredentialsHelper.doFillCredentialsIdItems(item, credentialsId, url);\n        }\n\n        @SuppressWarnings(\"unused\") // Used by stapler\n        public FormValidation doCheckCredentialsId(\n                @AncestorInPath final Item item, @QueryParameter final String value, @QueryParameter final String url) {\n            return CredentialsHelper.doCheckFillCredentialsId(item, value, url);\n        }\n\n        Builder getBuilder() {\n            return new Builder();\n        }\n    }\n\n    static class Builder {\n        private URL mainURL;\n        private URL alternativeURL;\n        private String credentialsId;\n        private boolean supportsWikiStyleComment;\n        private boolean recordScmChanges;\n        private String userPattern;\n        private boolean updateJiraIssueForAllStatus;\n        private String groupVisibility;\n        private String roleVisibility;\n        private boolean useHTTPAuth;\n\n        public Builder withMainURL(URL mainURL) {\n            this.mainURL = mainURL;\n            return this;\n        }\n\n        public Builder withAlternativeURL(URL alternativeURL) {\n            this.alternativeURL = alternativeURL;\n            return this;\n        }\n\n        public Builder withCredentialsId(String credentialsId) {\n            this.credentialsId = credentialsId;\n            return this;\n        }\n\n        public Builder withSupportsWikiStyleComment(boolean supportsWikiStyleComment) {\n            this.supportsWikiStyleComment = supportsWikiStyleComment;\n            return this;\n        }\n\n        public Builder withRecordScmChanges(boolean recordScmChanges) {\n            this.recordScmChanges = recordScmChanges;\n            return this;\n        }\n\n        public Builder withUserPattern(String userPattern) {\n            this.userPattern = userPattern;\n            return this;\n        }\n\n        public Builder withUpdateJiraIssueForAllStatus(boolean updateJiraIssueForAllStatus) {\n            this.updateJiraIssueForAllStatus = updateJiraIssueForAllStatus;\n            return this;\n        }\n\n        public Builder withGroupVisibility(String groupVisibility) {\n            this.groupVisibility = groupVisibility;\n            return this;\n        }\n\n        public Builder withRoleVisibility(String roleVisibility) {\n            this.roleVisibility = roleVisibility;\n            return this;\n        }\n\n        public Builder withUseHTTPAuth(boolean useHTTPAuth) {\n            this.useHTTPAuth = useHTTPAuth;\n            return this;\n        }\n\n        public JiraSite build() {\n            return new JiraSite(\n                    mainURL,\n                    alternativeURL,\n                    credentialsId,\n                    supportsWikiStyleComment,\n                    recordScmChanges,\n                    userPattern,\n                    updateJiraIssueForAllStatus,\n                    groupVisibility,\n                    roleVisibility,\n                    useHTTPAuth);\n        }\n    }\n\n    // helper methods\n    // yes this class hierarchy can be a real big mess...\n\n    /**\n     * @param item the Jenkins {@link Item} can be a {@link Job} or {@link Folder}\n     * @return the parent as {@link ItemGroup} which can be {@link Jenkins} or {@link Folder}\n     */\n    public static ItemGroup map(Item item) {\n        ItemGroup parent = null;\n        if (item != null) {\n            parent = item instanceof Folder ? (Folder) item : item.getParent();\n        }\n        return parent;\n    }\n\n    /**\n     * Creates automatically jiraSession for each jiraSite found\n     */\n    public static List<JiraSite> getJiraSites(Item item) {\n        ItemGroup itemGroup = JiraSite.map(item);\n        List<JiraSite> sites = (itemGroup instanceof Folder)\n                ? getSitesFromFolders(itemGroup)\n                : JiraGlobalConfiguration.get().getSites();\n        sites.stream().forEach(jiraSite -> jiraSite.getSession(item));\n        return sites;\n    }\n\n    /**\n     * Creates automatically jiraSession for each jiraSite found\n     */\n    public static List<JiraSite> getSitesFromFolders(ItemGroup itemGroup) {\n        List<JiraSite> result = new ArrayList<>();\n        while (itemGroup instanceof AbstractFolder<?>) {\n            AbstractFolder<?> folder = (AbstractFolder<?>) itemGroup;\n            JiraFolderProperty jiraFolderProperty = folder.getProperties().get(JiraFolderProperty.class);\n            if (jiraFolderProperty != null && jiraFolderProperty.getSites().length != 0) {\n                List<JiraSite> sites = Arrays.asList(jiraFolderProperty.getSites());\n                // setup session for each so it's ready to use\n                sites.forEach(jiraSite -> jiraSite.getSession(folder));\n                result.addAll(sites);\n            }\n            itemGroup = folder.getParent();\n        }\n        return result;\n    }\n\n    /**\n     * Gets the effective {@link JiraSite} associated with the given project\n     * and creates automatically jiraSession for each jiraSite found\n     *\n     * @return <code>null</code> if no such was found.\n     */\n    @Nullable\n    public static JiraSite get(Job<?, ?> p) {\n        JiraSite found = null;\n        if (p != null) {\n            JiraProjectProperty jpp = p.getProperty(JiraProjectProperty.class);\n            if (jpp != null) {\n                // Looks in global configuration for the site configured\n                JiraSite site = jpp.getSite();\n                if (site != null) {\n                    found = site;\n                }\n            }\n        }\n\n        if (found == null && p != null) {\n            // Check up the folder chain if a site is defined there\n            // This only supports one site per folder\n            List<JiraSite> sitesFromFolders = getSitesFromFolders(p.getParent());\n            if (sitesFromFolders.size() > 0) {\n                found = sitesFromFolders.get(0);\n            }\n        }\n        if (found == null) {\n            // none is explicitly configured. try the default ---\n            // if only one is configured, that must be it.\n            List<JiraSite> sites = JiraGlobalConfiguration.get().getSites();\n            if (sites != null && sites.size() == 1) {\n                found = sites.get(0);\n            }\n        }\n        if (found != null) {\n            // we create the session here\n            found.getSession(p);\n        }\n        return found;\n    }\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/JiraVersionCreator.java",
    "content": "package hudson.plugins.jira;\n\nimport hudson.Extension;\nimport hudson.Launcher;\nimport hudson.model.AbstractBuild;\nimport hudson.model.AbstractProject;\nimport hudson.model.BuildListener;\nimport hudson.tasks.BuildStepDescriptor;\nimport hudson.tasks.BuildStepMonitor;\nimport hudson.tasks.Notifier;\nimport hudson.tasks.Publisher;\nimport net.sf.json.JSONObject;\nimport org.kohsuke.stapler.DataBoundConstructor;\nimport org.kohsuke.stapler.DataBoundSetter;\nimport org.kohsuke.stapler.StaplerRequest2;\n\n/**\n * A build step which creates new Jira version\n *\n * @author Artem Koshelev artkoshelev@gmail.com\n * @deprecated Replaced by {@link JiraVersionCreatorBuilder}. Read its\n * description to see why. Kept for backward compatibility.\n */\n@Deprecated\npublic class JiraVersionCreator extends Notifier {\n\n    private String jiraVersion;\n    private String jiraProjectKey;\n    private Boolean failIfAlreadyExists = true;\n\n    @DataBoundConstructor\n    public JiraVersionCreator(String jiraVersion, String jiraProjectKey) {\n        this.jiraVersion = jiraVersion;\n        this.jiraProjectKey = jiraProjectKey;\n    }\n\n    @Override\n    public BuildStepMonitor getRequiredMonitorService() {\n        return BuildStepMonitor.NONE;\n    }\n\n    public String getJiraVersion() {\n        return jiraVersion;\n    }\n\n    public void setJiraVersion(String jiraVersion) {\n        this.jiraVersion = jiraVersion;\n    }\n\n    public String getJiraProjectKey() {\n        return jiraProjectKey;\n    }\n\n    public void setJiraProjectKey(String jiraProjectKey) {\n        this.jiraProjectKey = jiraProjectKey;\n    }\n\n    public boolean isFailIfAlreadyExists() {\n        return failIfAlreadyExists;\n    }\n\n    @DataBoundSetter\n    public void setFailIfAlreadyExists(boolean failIfAlreadyExists) {\n        this.failIfAlreadyExists = failIfAlreadyExists;\n    }\n\n    @Override\n    public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) {\n        VersionCreator versionCreator = new VersionCreator();\n        versionCreator.setFailIfAlreadyExists(failIfAlreadyExists);\n        versionCreator.setJiraVersion(jiraVersion);\n        versionCreator.setJiraProjectKey(jiraProjectKey);\n        return versionCreator.perform(build.getProject(), build, listener);\n    }\n\n    @Override\n    public BuildStepDescriptor<Publisher> getDescriptor() {\n        return DESCRIPTOR;\n    }\n\n    protected Object readResolve() {\n        if (failIfAlreadyExists == null) {\n            setFailIfAlreadyExists(true);\n        }\n\n        return this;\n    }\n\n    @Extension\n    public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl();\n\n    public static class DescriptorImpl extends BuildStepDescriptor<Publisher> {\n\n        public DescriptorImpl() {\n            super(JiraVersionCreator.class);\n        }\n\n        @Override\n        public boolean isApplicable(Class<? extends AbstractProject> jobType) {\n            return true;\n        }\n\n        @Override\n        public JiraVersionCreator newInstance(StaplerRequest2 req, JSONObject formData) throws FormException {\n            return req.bindJSON(JiraVersionCreator.class, formData);\n        }\n\n        @Override\n        public String getDisplayName() {\n            return Messages.JiraVersionCreator_DisplayName();\n        }\n\n        @Override\n        public String getHelpFile() {\n            return \"/plugin/jira/help-version-create.html\";\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/JiraVersionCreatorBuilder.java",
    "content": "package hudson.plugins.jira;\n\nimport hudson.EnvVars;\nimport hudson.Extension;\nimport hudson.model.AbstractProject;\nimport hudson.model.Run;\nimport hudson.model.TaskListener;\nimport hudson.tasks.BuildStepDescriptor;\nimport hudson.tasks.BuildStepMonitor;\nimport hudson.tasks.Builder;\nimport jenkins.tasks.SimpleBuildStep;\nimport net.sf.json.JSONObject;\nimport org.jenkinsci.Symbol;\nimport org.kohsuke.stapler.DataBoundConstructor;\nimport org.kohsuke.stapler.DataBoundSetter;\nimport org.kohsuke.stapler.StaplerRequest2;\n\n/**\n * A build step which creates new Jira version. It has the same functionality as\n * {@link JiraVersionCreator} but can be used multiple times in the same build\n * (e.g. for different projects) and supports conditional triggering.\n *\n * @author marcin.czerwinski marcinczrw@gmail.com\n */\npublic class JiraVersionCreatorBuilder extends Builder implements SimpleBuildStep {\n\n    private String jiraVersion;\n    private String jiraProjectKey;\n    private Boolean failIfAlreadyExists = true;\n\n    @DataBoundConstructor\n    public JiraVersionCreatorBuilder(String jiraVersion, String jiraProjectKey) {\n        this.jiraVersion = jiraVersion;\n        this.jiraProjectKey = jiraProjectKey;\n    }\n\n    @Override\n    public BuildStepMonitor getRequiredMonitorService() {\n        return BuildStepMonitor.NONE;\n    }\n\n    public String getJiraVersion() {\n        return jiraVersion;\n    }\n\n    public void setJiraVersion(String jiraVersion) {\n        this.jiraVersion = jiraVersion;\n    }\n\n    public String getJiraProjectKey() {\n        return jiraProjectKey;\n    }\n\n    public void setJiraProjectKey(String jiraProjectKey) {\n        this.jiraProjectKey = jiraProjectKey;\n    }\n\n    public boolean isFailIfAlreadyExists() {\n        return failIfAlreadyExists;\n    }\n\n    protected Object readResolve() {\n        if (failIfAlreadyExists == null) {\n            setFailIfAlreadyExists(true);\n        }\n\n        return this;\n    }\n\n    @DataBoundSetter\n    public void setFailIfAlreadyExists(boolean failIfAlreadyExists) {\n        this.failIfAlreadyExists = failIfAlreadyExists;\n    }\n\n    @Override\n    public void perform(Run<?, ?> run, EnvVars env, TaskListener listener) {\n        VersionCreator versionCreator = new VersionCreator();\n        versionCreator.setFailIfAlreadyExists(failIfAlreadyExists);\n        versionCreator.setJiraVersion(jiraVersion);\n        versionCreator.setJiraProjectKey(jiraProjectKey);\n        versionCreator.perform(run.getParent(), run, listener);\n    }\n\n    @Override\n    public boolean requiresWorkspace() {\n        return false;\n    }\n\n    @Override\n    public BuildStepDescriptor<Builder> getDescriptor() {\n        return DESCRIPTOR;\n    }\n\n    @Extension\n    public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl();\n\n    @Symbol(\"jiraCreateVersion\")\n    public static class DescriptorImpl extends BuildStepDescriptor<Builder> {\n\n        public DescriptorImpl() {\n            super(JiraVersionCreatorBuilder.class);\n        }\n\n        @Override\n        public boolean isApplicable(Class<? extends AbstractProject> jobType) {\n            return true;\n        }\n\n        @Override\n        public JiraVersionCreatorBuilder newInstance(StaplerRequest2 req, JSONObject formData) throws FormException {\n            return req.bindJSON(JiraVersionCreatorBuilder.class, formData);\n        }\n\n        @Override\n        public String getDisplayName() {\n            return Messages.JiraVersionCreatorBuilder_DisplayName();\n        }\n\n        @Override\n        public String getHelpFile() {\n            return \"/plugin/jira/help-version-create.html\";\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/RunScmChangeExtractor.java",
    "content": "package hudson.plugins.jira;\n\nimport hudson.model.AbstractBuild;\nimport hudson.model.AbstractBuild.DependencyChange;\nimport hudson.model.AbstractProject;\nimport hudson.model.Run;\nimport hudson.scm.ChangeLogSet;\nimport hudson.scm.ChangeLogSet.Entry;\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Method;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\npublic class RunScmChangeExtractor {\n\n    // for reflection, until JENKINS-24141\n    private static final String GET_CHANGESET_METHOD = \"getChangeSets\";\n    private static final String CANNOT_ACCESS_GET_CHANGESET_METHOD = \"cannot call \" + GET_CHANGESET_METHOD;\n\n    private RunScmChangeExtractor() {}\n\n    public static List<ChangeLogSet<? extends Entry>> getChanges(Run<?, ?> run) {\n        if (run instanceof AbstractBuild) {\n            ChangeLogSet<? extends Entry> cs = ((AbstractBuild<?, ?>) run).getChangeSet();\n            return cs.isEmptySet()\n                    ? Collections.<ChangeLogSet<? extends ChangeLogSet.Entry>>emptyList()\n                    : Collections.<ChangeLogSet<? extends ChangeLogSet.Entry>>singletonList(cs);\n        } else if (run == null) {\n            throw new IllegalStateException(\"run cannot be null!\");\n        } else {\n            return getChangesUsingReflection(run);\n        }\n    }\n\n    /**\n     * return changeSets using java reflection api, for example for workflow\n     * jobs.\n     *\n     * until JENKINS-24141\n     *\n     * @param run\n     *            - run that implements some type with GET_CHANGESET_METHOD\n     * @return collection of scm ChangeLogSet entries\n     */\n    @SuppressWarnings(\"unchecked\")\n    static List<ChangeLogSet<? extends Entry>> getChangesUsingReflection(Run<?, ?> run) {\n        Method getChangeSetMethod = null;\n        for (Method method : run.getClass().getMethods()) {\n            if (method.getName().equals(GET_CHANGESET_METHOD) && List.class.isAssignableFrom(method.getReturnType())) {\n                getChangeSetMethod = method;\n                break;\n            }\n        }\n        if (getChangeSetMethod != null) {\n            try {\n                Object result = getChangeSetMethod.invoke(run, new Object[] {});\n                return (List<ChangeLogSet<? extends Entry>>) result;\n            } catch (IllegalAccessException e) {\n                throw new IllegalStateException(CANNOT_ACCESS_GET_CHANGESET_METHOD, e);\n            } catch (IllegalArgumentException e) {\n                throw new IllegalStateException(CANNOT_ACCESS_GET_CHANGESET_METHOD, e);\n            } catch (InvocationTargetException e) {\n                throw new IllegalStateException(CANNOT_ACCESS_GET_CHANGESET_METHOD, e);\n            }\n        } else {\n            // if run don't have GET_CHANGESET_METHOD, we don't support it\n            throw new IllegalArgumentException(\n                    \"Unsupported Run type \" + run.getClass().getName());\n        }\n    }\n\n    public static Map<AbstractProject, DependencyChange> getDependencyChanges(Run<?, ?> run) {\n        if (run instanceof AbstractBuild) {\n            Run<?, ?> previousBuild = run.getPreviousCompletedBuild();\n            if (previousBuild instanceof AbstractBuild) {\n                return ((AbstractBuild) run).getDependencyChanges((AbstractBuild) previousBuild);\n            }\n        }\n        // jenkins workflow plugin etc.\n        return new HashMap();\n    }\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/Updater.java",
    "content": "package hudson.plugins.jira;\n\nimport static java.lang.String.format;\n\nimport com.atlassian.jira.rest.client.api.RestClientException;\nimport com.atlassian.jira.rest.client.api.domain.Issue;\nimport hudson.Util;\nimport hudson.model.Result;\nimport hudson.model.Run;\nimport hudson.model.TaskListener;\nimport hudson.plugins.jira.model.JiraIssue;\nimport hudson.plugins.jira.selector.AbstractIssueSelector;\nimport hudson.scm.ChangeLogSet;\nimport hudson.scm.ChangeLogSet.AffectedFile;\nimport hudson.scm.ChangeLogSet.Entry;\nimport hudson.scm.RepositoryBrowser;\nimport hudson.scm.SCM;\nimport java.io.IOException;\nimport java.io.PrintStream;\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Method;\nimport java.net.URL;\nimport java.rmi.RemoteException;\nimport java.text.DateFormat;\nimport java.text.SimpleDateFormat;\nimport java.util.ArrayList;\nimport java.util.Date;\nimport java.util.HashSet;\nimport java.util.LinkedHashSet;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.Set;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\nimport jenkins.model.Jenkins;\nimport org.apache.commons.lang3.StringUtils;\n\n/**\n * Actual Jira update logic.\n *\n * @author Kohsuke Kawaguchi\n */\nclass Updater {\n\n    private SCM scm;\n    private List<String> labels;\n\n    private static final Logger LOGGER = Logger.getLogger(Updater.class.getName());\n\n    /**\n     * Debug flag.\n     */\n    public static boolean debug = false;\n\n    Updater(SCM scm) {\n        this(scm, new ArrayList<>());\n    }\n\n    Updater(SCM scm, List<String> labels) {\n        this.scm = scm;\n        if (labels == null) {\n            this.labels = new ArrayList<>();\n        } else {\n            this.labels = labels;\n        }\n    }\n\n    boolean perform(Run<?, ?> run, TaskListener listener, AbstractIssueSelector selector) {\n        PrintStream logger = listener.getLogger();\n        Set<JiraIssue> issues = null;\n\n        try {\n            JiraSite site = JiraSite.get(run.getParent());\n            if (site == null) {\n                logger.println(Messages.NoJiraSite());\n                run.setResult(Result.FAILURE);\n                return true;\n            }\n\n            String rootUrl = Jenkins.get().getRootUrl();\n            if (rootUrl == null) {\n                logger.println(Messages.NoJenkinsUrl());\n                run.setResult(Result.FAILURE);\n                return true;\n            }\n\n            Set<String> ids = selector.findIssueIds(run, site, listener);\n\n            if (ids.isEmpty()) {\n                if (debug) {\n                    logger.println(\"No Jira issues found.\");\n                }\n                return true; // nothing found here.\n            }\n\n            JiraSession session = site.getSession(run.getParent());\n            if (session == null) {\n                logger.println(Messages.NoRemoteAccess());\n                run.setResult(Result.FAILURE);\n                return true;\n            }\n\n            boolean doUpdate = false;\n            // in case of workflow, it may be null\n            if (site.updateJiraIssueForAllStatus || run.getResult() == null) {\n                doUpdate = true;\n            } else {\n                doUpdate = run.getResult().isBetterOrEqualTo(Result.UNSTABLE);\n            }\n            boolean useWikiStyleComments = site.supportsWikiStyleComment;\n\n            issues = getJiraIssues(ids, session, logger);\n            run.addAction(new JiraBuildAction(issues));\n\n            if (doUpdate) {\n                submitComments(\n                        run,\n                        logger,\n                        rootUrl,\n                        issues,\n                        session,\n                        useWikiStyleComments,\n                        site.recordScmChanges,\n                        site.groupVisibility,\n                        site.roleVisibility);\n            } else {\n                // this build didn't work, so carry forward the issues to the next build\n                run.addAction(new JiraCarryOverAction(issues));\n            }\n        } catch (Exception e) {\n            LOGGER.log(Level.WARNING, \"Error updating Jira issues. Saving issues for next build.\", e);\n            logger.println(e.getMessage());\n            if (issues != null && !issues.isEmpty()) {\n                // updating issues failed, so carry forward issues to the next build\n                run.addAction(new JiraCarryOverAction(issues));\n            }\n        }\n\n        return true;\n    }\n\n    /**\n     * Submits comments for the given issues.\n     * Removes from <code>issues</code> issues which have been successfully updated or are invalid\n     *\n     * @param build build\n     * @param logger logger\n     * @param jenkinsRootUrl jenkins root URL\n     * @param session session\n     * @param useWikiStyleComments whether to use wiki style comments\n     * @param recordScmChanges whether to record SCM changes\n     * @param groupVisibility group visibility\n     * @throws RestClientException when HTTP request fails\n     */\n    void submitComments(\n            Run<?, ?> build,\n            PrintStream logger,\n            String jenkinsRootUrl,\n            Set<JiraIssue> issues,\n            JiraSession session,\n            boolean useWikiStyleComments,\n            boolean recordScmChanges,\n            String groupVisibility,\n            String roleVisibility) {\n\n        // copy to prevent ConcurrentModificationException\n        Set<JiraIssue> copy = new HashSet<>(issues);\n\n        for (JiraIssue issue : copy) {\n            logger.println(Messages.UpdatingIssue(issue.getKey()));\n\n            try {\n                session.addComment(\n                        issue.getKey(),\n                        createComment(build, useWikiStyleComments, jenkinsRootUrl, recordScmChanges, issue),\n                        groupVisibility,\n                        roleVisibility);\n                if (!labels.isEmpty()) {\n                    session.addLabels(issue.getKey(), labels);\n                }\n\n            } catch (RestClientException e) {\n\n                if (e.getStatusCode().or(0).equals(404)) {\n                    logger.println(issue.getKey() + \" - Jira issue not found. Dropping comment from update queue.\");\n                }\n\n                if (e.getStatusCode().or(0).equals(403)) {\n                    logger.println(\n                            issue.getKey()\n                                    + \" - Jenkins Jira user does not have permissions to comment on this issue. Preserving comment for future update.\");\n                    continue;\n                }\n\n                if (e.getStatusCode().or(0).equals(401)) {\n                    logger.println(issue.getKey()\n                            + \" - Jenkins Jira authentication problem. Preserving comment for future update.\");\n                    continue;\n                }\n\n                logger.println(Messages.FailedToUpdateIssueWithCarryOver(issue.getKey()));\n                logger.println(e.getLocalizedMessage());\n            }\n\n            // if no exception is thrown during update, remove from the list as successfully updated\n            issues.remove(issue);\n        }\n    }\n\n    private static Set<JiraIssue> getJiraIssues(Set<String> ids, JiraSession session, PrintStream logger)\n            throws RemoteException {\n        Set<JiraIssue> issues = new LinkedHashSet<>(ids.size());\n        for (String id : ids) {\n            Issue issue = session.getIssue(id);\n            if (issue == null) {\n                logger.println(id + \" issue doesn't exist in Jira\");\n                continue;\n            }\n\n            issues.add(new JiraIssue(issue));\n        }\n        return issues;\n    }\n\n    /**\n     * Creates a comment to be used in Jira for the build.\n     * For example:\n     * <pre>\n     *  SUCCESS: Integrated in Job #nnnn (See [http://jenkins.domain/job/Job/nnnn/])\\r\n     *  JIRA-XXXX: Commit message. (Author _author@email.domain_:\n     *  [https://bitbucket.org/user/repo/changeset/9af8e4c4c909/])\\r\n     * </pre>\n     */\n    private String createComment(\n            Run<?, ?> build, boolean wikiStyle, String jenkinsRootUrl, boolean recordScmChanges, JiraIssue jiraIssue) {\n        Result result = build.getResult();\n        // if we run from workflow we dont known final result\n        if (result == null) {\n            return format(\n                    wikiStyle\n                            ? \"Integrated in [%2$s|%3$s]\\n%4$s\"\n                            : \"Integrated in Jenkins build %2$s (See [%3$s])\\n%4$s\",\n                    jenkinsRootUrl,\n                    build.getFullDisplayName(),\n                    Util.encode(jenkinsRootUrl + build.getUrl()),\n                    getScmComments(wikiStyle, build, recordScmChanges, jiraIssue));\n        } else {\n            return format(\n                    wikiStyle\n                            ? \"%6$s: Integrated in !%1$simages/16x16/%3$s! [%2$s|%4$s]\\n%5$s\"\n                            : \"%6$s: Integrated in Jenkins build %2$s (See [%4$s])\\n%5$s\",\n                    jenkinsRootUrl,\n                    build.getFullDisplayName(),\n                    result.color.getImage(),\n                    Util.encode(jenkinsRootUrl + build.getUrl()),\n                    getScmComments(wikiStyle, build, recordScmChanges, jiraIssue),\n                    result.toString());\n        }\n    }\n\n    private String getScmComments(boolean wikiStyle, Run<?, ?> run, boolean recordScmChanges, JiraIssue jiraIssue) {\n        StringBuilder comment = new StringBuilder();\n        for (ChangeLogSet<? extends Entry> set : RunScmChangeExtractor.getChanges(run)) {\n            for (Entry change : set) {\n                if (jiraIssue != null && !StringUtils.containsIgnoreCase(change.getMsg(), jiraIssue.getKey())) {\n                    continue;\n                }\n                comment.append(createScmChangeEntryDescription(run, change, wikiStyle, recordScmChanges));\n            }\n        }\n\n        if (jiraIssue != null) {\n            final Run<?, ?> prev = run.getPreviousCompletedBuild();\n            if (prev != null) {\n                final JiraCarryOverAction a = prev.getAction(JiraCarryOverAction.class);\n                if (a != null && a.getIDs().contains(jiraIssue.getKey())) {\n                    comment.append(getScmComments(wikiStyle, prev, recordScmChanges, jiraIssue));\n                }\n            }\n        }\n\n        return comment.toString();\n    }\n\n    protected String createScmChangeEntryDescription(\n            Run<?, ?> run, Entry change, boolean wikiStyle, boolean recordScmChanges) {\n        StringBuilder description = new StringBuilder();\n        RepositoryBrowser repoBrowser = getRepositoryBrowser(run);\n        JiraSite site = JiraSite.get(run.getParent());\n\n        if (change.getMsg() != null) {\n            description.append(change.getMsg());\n        }\n        String revision = getRevision(change);\n        if (revision != null) {\n            description.append(\" (\");\n            appendAuthorToDescription(change, description);\n            if (site.isAppendChangeTimestamp() && change.getTimestamp() > 0) {\n                appendChangeTimestampToDescription(description, site, change.getTimestamp());\n                description.append(\" \");\n            }\n            appendRevisionToDescription(change, wikiStyle, description, repoBrowser, revision);\n            description.append(\")\");\n        }\n        description.append(\"\\n\");\n        if (recordScmChanges) {\n            appendAffectedFilesToDescription(change, description);\n        }\n        return description.toString();\n    }\n\n    protected void appendAuthorToDescription(Entry change, StringBuilder description) {\n        if (change.getAuthor() != null) {\n            change.getAuthor();\n            String uid = change.getAuthor().getId();\n            if (StringUtils.isNotBlank(uid)) {\n                description.append(uid).append(\": \");\n            }\n        }\n    }\n\n    protected void appendRevisionToDescription(\n            Entry change,\n            boolean wikiStyle,\n            StringBuilder description,\n            RepositoryBrowser repoBrowser,\n            String revision) {\n        URL url = null;\n        if (repoBrowser != null) {\n            try {\n                url = repoBrowser.getChangeSetLink(change);\n            } catch (IOException e) {\n                LOGGER.log(Level.WARNING, \"Failed to calculate SCM repository browser link\", e);\n            }\n        }\n        if (url != null && StringUtils.isNotBlank(url.toExternalForm())) {\n            if (wikiStyle) {\n                description.append(\"[\").append(revision).append(\"|\");\n                description.append(url.toExternalForm()).append(\"]\");\n            } else {\n                description.append(\"[\").append(url.toExternalForm()).append(\"]\");\n            }\n        } else {\n            description.append(\"rev \").append(revision);\n        }\n    }\n\n    protected void appendAffectedFilesToDescription(Entry change, StringBuilder description) {\n        // see http://issues.jenkins-ci.org/browse/JENKINS-2508\n        // added additional try .. catch; getAffectedFiles is not supported\n        // by all SCM implementations\n        try {\n            for (AffectedFile affectedFile : change.getAffectedFiles()) {\n                description.append(\"* \");\n                if (affectedFile.getEditType() != null) {\n                    description\n                            .append(\"(\")\n                            .append(affectedFile.getEditType().getName())\n                            .append(\") \");\n                }\n                if (affectedFile.getPath() != null) {\n                    description.append(affectedFile.getPath());\n                }\n                description.append(\"\\n\");\n            }\n        } catch (UnsupportedOperationException e) {\n            LOGGER.warning(\"Unsupported SCM operation 'getAffectedFiles'. Fall back to getAffectedPaths.\");\n            for (String affectedPath : change.getAffectedPaths()) {\n                description.append(\"* \").append(affectedPath).append(\"\\n\");\n            }\n        }\n    }\n\n    protected void appendChangeTimestampToDescription(StringBuilder description, JiraSite site, long timestamp) {\n        DateFormat df = null;\n        if (StringUtils.isBlank(site.getDateTimePattern())) {\n            // default format for current locale\n            df = new SimpleDateFormat(\"d/M/yy hh:mm a\", Locale.getDefault());\n        } else {\n            df = new SimpleDateFormat(site.getDateTimePattern());\n        }\n        Date changeDate = new Date(timestamp);\n        String dateTimeString = df.format(changeDate);\n        description.append(dateTimeString);\n    }\n\n    private RepositoryBrowser<?> getRepositoryBrowser(Run<?, ?> run) {\n        SCM scm = getScm();\n        if (scm != null) {\n            return scm.getEffectiveBrowser();\n        }\n        return null;\n    }\n\n    private static String getRevision(Entry entry) {\n        String commitId = entry.getCommitId();\n        if (commitId != null) {\n            return commitId;\n        }\n\n        // fall back to old SVN-specific solution, if we have only installed an old subversion-plugin\n        // which doesn't implement getCommitId, yet\n        try {\n            Class<?> clazz = entry.getClass();\n            Method method = clazz.getMethod(\"getRevision\", (Class[]) null);\n            Object revObj = method.invoke(entry, (Object[]) null);\n            return (revObj != null) ? revObj.toString() : null;\n        } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {\n            return null;\n        }\n    }\n\n    private SCM getScm() {\n        return scm;\n    }\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/VersionCreator.java",
    "content": "package hudson.plugins.jira;\n\nimport static org.apache.commons.lang3.StringUtils.isEmpty;\n\nimport hudson.model.BuildListener;\nimport hudson.model.Job;\nimport hudson.model.Result;\nimport hudson.model.Run;\nimport hudson.model.TaskListener;\nimport hudson.plugins.jira.extension.ExtendedVersion;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.logging.Logger;\n\n/**\n * Performs an action which creates new Jira version.\n */\nclass VersionCreator {\n\n    private static final Logger LOGGER = Logger.getLogger(VersionCreator.class.getName());\n\n    private boolean failIfAlreadyExists = true;\n\n    private String jiraVersion;\n\n    private String jiraProjectKey;\n\n    public VersionCreator setFailIfAlreadyExists(boolean failIfAlreadyExists) {\n        this.failIfAlreadyExists = failIfAlreadyExists;\n        return this;\n    }\n\n    public VersionCreator setJiraVersion(String jiraVersion) {\n        this.jiraVersion = jiraVersion;\n        return this;\n    }\n\n    public VersionCreator setJiraProjectKey(String jiraProjectKey) {\n        this.jiraProjectKey = jiraProjectKey;\n        return this;\n    }\n\n    protected boolean perform(Job<?, ?> project, Run<?, ?> build, TaskListener listener) {\n        String realVersion = null;\n        String realProjectKey = null;\n\n        try {\n            realVersion = build.getEnvironment(listener).expand(jiraVersion);\n            realProjectKey = build.getEnvironment(listener).expand(jiraProjectKey);\n\n            if (isEmpty(realVersion)) {\n                throw new IllegalArgumentException(\"No version specified\");\n            }\n            if (isEmpty(realProjectKey)) {\n                throw new IllegalArgumentException(\"No project specified\");\n            }\n\n            String finalRealVersion = realVersion;\n            JiraSession session = getSiteForProject(project).getSession(project);\n\n            List<ExtendedVersion> existingVersions =\n                    Optional.ofNullable(session.getVersions(realProjectKey)).orElse(Collections.emptyList());\n\n            // check if version already exists\n            if (existingVersions.stream().anyMatch(v -> v.getName().equals(finalRealVersion))) {\n                listener.getLogger().println(Messages.JiraVersionCreator_VersionExists(realVersion, realProjectKey));\n                if (failIfAlreadyExists) {\n                    if (listener instanceof BuildListener) {\n                        ((BuildListener) listener).finished(Result.FAILURE);\n                    }\n                    return false;\n                }\n                // version exists but we don't fail the build\n                return true;\n            }\n\n            listener.getLogger().println(Messages.JiraVersionCreator_CreatingVersion(realVersion, realProjectKey));\n            addVersion(realVersion, realProjectKey, session);\n            return true;\n        } catch (Exception e) {\n            e.printStackTrace(\n                    listener.fatalError(\"Unable to add version %s to Jira project %s\", realVersion, realProjectKey, e));\n        }\n\n        return false;\n    }\n\n    /**\n     * Creates given version in given project\n     * @param version version name\n     * @param projectKey project key\n     * @param session session\n     */\n    protected void addVersion(String version, String projectKey, JiraSession session) {\n        if (session == null) {\n            LOGGER.warning(\"Jira session could not be established\");\n            return;\n        }\n\n        session.addVersion(version, projectKey);\n    }\n\n    protected JiraSite getSiteForProject(Job<?, ?> project) {\n        return JiraSite.get(project);\n    }\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/VersionReleaser.java",
    "content": "package hudson.plugins.jira;\n\nimport hudson.model.BuildListener;\nimport hudson.model.Job;\nimport hudson.model.Result;\nimport hudson.model.Run;\nimport hudson.model.TaskListener;\nimport hudson.plugins.jira.extension.ExtendedVersion;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.logging.Logger;\nimport org.apache.commons.lang3.StringUtils;\nimport org.joda.time.DateTime;\n\n/**\n * used by JiraReleaseVersionUpdaterBuilder to mark a version as released\n */\npublic class VersionReleaser {\n\n    private static final Logger LOGGER = Logger.getLogger(VersionReleaser.class.getName());\n\n    protected boolean perform(\n            Job<?, ?> project,\n            String jiraProjectKey,\n            String jiraRelease,\n            String jiraDescription,\n            Run<?, ?> run,\n            TaskListener listener) {\n        String realRelease = \"NOT_SET\";\n        String realProjectKey = null;\n        String realDescription;\n\n        try {\n            realProjectKey = run.getEnvironment(listener).expand(jiraProjectKey);\n            realRelease = run.getEnvironment(listener).expand(jiraRelease);\n            realDescription = run.getEnvironment(listener).expand(jiraDescription);\n\n            if (StringUtils.isEmpty(realRelease)) {\n                throw new IllegalArgumentException(\"Release is Empty\");\n            }\n\n            if (StringUtils.isEmpty(realProjectKey)) {\n                throw new IllegalArgumentException(\"No project specified\");\n            }\n\n            String finalRealRelease = realRelease;\n            JiraSite site = getSiteForProject(project);\n            Optional<ExtendedVersion> sameNamedVersion = site.getVersions(realProjectKey).stream()\n                    .filter(version -> version.getName().equals(finalRealRelease) && version.isReleased())\n                    .findFirst();\n\n            if (sameNamedVersion.isPresent()) {\n                listener.getLogger().println(Messages.VersionReleaser_AlreadyReleased(realRelease, realProjectKey));\n            } else {\n                listener.getLogger().println(Messages.VersionReleaser_MarkingReleased(realRelease, realProjectKey));\n                releaseVersion(realProjectKey, realRelease, realDescription, site.getSession(project));\n            }\n        } catch (Exception e) {\n            listener.fatalError(\"Unable to release jira version %s/%s: %s\", realRelease, realProjectKey, e);\n            if (listener instanceof BuildListener) {\n                ((BuildListener) listener).finished(Result.FAILURE);\n            }\n            return false;\n        }\n        return true;\n    }\n\n    /**\n     * Release a given version.\n     *\n     * @param projectKey  The Project Key\n     * @param versionName The name of the version\n     * @param versionDescription The description of the version\n     */\n    protected void releaseVersion(\n            String projectKey, String versionName, String versionDescription, JiraSession session) {\n        if (session == null) {\n            LOGGER.warning(\"Jira session could not be established\");\n            return;\n        }\n\n        List<ExtendedVersion> versions = session.getVersions(projectKey);\n        java.util.Optional<ExtendedVersion> matchingVersion = versions.stream()\n                .filter(version -> version.getName().equals(versionName))\n                .findFirst();\n\n        if (matchingVersion.isPresent()) {\n            ExtendedVersion version = matchingVersion.get();\n            ExtendedVersion releaseVersion = new ExtendedVersion(\n                    version.getSelf(),\n                    version.getId(),\n                    version.getName(),\n                    !StringUtils.isEmpty(versionDescription) ? versionDescription : version.getDescription(),\n                    version.isArchived(),\n                    true,\n                    version.getStartDate(),\n                    new DateTime());\n            session.releaseVersion(projectKey, releaseVersion);\n        }\n    }\n\n    protected JiraSite getSiteForProject(Job<?, ?> project) {\n        return JiraSite.get(project);\n    }\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/auth/BearerHttpAuthenticationHandler.java",
    "content": "package hudson.plugins.jira.auth;\n\nimport com.atlassian.httpclient.api.Request.Builder;\nimport com.atlassian.jira.rest.client.api.AuthenticationHandler;\n\n/**\n * Authentication handler for bearer authentication\n *\n * @author Elia Bracci\n */\npublic class BearerHttpAuthenticationHandler implements AuthenticationHandler {\n\n    private static final String AUTHORIZATION_HEADER = \"Authorization\";\n    private final String token;\n\n    /**\n     * Bearer http authentication handler constructor\n     * @param token pat or api token to use for bearer authentication\n     */\n    public BearerHttpAuthenticationHandler(final String token) {\n        this.token = token;\n    }\n\n    @Override\n    public void configure(Builder builder) {\n        builder.setHeader(AUTHORIZATION_HEADER, \"Bearer \" + token);\n    }\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/extension/ExtendedAsynchronousJiraRestClient.java",
    "content": "package hudson.plugins.jira.extension;\n\nimport com.atlassian.jira.rest.client.internal.async.AsynchronousJiraRestClient;\nimport com.atlassian.jira.rest.client.internal.async.DisposableHttpClient;\nimport java.net.URI;\nimport javax.ws.rs.core.UriBuilder;\n\npublic class ExtendedAsynchronousJiraRestClient extends AsynchronousJiraRestClient implements ExtendedJiraRestClient {\n    private final ExtendedVersionRestClient extendedVersionRestClient;\n    private final ExtendedMyPermissionsRestClient extendedMyPermissionsRestClient;\n\n    public ExtendedAsynchronousJiraRestClient(URI serverUri, DisposableHttpClient httpClient) {\n        super(serverUri, httpClient);\n        final URI baseUri =\n                UriBuilder.fromUri(serverUri).path(\"/rest/api/latest\").build();\n        extendedVersionRestClient = new ExtendedAsynchronousVersionRestClient(baseUri, httpClient);\n        extendedMyPermissionsRestClient = new ExtendedAsynchronousMyPermissionsRestClient(baseUri, httpClient);\n    }\n\n    @Override\n    public ExtendedVersionRestClient getExtendedVersionRestClient() {\n        return extendedVersionRestClient;\n    }\n\n    @Override\n    public ExtendedMyPermissionsRestClient getExtendedMyPermissionsRestClient() {\n        return extendedMyPermissionsRestClient;\n    }\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/extension/ExtendedAsynchronousMyPermissionsRestClient.java",
    "content": "package hudson.plugins.jira.extension;\n\nimport com.atlassian.httpclient.api.HttpClient;\nimport com.atlassian.jira.rest.client.api.domain.Permissions;\nimport com.atlassian.jira.rest.client.internal.async.AsynchronousMyPermissionsRestClient;\nimport com.atlassian.jira.rest.client.internal.json.PermissionsJsonParser;\nimport io.atlassian.util.concurrent.Promise;\nimport java.net.URI;\nimport javax.ws.rs.core.UriBuilder;\n\npublic class ExtendedAsynchronousMyPermissionsRestClient extends AsynchronousMyPermissionsRestClient\n        implements ExtendedMyPermissionsRestClient {\n\n    private static final String URI_PREFIX = \"mypermissions\";\n    private final URI baseUri;\n    private final PermissionsJsonParser permissionsJsonParser = new PermissionsJsonParser();\n\n    ExtendedAsynchronousMyPermissionsRestClient(final URI baseUri, final HttpClient client) {\n        super(baseUri, client);\n        this.baseUri = baseUri;\n    }\n\n    @Override\n    public Promise<Permissions> getMyPermissions() {\n        final UriBuilder uriBuilder = UriBuilder.fromUri(baseUri).path(URI_PREFIX);\n        uriBuilder.queryParam(\"permissions\", \"BROWSE_PROJECTS\");\n        return getAndParse(uriBuilder.build(), permissionsJsonParser);\n    }\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/extension/ExtendedAsynchronousVersionRestClient.java",
    "content": "package hudson.plugins.jira.extension;\n\nimport com.atlassian.httpclient.api.HttpClient;\nimport com.atlassian.jira.rest.client.internal.async.AsynchronousVersionRestClient;\nimport io.atlassian.util.concurrent.Promise;\nimport java.net.URI;\nimport javax.ws.rs.core.UriBuilder;\n\npublic class ExtendedAsynchronousVersionRestClient extends AsynchronousVersionRestClient\n        implements ExtendedVersionRestClient {\n    private final URI versionRootUri;\n\n    ExtendedAsynchronousVersionRestClient(URI baseUri, HttpClient client) {\n        super(baseUri, client);\n        versionRootUri = UriBuilder.fromUri(baseUri).path(\"version\").build();\n    }\n\n    @Override\n    public Promise<ExtendedVersion> getExtendedVersion(URI versionUri) {\n        return getAndParse(versionUri, new ExtendedVersionJsonParser());\n    }\n\n    @Override\n    public Promise<ExtendedVersion> createExtendedVersion(ExtendedVersionInput versionInput) {\n        return postAndParse(\n                versionRootUri, versionInput, new ExtendedVersionInputJsonGenerator(), new ExtendedVersionJsonParser());\n    }\n\n    @Override\n    public Promise<ExtendedVersion> updateExtendedVersion(URI versionUri, ExtendedVersionInput versionInput) {\n        return putAndParse(\n                versionUri, versionInput, new ExtendedVersionInputJsonGenerator(), new ExtendedVersionJsonParser());\n    }\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/extension/ExtendedJiraRestClient.java",
    "content": "package hudson.plugins.jira.extension;\n\nimport com.atlassian.jira.rest.client.api.JiraRestClient;\n\npublic interface ExtendedJiraRestClient extends JiraRestClient {\n    ExtendedVersionRestClient getExtendedVersionRestClient();\n\n    ExtendedMyPermissionsRestClient getExtendedMyPermissionsRestClient();\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/extension/ExtendedMyPermissionsRestClient.java",
    "content": "package hudson.plugins.jira.extension;\n\nimport com.atlassian.jira.rest.client.api.domain.Permissions;\nimport io.atlassian.util.concurrent.Promise;\n\npublic interface ExtendedMyPermissionsRestClient {\n\n    Promise<Permissions> getMyPermissions();\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/extension/ExtendedVersion.java",
    "content": "package hudson.plugins.jira.extension;\n\nimport com.atlassian.jira.rest.client.api.domain.Version;\nimport edu.umd.cs.findbugs.annotations.Nullable;\nimport java.net.URI;\nimport org.joda.time.DateTime;\n\npublic class ExtendedVersion extends Version {\n    private final DateTime startDate;\n\n    public ExtendedVersion(\n            URI self,\n            @Nullable Long id,\n            String name,\n            String description,\n            boolean archived,\n            boolean released,\n            @Nullable DateTime startDate,\n            @Nullable DateTime releaseDate) {\n        super(self, id, name, description, archived, released, releaseDate);\n        this.startDate = startDate;\n    }\n\n    @Nullable\n    public DateTime getStartDate() {\n        return startDate;\n    }\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/extension/ExtendedVersionInput.java",
    "content": "package hudson.plugins.jira.extension;\n\nimport com.atlassian.jira.rest.client.api.domain.input.VersionInput;\nimport edu.umd.cs.findbugs.annotations.Nullable;\nimport java.util.Objects;\nimport org.apache.commons.lang3.builder.ToStringBuilder;\nimport org.joda.time.DateTime;\n\npublic class ExtendedVersionInput extends VersionInput {\n    private final DateTime startDate;\n\n    public ExtendedVersionInput(\n            String projectKey,\n            String name,\n            @Nullable String description,\n            @Nullable DateTime startDate,\n            @Nullable DateTime releaseDate,\n            boolean isArchived,\n            boolean isReleased) {\n        super(projectKey, name, description, releaseDate, isArchived, isReleased);\n        this.startDate = startDate;\n    }\n\n    @Override\n    public String toString() {\n        return new ToStringBuilder(this)\n                .append(\"parent\", super.toString())\n                .append(\"startDate\", startDate)\n                .toString();\n    }\n\n    @Override\n    public boolean equals(Object obj) {\n        if (obj instanceof ExtendedVersionInput) {\n            ExtendedVersionInput that = (ExtendedVersionInput) obj;\n            return Objects.equals(this.startDate, that.startDate);\n        }\n        return super.equals(obj);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(startDate, super.hashCode());\n    }\n\n    DateTime getStartDate() {\n        return startDate;\n    }\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/extension/ExtendedVersionInputJsonGenerator.java",
    "content": "package hudson.plugins.jira.extension;\n\nimport com.atlassian.jira.rest.client.internal.json.JsonParseUtil;\nimport com.atlassian.jira.rest.client.internal.json.gen.JsonGenerator;\nimport org.codehaus.jettison.json.JSONException;\nimport org.codehaus.jettison.json.JSONObject;\n\npublic class ExtendedVersionInputJsonGenerator implements JsonGenerator<ExtendedVersionInput> {\n    @Override\n    public JSONObject generate(ExtendedVersionInput version) throws JSONException {\n        final JSONObject jsonObject = new JSONObject();\n        jsonObject.put(\"name\", version.getName());\n        jsonObject.put(\"project\", version.getProjectKey());\n\n        if (version.getDescription() != null) {\n            jsonObject.put(\"description\", version.getDescription());\n        }\n\n        if (version.getStartDate() != null) {\n            jsonObject.put(\"startDate\", JsonParseUtil.formatDate(version.getStartDate()));\n        }\n\n        if (version.getReleaseDate() != null) {\n            jsonObject.put(\"releaseDate\", JsonParseUtil.formatDate(version.getReleaseDate()));\n        }\n\n        jsonObject.put(\"released\", version.isReleased());\n        jsonObject.put(\"archived\", version.isArchived());\n        return jsonObject;\n    }\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/extension/ExtendedVersionJsonParser.java",
    "content": "package hudson.plugins.jira.extension;\n\nimport com.atlassian.jira.rest.client.internal.json.JsonObjectParser;\nimport com.atlassian.jira.rest.client.internal.json.JsonParseUtil;\nimport java.net.URI;\nimport org.codehaus.jettison.json.JSONException;\nimport org.codehaus.jettison.json.JSONObject;\nimport org.joda.time.DateTime;\n\npublic class ExtendedVersionJsonParser implements JsonObjectParser<ExtendedVersion> {\n    @Override\n    public ExtendedVersion parse(JSONObject json) throws JSONException {\n        final URI self = JsonParseUtil.getSelfUri(json);\n        final Long id = JsonParseUtil.getOptionalLong(json, \"id\");\n        final String name = json.getString(\"name\");\n        final String description = JsonParseUtil.getOptionalString(json, \"description\");\n        final boolean isArchived = json.getBoolean(\"archived\");\n        final boolean isReleased = json.getBoolean(\"released\");\n        final String startDateStr = JsonParseUtil.getOptionalString(json, \"startDate\");\n        final String releaseDateStr = JsonParseUtil.getOptionalString(json, \"releaseDate\");\n        final DateTime startDate = parseDate(startDateStr);\n        final DateTime releaseDate = parseDate(releaseDateStr);\n        return new ExtendedVersion(self, id, name, description, isArchived, isReleased, startDate, releaseDate);\n    }\n\n    private DateTime parseDate(String dateStr) {\n        if (dateStr != null) {\n            return dateStr.length() > \"YYYY-MM-RR\".length()\n                    ? JsonParseUtil.parseDateTime(dateStr)\n                    : JsonParseUtil.parseDate(dateStr);\n        } else {\n            return null;\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/extension/ExtendedVersionRestClient.java",
    "content": "package hudson.plugins.jira.extension;\n\nimport com.atlassian.jira.rest.client.api.VersionRestClient;\nimport io.atlassian.util.concurrent.Promise;\nimport java.net.URI;\n\npublic interface ExtendedVersionRestClient extends VersionRestClient {\n    Promise<ExtendedVersion> getExtendedVersion(URI versionUri);\n\n    Promise<ExtendedVersion> createExtendedVersion(ExtendedVersionInput versionInput);\n\n    Promise<ExtendedVersion> updateExtendedVersion(URI versionUri, ExtendedVersionInput versionInput);\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/listissuesparameter/JiraIssueParameterDefinition.java",
    "content": "/*\n * Copyright 2011-2012 Insider Guides, Inc., MeetMe, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage hudson.plugins.jira.listissuesparameter;\n\nimport static hudson.Util.fixNull;\n\nimport com.atlassian.jira.rest.client.api.RestClientException;\nimport com.atlassian.jira.rest.client.api.domain.Issue;\nimport com.atlassian.jira.rest.client.api.domain.IssueField;\nimport hudson.Extension;\nimport hudson.cli.CLICommand;\nimport hudson.model.Item;\nimport hudson.model.Job;\nimport hudson.model.ParameterDefinition;\nimport hudson.model.ParameterValue;\nimport hudson.model.ParametersDefinitionProperty;\nimport hudson.plugins.jira.JiraSession;\nimport hudson.plugins.jira.JiraSite;\nimport hudson.plugins.jira.Messages;\nimport hudson.util.ListBoxModel;\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.TimeoutException;\nimport net.sf.json.JSONObject;\nimport org.apache.commons.lang3.StringUtils;\nimport org.jenkinsci.Symbol;\nimport org.kohsuke.stapler.AncestorInPath;\nimport org.kohsuke.stapler.DataBoundConstructor;\nimport org.kohsuke.stapler.DataBoundSetter;\nimport org.kohsuke.stapler.QueryParameter;\nimport org.kohsuke.stapler.Stapler;\nimport org.kohsuke.stapler.StaplerRequest2;\nimport org.kohsuke.stapler.interceptor.RequirePOST;\n\npublic class JiraIssueParameterDefinition extends ParameterDefinition {\n    private static final long serialVersionUID = 3927562542249244416L;\n\n    private String jiraIssueFilter;\n    private String altSummaryFields;\n\n    @DataBoundConstructor\n    public JiraIssueParameterDefinition(String name, String description, String jiraIssueFilter) {\n        super(name, description);\n        this.jiraIssueFilter = jiraIssueFilter;\n    }\n\n    @Override\n    public ParameterValue createValue(StaplerRequest2 req) {\n        String[] values = req.getParameterValues(getName());\n        if (values == null || values.length != 1 || values[0].isEmpty()) {\n            return null;\n        }\n\n        return new JiraIssueParameterValue(getName(), values[0]);\n    }\n\n    @Override\n    public ParameterValue createValue(StaplerRequest2 req, JSONObject formData) {\n        JiraIssueParameterValue value = req.bindJSON(JiraIssueParameterValue.class, formData);\n        return value;\n    }\n\n    @Override\n    public ParameterValue createValue(CLICommand command, String value) throws IOException, InterruptedException {\n        return new JiraIssueParameterValue(getName(), value);\n    }\n\n    public List<JiraIssueParameterDefinition.Result> getIssues()\n            throws IOException, TimeoutException, RestClientException {\n        Job<?, ?> job = Stapler.getCurrentRequest2().findAncestorObject(Job.class);\n        return getIssues(job);\n    }\n\n    List<JiraIssueParameterDefinition.Result> getIssues(Job<?, ?> job) {\n        JiraSite site = JiraSite.get(job);\n        if (site == null) {\n            throw new IllegalStateException(\n                    \"Jira site needs to be configured in the project \" + job.getFullDisplayName());\n        }\n\n        JiraSession session = site.getSession(job);\n        if (session == null) {\n            throw new IllegalStateException(\"Remote access for Jira isn't configured in Jenkins\");\n        }\n\n        List<Issue> issues = session.getIssuesFromJqlSearch(jiraIssueFilter);\n\n        List<Result> issueValues = new ArrayList<>();\n\n        for (Issue issue : fixNull(issues)) {\n            issueValues.add(new Result(issue, this.altSummaryFields));\n        }\n\n        return issueValues;\n    }\n\n    public String getJiraIssueFilter() {\n        return jiraIssueFilter;\n    }\n\n    public void setJiraIssueFilter(String jiraIssueFilter) {\n        this.jiraIssueFilter = jiraIssueFilter;\n    }\n\n    public String getAltSummaryFields() {\n        return altSummaryFields;\n    }\n\n    @DataBoundSetter\n    public void setAltSummaryFields(String altSummaryFields) {\n        this.altSummaryFields = altSummaryFields;\n    }\n\n    @Extension\n    @Symbol(\"jiraIssue\")\n    public static class DescriptorImpl extends ParameterDescriptor {\n        @Override\n        public String getDisplayName() {\n            return \"Jira Issue Parameter\";\n        }\n\n        @RequirePOST\n        public ListBoxModel doFillValueItems(@AncestorInPath Job<?, ?> job, @QueryParameter String name) {\n            ListBoxModel items = new ListBoxModel();\n            if (job.hasPermission(Item.BUILD)) {\n                ParametersDefinitionProperty prop = job.getProperty(ParametersDefinitionProperty.class);\n                if (prop != null) {\n                    ParameterDefinition def = prop.getParameterDefinition(name);\n                    if (def instanceof JiraIssueParameterDefinition jiraIssueDef) {\n                        List<Result> issueValues = jiraIssueDef.getIssues(job);\n                        issueValues.forEach(it -> items.add(it.key + \": \" + it.summary, it.key));\n                    }\n                }\n            }\n            if (items.isEmpty()) {\n                items.add(Messages.JiraIssueParameterDefinition_NoIssueMatchedSearch(), \"\");\n            }\n            return items;\n        }\n    }\n\n    public static class Result {\n        public final String key;\n        public final String summary;\n\n        public Result(final Issue issue) {\n            this(issue, null);\n        }\n\n        public Result(final Issue issue, String altSummaryFields) {\n            this.key = issue.getKey();\n            if (StringUtils.isEmpty(altSummaryFields)) {\n                this.summary = issue.getSummary();\n            } else {\n                String[] fields = altSummaryFields.split(\",\");\n                StringBuilder sb = new StringBuilder();\n                for (String f : fields) {\n                    String fn = f.trim();\n                    if (StringUtils.isNotEmpty(fn)) {\n                        IssueField field = issue.getFieldByName(fn);\n                        if (field != null && field.getValue() != null) {\n                            String fv = field.getValue().toString();\n                            if (StringUtils.isNotEmpty(fv)) {\n                                sb.append(fv);\n                                sb.append(' ');\n                            }\n                        }\n                    }\n                }\n                this.summary = sb.toString().trim();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/listissuesparameter/JiraIssueParameterValue.java",
    "content": "/*\n * Copyright 2011-2012 Insider Guides, Inc., MeetMe, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage hudson.plugins.jira.listissuesparameter;\n\nimport hudson.EnvVars;\nimport hudson.model.AbstractBuild;\nimport hudson.model.ParameterValue;\nimport hudson.model.Run;\nimport hudson.util.VariableResolver;\nimport org.kohsuke.stapler.DataBoundConstructor;\nimport org.kohsuke.stapler.export.Exported;\n\npublic class JiraIssueParameterValue extends ParameterValue {\n    private static final long serialVersionUID = -1078274709338167211L;\n\n    private String value;\n\n    @DataBoundConstructor\n    public JiraIssueParameterValue(final String name, final String value) {\n        super(name);\n        this.value = value;\n    }\n\n    @Override\n    public void buildEnvironment(final Run<?, ?> run, final EnvVars env) {\n        Object paramValue = getValue();\n        env.put(getName(), paramValue != null ? paramValue.toString() : \"\");\n    }\n\n    @Override\n    public VariableResolver<String> createVariableResolver(final AbstractBuild<?, ?> build) {\n        return new VariableResolver<String>() {\n            @Override\n            public String resolve(final String name) {\n                if (JiraIssueParameterValue.this.name.equals(name)) {\n                    Object paramValue = getValue();\n                    return paramValue != null ? paramValue.toString() : \"\";\n                }\n                return null;\n            }\n        };\n    }\n\n    public void setValue(final String value) {\n        this.value = value;\n    }\n\n    @Override\n    @Exported\n    public Object getValue() {\n        return value;\n    }\n\n    @Override\n    public String toString() {\n        return \"(JiraIssueParameterValue) \" + getName() + \"='\" + value + \"'\";\n    }\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/model/JiraIssue.java",
    "content": "package hudson.plugins.jira.model;\n\nimport com.atlassian.jira.rest.client.api.domain.Issue;\nimport edu.umd.cs.findbugs.annotations.NonNull;\nimport hudson.plugins.jira.JiraSite;\nimport org.kohsuke.stapler.export.Exported;\nimport org.kohsuke.stapler.export.ExportedBean;\n\n/**\n * One Jira issue.\n * This class is used to persist crucial issue information\n * so that Jenkins can display it without talking to Jira.\n *\n * @author Kohsuke Kawaguchi\n * @see JiraSite#getUrl(JiraIssue)\n */\n@ExportedBean\npublic final class JiraIssue implements Comparable<JiraIssue> {\n\n    /** Note these fields have not been renamed for backward compatibility purposes */\n    private final String id;\n\n    private final String title;\n\n    public JiraIssue(String key, String summary) {\n        this.id = key;\n        this.title = summary;\n    }\n\n    /**\n     * @return Jira ID, like \"MNG-1235\".\n     */\n    @Exported\n    public String getKey() {\n        return id;\n    }\n\n    /**\n     * @return Summary of the issue. For example, in case of MNG-1235, this is \"NPE In DiagnosisUtils while using tomcat plugin\"\n     */\n    @Exported\n    public String getSummary() {\n        return title;\n    }\n\n    public JiraIssue(Issue issue) {\n        this(issue.getKey(), issue.getSummary());\n    }\n\n    @Override\n    public int compareTo(@NonNull JiraIssue that) {\n        return this.id.compareTo(that.id);\n    }\n\n    @Override\n    public int hashCode() {\n        final int prime = 31;\n        int result = 1;\n        result = prime * result + ((id == null) ? 0 : id.hashCode());\n        return result;\n    }\n\n    @Override\n    public boolean equals(Object obj) {\n        if (this == obj) {\n            return true;\n        }\n        if (obj == null) {\n            return false;\n        }\n        if (getClass() != obj.getClass()) {\n            return false;\n        }\n        JiraIssue other = (JiraIssue) obj;\n        if (id == null) {\n            if (other.id != null) {\n                return false;\n            }\n        } else if (!id.equals(other.id)) {\n            return false;\n        }\n        return true;\n    }\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/model/JiraIssueField.java",
    "content": "package hudson.plugins.jira.model;\n\npublic class JiraIssueField implements Comparable<JiraIssueField> {\n\n    private final String fieldId;\n    private final Object fieldValue;\n\n    public JiraIssueField(String fieldId, Object fieldValue) {\n        this.fieldId = fieldId;\n        this.fieldValue = fieldValue;\n    }\n\n    @Override\n    public int compareTo(JiraIssueField that) {\n        return this.compareTo(that);\n    }\n\n    @Override\n    public int hashCode() {\n        final int prime = 31;\n        int result = 1;\n        result = prime * result + ((fieldId == null) ? 0 : fieldId.hashCode());\n        result = prime * result + ((fieldValue == null) ? 0 : fieldValue.hashCode());\n        return result;\n    }\n\n    @Override\n    public boolean equals(Object obj) {\n        if (this == obj) {\n            return true;\n        }\n        if (obj == null) {\n            return false;\n        }\n        if (getClass() != obj.getClass()) {\n            return false;\n        }\n\n        JiraIssueField other = (JiraIssueField) obj;\n        if (fieldId == null) {\n            if (other.fieldId != null) {\n                return false;\n            }\n        } else if (!fieldId.equals(other.fieldId)) {\n            return false;\n        }\n\n        if (fieldValue == null) {\n            if (other.fieldValue != null) {\n                return false;\n            }\n        } else if (!fieldValue.equals(other.fieldValue)) {\n            return false;\n        }\n\n        return true;\n    }\n\n    public String getId() {\n        return fieldId;\n    }\n\n    public Object getValue() {\n        return fieldValue;\n    }\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/model/JiraVersion.java",
    "content": "package hudson.plugins.jira.model;\n\nimport com.atlassian.jira.rest.client.api.domain.Version;\nimport hudson.plugins.jira.extension.ExtendedVersion;\nimport java.util.Calendar;\n\npublic class JiraVersion implements Comparable<JiraVersion> {\n\n    private final String name;\n    private String description;\n    private Calendar startDate;\n    private final Calendar releaseDate;\n    private final boolean released;\n    private final boolean archived;\n\n    public JiraVersion(String name, Calendar releaseDate, boolean released, boolean archived) {\n        this.name = name;\n        this.releaseDate = releaseDate;\n        this.released = released;\n        this.archived = archived;\n    }\n\n    @Deprecated\n    public JiraVersion(String name, Calendar startDate, Calendar releaseDate, boolean released, boolean archived) {\n        this.name = name;\n        this.startDate = startDate;\n        this.releaseDate = releaseDate;\n        this.released = released;\n        this.archived = archived;\n    }\n\n    public JiraVersion(\n            String name,\n            String description,\n            Calendar startDate,\n            Calendar releaseDate,\n            boolean released,\n            boolean archived) {\n        this.name = name;\n        this.description = description;\n        this.startDate = startDate;\n        this.releaseDate = releaseDate;\n        this.released = released;\n        this.archived = archived;\n    }\n\n    public JiraVersion(Version version) {\n        this(\n                version.getName(),\n                version.getReleaseDate() == null\n                        ? null\n                        : version.getReleaseDate().toGregorianCalendar(),\n                version.isReleased(),\n                version.isArchived());\n    }\n\n    public JiraVersion(ExtendedVersion version) {\n        this(\n                version.getName(),\n                version.getDescription(),\n                version.getStartDate() == null ? null : version.getStartDate().toGregorianCalendar(),\n                version.getReleaseDate() == null\n                        ? null\n                        : version.getReleaseDate().toGregorianCalendar(),\n                version.isReleased(),\n                version.isArchived());\n    }\n\n    @Override\n    public int compareTo(JiraVersion that) {\n        int result = this.releaseDate.compareTo(that.releaseDate);\n        if (result == 0) {\n            return this.name.compareTo(that.name);\n        }\n        return result;\n    }\n\n    @Override\n    public int hashCode() {\n        final int prime = 31;\n        int result = 1;\n        result = prime * result + (archived ? 1231 : 1237);\n        result = prime * result + ((name == null) ? 0 : name.hashCode());\n        result = prime * result + ((releaseDate == null) ? 0 : releaseDate.hashCode());\n        result = prime * result + (released ? 1231 : 1237);\n        return result;\n    }\n\n    @Override\n    public boolean equals(Object obj) {\n        if (this == obj) {\n            return true;\n        }\n        if (obj == null) {\n            return false;\n        }\n        if (getClass() != obj.getClass()) {\n            return false;\n        }\n        JiraVersion other = (JiraVersion) obj;\n        if (archived != other.archived) {\n            return false;\n        }\n        if (name == null) {\n            if (other.name != null) {\n                return false;\n            }\n        } else if (!name.equals(other.name)) {\n            return false;\n        }\n        if (startDate == null) {\n            if (other.startDate != null) {\n                return false;\n            }\n        } else if (!startDate.equals(other.startDate)) {\n            return false;\n        }\n        if (releaseDate == null) {\n            if (other.releaseDate != null) {\n                return false;\n            }\n        } else if (!releaseDate.equals(other.releaseDate)) {\n            return false;\n        }\n        if (released != other.released) {\n            return false;\n        }\n        return true;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public String getDescription() {\n        return description;\n    }\n\n    public Calendar getStartDate() {\n        return startDate;\n    }\n\n    public Calendar getReleaseDate() {\n        return releaseDate;\n    }\n\n    public boolean isReleased() {\n        return released;\n    }\n\n    public boolean isArchived() {\n        return archived;\n    }\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/pipeline/CommentStep.java",
    "content": "package hudson.plugins.jira.pipeline;\n\nimport com.atlassian.jira.rest.client.api.RestClientException;\nimport edu.umd.cs.findbugs.annotations.NonNull;\nimport hudson.Extension;\nimport hudson.model.Run;\nimport hudson.model.TaskListener;\nimport hudson.plugins.jira.JiraSession;\nimport hudson.plugins.jira.JiraSite;\nimport hudson.plugins.jira.Messages;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.Set;\nimport org.jenkinsci.plugins.workflow.steps.Step;\nimport org.jenkinsci.plugins.workflow.steps.StepContext;\nimport org.jenkinsci.plugins.workflow.steps.StepDescriptor;\nimport org.jenkinsci.plugins.workflow.steps.StepExecution;\nimport org.jenkinsci.plugins.workflow.steps.SynchronousNonBlockingStepExecution;\nimport org.kohsuke.stapler.DataBoundConstructor;\n\n/**\n * Simple add comment step.\n *\n * @author jan zajic\n */\npublic class CommentStep extends Step {\n\n    public final String issueKey;\n\n    public final String body;\n\n    @DataBoundConstructor\n    public CommentStep(@NonNull String issueKey, @NonNull String body) {\n        this.issueKey = issueKey;\n        this.body = body;\n    }\n\n    public String getIssueKey() {\n        return issueKey;\n    }\n\n    public String getBody() {\n        return body;\n    }\n\n    @Override\n    public StepExecution start(StepContext context) throws Exception {\n        return new CommentStepExecution(this, context);\n    }\n\n    @Extension(optional = true)\n    public static final class DescriptorImpl extends StepDescriptor {\n\n        @Override\n        public Set<? extends Class<?>> getRequiredContext() {\n            Set<Class<?>> context = new HashSet<>();\n            Collections.addAll(context, Run.class, TaskListener.class);\n            return Collections.unmodifiableSet(context);\n        }\n\n        @Override\n        public String getFunctionName() {\n            return \"jiraComment\";\n        }\n\n        @Override\n        public String getDisplayName() {\n            return Messages.CommentStep_Descriptor_DisplayName();\n        }\n    }\n\n    /**\n     * @author jan zajic\n     */\n    public static class CommentStepExecution extends SynchronousNonBlockingStepExecution<Void> {\n\n        private static final long serialVersionUID = 1L;\n\n        private final transient CommentStep step;\n\n        protected CommentStepExecution(CommentStep step, @NonNull StepContext context) {\n            super(context);\n            this.step = step;\n        }\n\n        @Override\n        protected Void run() throws Exception {\n            JiraSite site = JiraSite.get(getContext().get(Run.class).getParent());\n            if (site == null) {\n                return null;\n            }\n            JiraSession session = site.getSession(getContext().get(Run.class).getParent());\n            if (session == null) {\n                getContext().get(TaskListener.class).getLogger().println(Messages.FailedToConnect());\n                return null;\n            }\n\n            try {\n                session.addComment(step.issueKey, step.body, site.groupVisibility, site.roleVisibility);\n            } catch (RestClientException e) {\n                getContext().get(TaskListener.class).getLogger().println(e.getMessage());\n            }\n            return null;\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/pipeline/IssueFieldUpdateStep.java",
    "content": "package hudson.plugins.jira.pipeline;\n\nimport com.atlassian.jira.rest.client.api.RestClientException;\nimport hudson.EnvVars;\nimport hudson.Extension;\nimport hudson.Util;\nimport hudson.model.AbstractProject;\nimport hudson.model.Result;\nimport hudson.model.Run;\nimport hudson.model.TaskListener;\nimport hudson.plugins.jira.EnvironmentExpander;\nimport hudson.plugins.jira.JiraSession;\nimport hudson.plugins.jira.JiraSite;\nimport hudson.plugins.jira.Messages;\nimport hudson.plugins.jira.model.JiraIssueField;\nimport hudson.plugins.jira.selector.AbstractIssueSelector;\nimport hudson.tasks.BuildStepDescriptor;\nimport hudson.tasks.Builder;\nimport hudson.util.FormValidation;\nimport jakarta.servlet.ServletException;\nimport java.io.IOException;\nimport java.io.PrintStream;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Set;\nimport jenkins.tasks.SimpleBuildStep;\nimport org.jenkinsci.Symbol;\nimport org.kohsuke.stapler.DataBoundConstructor;\nimport org.kohsuke.stapler.DataBoundSetter;\nimport org.kohsuke.stapler.QueryParameter;\n\n/**\n * Issue custom fields updater\n *\n * @author Dmitry Frolov tekillaz.dev@gmail.com\n *\n */\npublic class IssueFieldUpdateStep extends Builder implements SimpleBuildStep {\n\n    private AbstractIssueSelector issueSelector;\n\n    public AbstractIssueSelector getIssueSelector() {\n        return this.issueSelector;\n    }\n\n    @DataBoundSetter\n    public void setIssueSelector(AbstractIssueSelector issueSelector) {\n        this.issueSelector = issueSelector;\n    }\n\n    public String fieldId;\n\n    public String getFieldId() {\n        return this.fieldId;\n    }\n\n    @DataBoundSetter\n    public void setFieldId(String fieldId) {\n        this.fieldId = fieldId;\n    }\n\n    public String fieldValue;\n\n    public String getFieldValue() {\n        return this.fieldValue;\n    }\n\n    @DataBoundSetter\n    public void setFieldValue(String fieldValue) {\n        this.fieldValue = fieldValue;\n    }\n\n    @DataBoundConstructor\n    public IssueFieldUpdateStep(AbstractIssueSelector issueSelector, String fieldId, String fieldValue) {\n        this.issueSelector = issueSelector;\n        this.fieldId = fieldId;\n        this.fieldValue = fieldValue;\n    }\n\n    public String prepareFieldId(String fieldId) {\n        String prepared = fieldId;\n        if (!prepared.startsWith(\"customfield_\")) {\n            prepared = \"customfield_\" + prepared;\n        }\n        return prepared;\n    }\n\n    @Override\n    public void perform(Run<?, ?> run, EnvVars env, TaskListener listener) throws IOException {\n\n        PrintStream logger = listener.getLogger();\n\n        AbstractIssueSelector selector = issueSelector;\n        if (selector == null) {\n            logger.println(\"[Jira][IssueFieldUpdateStep] No issue selector found!\");\n            throw new IOException(\"[Jira][IssueFieldUpdateStep] No issue selector found!\");\n        }\n\n        JiraSite site = JiraSite.get(run.getParent());\n        if (site == null) {\n            logger.println(Messages.NoJiraSite());\n            run.setResult(Result.FAILURE);\n            return;\n        }\n\n        JiraSession session = site.getSession(run.getParent());\n        if (session == null) {\n            logger.println(Messages.NoRemoteAccess());\n            run.setResult(Result.FAILURE);\n            return;\n        }\n\n        Set<String> issues;\n        try {\n            issues = selector.findIssueIds(run, site, listener);\n            if (issues.isEmpty()) {\n                logger.println(\"[Jira][IssueFieldUpdateStep] Issue list is empty!\");\n                return;\n            }\n        } catch (RestClientException e) {\n            logger.println(e.getMessage());\n            return;\n        }\n\n        List<JiraIssueField> fields = Collections.singletonList(new JiraIssueField(\n                prepareFieldId(getFieldId()), EnvironmentExpander.expandVariable(getFieldValue(), env)));\n\n        try {\n            for (String issue : issues) {\n                submitFields(session, issue, fields, logger);\n            }\n        } catch (RestClientException e) {\n            logger.println(e.getMessage());\n        }\n    }\n\n    @Override\n    public boolean requiresWorkspace() {\n        return false;\n    }\n\n    /**\n     * @deprecated no reason for this to be exposed/public, use perform(...) instead\n     */\n    @Deprecated\n    public void submitFields(JiraSession session, String issueId, List<JiraIssueField> fields, PrintStream logger) {\n        try {\n            session.addFields(issueId, fields);\n        } catch (RestClientException e) {\n\n            if (e.getStatusCode().or(0).equals(404)) {\n                logger.println(\"[Jira] \" + issueId + \" - Jira issue not found\");\n            }\n\n            if (e.getStatusCode().or(0).equals(403)) {\n                logger.println(\"[Jira] \" + issueId\n                        + \" - Jenkins Jira user does not have permissions to comment on this issue\");\n            }\n\n            if (e.getStatusCode().or(0).equals(401)) {\n                logger.println(\"[Jira] \" + issueId + \" - Jenkins Jira authentication problem\");\n            }\n\n            logger.println(Messages.FailedToUpdateIssue(issueId));\n            logger.println(e.getLocalizedMessage());\n        }\n    }\n\n    @Override\n    public DescriptorImpl getDescriptor() {\n        return (DescriptorImpl) super.getDescriptor();\n    }\n\n    @Extension\n    @Symbol(\"jiraUpdateIssueField\")\n    public static class DescriptorImpl extends BuildStepDescriptor<Builder> {\n\n        public FormValidation doCheckField_id(@QueryParameter String value) throws IOException, ServletException {\n            if (Util.fixNull(value).trim().length() == 0) {\n                return FormValidation.warning(Messages.JiraIssueFieldUpdater_NoIssueFieldID());\n            }\n            if (!value.matches(\"\\\\d+\")) {\n                return FormValidation.error(Messages.JiraIssueFieldUpdater_NotAtIssueFieldID());\n            }\n            return FormValidation.ok();\n        }\n\n        @Override\n        public boolean isApplicable(Class<? extends AbstractProject> jobType) {\n            return true;\n        }\n\n        @Override\n        public String getDisplayName() {\n            return Messages.JiraIssueFieldUpdater_DisplayName();\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/pipeline/IssueSelectorStep.java",
    "content": "package hudson.plugins.jira.pipeline;\n\nimport com.atlassian.jira.rest.client.api.RestClientException;\nimport edu.umd.cs.findbugs.annotations.NonNull;\nimport hudson.Extension;\nimport hudson.model.Descriptor;\nimport hudson.model.Result;\nimport hudson.model.Run;\nimport hudson.model.TaskListener;\nimport hudson.plugins.jira.JiraSite;\nimport hudson.plugins.jira.Messages;\nimport hudson.plugins.jira.selector.AbstractIssueSelector;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.Optional;\nimport java.util.Set;\nimport jenkins.model.Jenkins;\nimport org.jenkinsci.plugins.workflow.steps.Step;\nimport org.jenkinsci.plugins.workflow.steps.StepContext;\nimport org.jenkinsci.plugins.workflow.steps.StepDescriptor;\nimport org.jenkinsci.plugins.workflow.steps.StepExecution;\nimport org.jenkinsci.plugins.workflow.steps.SynchronousNonBlockingStepExecution;\nimport org.kohsuke.stapler.DataBoundConstructor;\nimport org.kohsuke.stapler.DataBoundSetter;\n\n/**\n * Step that run selected issue selector.\n *\n * @see hudson.plugins.jira.selector.AbstractIssueSelector\n */\npublic class IssueSelectorStep extends Step {\n\n    private AbstractIssueSelector issueSelector;\n\n    @DataBoundConstructor\n    public IssueSelectorStep() {}\n\n    @DataBoundSetter\n    public void setIssueSelector(AbstractIssueSelector issueSelector) {\n        this.issueSelector = issueSelector;\n    }\n\n    public AbstractIssueSelector getIssueSelector() {\n        return issueSelector;\n    }\n\n    @Override\n    public StepExecution start(StepContext context) throws Exception {\n        return new IssueSelectorStepExecution(this, context);\n    }\n\n    @Extension(optional = true)\n    public static final class DescriptorImpl extends StepDescriptor {\n\n        public Collection<? extends Descriptor<?>> getApplicableDescriptors() {\n            return Jenkins.get().getDescriptorList(AbstractIssueSelector.class);\n        }\n\n        @Override\n        public Set<? extends Class<?>> getRequiredContext() {\n            Set<Class<?>> context = new HashSet<>();\n            Collections.addAll(context, Run.class, TaskListener.class);\n            return Collections.unmodifiableSet(context);\n        }\n\n        @Override\n        public String getFunctionName() {\n            return \"jiraIssueSelector\";\n        }\n\n        @Override\n        public String getDisplayName() {\n            return Messages.IssueSelectorStep_Descriptor_DisplayName();\n        }\n    }\n\n    public static class IssueSelectorStepExecution extends SynchronousNonBlockingStepExecution<Set<String>> {\n\n        private static final long serialVersionUID = 1L;\n\n        private final transient IssueSelectorStep step;\n\n        protected IssueSelectorStepExecution(IssueSelectorStep step, @NonNull StepContext context) {\n            super(context);\n            this.step = step;\n        }\n\n        @Override\n        protected Set<String> run() throws Exception {\n            TaskListener listener = getContext().get(TaskListener.class);\n            Run run = getContext().get(Run.class);\n            try {\n                return Optional.ofNullable(JiraSite.get(run.getParent()))\n                        .map(site -> step.getIssueSelector().findIssueIds(run, site, listener))\n                        .orElseGet(() -> {\n                            listener.getLogger().println(Messages.NoJiraSite());\n                            run.setResult(Result.FAILURE);\n                            return new HashSet<>();\n                        });\n            } catch (RestClientException e) {\n                listener.getLogger().println(e.getMessage());\n                run.setResult(Result.FAILURE);\n                return new HashSet<>();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/pipeline/SearchIssuesStep.java",
    "content": "package hudson.plugins.jira.pipeline;\n\nimport com.atlassian.jira.rest.client.api.RestClientException;\nimport com.atlassian.jira.rest.client.api.domain.Issue;\nimport edu.umd.cs.findbugs.annotations.NonNull;\nimport hudson.AbortException;\nimport hudson.Extension;\nimport hudson.model.Run;\nimport hudson.model.TaskListener;\nimport hudson.plugins.jira.JiraSession;\nimport hudson.plugins.jira.JiraSite;\nimport hudson.plugins.jira.Messages;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\nimport org.jenkinsci.plugins.workflow.steps.Step;\nimport org.jenkinsci.plugins.workflow.steps.StepContext;\nimport org.jenkinsci.plugins.workflow.steps.StepDescriptor;\nimport org.jenkinsci.plugins.workflow.steps.StepExecution;\nimport org.jenkinsci.plugins.workflow.steps.SynchronousNonBlockingStepExecution;\nimport org.kohsuke.stapler.DataBoundConstructor;\n\n/**\n * Simple search issues step\n *\n * @author jan zajic\n */\npublic class SearchIssuesStep extends Step {\n\n    public final String jql;\n\n    @DataBoundConstructor\n    public SearchIssuesStep(@NonNull String jql) {\n        this.jql = jql;\n    }\n\n    public String getJql() {\n        return jql;\n    }\n\n    @Override\n    public StepExecution start(StepContext context) throws Exception {\n        return new SearchStepExecution(this, context);\n    }\n\n    @Extension(optional = true)\n    public static final class DescriptorImpl extends StepDescriptor {\n\n        @Override\n        public Set<? extends Class<?>> getRequiredContext() {\n            Set<Class<?>> context = new HashSet<>();\n            Collections.addAll(context, Run.class, TaskListener.class);\n            return Collections.unmodifiableSet(context);\n        }\n\n        @Override\n        public String getFunctionName() {\n            return \"jiraSearch\";\n        }\n\n        @Override\n        public String getDisplayName() {\n            return Messages.SearchIssuesStep_Descriptor_DisplayName();\n        }\n    }\n\n    /**\n     * @author jan zajic\n     */\n    public static class SearchStepExecution extends SynchronousNonBlockingStepExecution<List<String>> {\n\n        private static final long serialVersionUID = 1L;\n\n        private final transient SearchIssuesStep step;\n\n        protected SearchStepExecution(SearchIssuesStep step, @NonNull StepContext context) {\n            super(context);\n            this.step = step;\n        }\n\n        @Override\n        protected List<String> run() throws Exception {\n            JiraSite site = JiraSite.get(getContext().get(Run.class).getParent());\n            JiraSession session = site.getSession(getContext().get(Run.class).getParent());\n            if (session == null) {\n                getContext().get(TaskListener.class).getLogger().println(Messages.FailedToConnect());\n                throw new AbortException(\"Cannot open Jira session - error occurred\");\n            }\n\n            List<String> resultList = new ArrayList<>();\n            try {\n                List<Issue> issuesFromJqlSearch = session.getIssuesFromJqlSearch(step.jql);\n                for (Issue issue : issuesFromJqlSearch) {\n                    resultList.add(issue.getKey());\n                }\n            } catch (RestClientException e) {\n                getContext().get(TaskListener.class).getLogger().println(e.getMessage());\n            }\n            return resultList;\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/selector/AbstractIssueSelector.java",
    "content": "package hudson.plugins.jira.selector;\n\nimport edu.umd.cs.findbugs.annotations.NonNull;\nimport hudson.ExtensionPoint;\nimport hudson.model.AbstractDescribableImpl;\nimport hudson.model.Run;\nimport hudson.model.TaskListener;\nimport hudson.plugins.jira.JiraSite;\nimport java.util.Set;\n\n/**\n * Strategy of finding issues which should be updated after completed run.\n *\n * @author Franta Mejta\n */\npublic abstract class AbstractIssueSelector extends AbstractDescribableImpl<AbstractIssueSelector>\n        implements ExtensionPoint {\n\n    /**\n     * Finds the strings that match Jira issue ID patterns.\n     *\n     * This method returns all likely candidates and shouldn't check\n     * if such ID actually exists or not.\n     *\n     * @param run The completed run.\n     * @param site Jira site configured for current job.\n     * @param listener Current's run listener.\n     * @return Set of ids of issues which should be updated.\n     */\n    public abstract Set<String> findIssueIds(\n            @NonNull Run<?, ?> run, @NonNull JiraSite site, @NonNull TaskListener listener);\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/selector/DefaultIssueSelector.java",
    "content": "package hudson.plugins.jira.selector;\n\nimport edu.umd.cs.findbugs.annotations.NonNull;\nimport hudson.Extension;\nimport hudson.model.AbstractBuild;\nimport hudson.model.AbstractBuild.DependencyChange;\nimport hudson.model.Descriptor;\nimport hudson.model.ParameterValue;\nimport hudson.model.ParametersAction;\nimport hudson.model.Run;\nimport hudson.model.TaskListener;\nimport hudson.plugins.jira.JiraCarryOverAction;\nimport hudson.plugins.jira.JiraSite;\nimport hudson.plugins.jira.Messages;\nimport hudson.plugins.jira.RunScmChangeExtractor;\nimport hudson.plugins.jira.listissuesparameter.JiraIssueParameterValue;\nimport hudson.scm.ChangeLogSet;\nimport hudson.scm.ChangeLogSet.Entry;\nimport java.util.Collection;\nimport java.util.HashSet;\nimport java.util.LinkedHashSet;\nimport java.util.Set;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\nimport org.apache.commons.lang3.StringUtils;\nimport org.jenkinsci.Symbol;\nimport org.kohsuke.stapler.DataBoundConstructor;\n\npublic class DefaultIssueSelector extends AbstractIssueSelector {\n\n    private static final Logger LOGGER = Logger.getLogger(DefaultIssueSelector.class.getName());\n\n    @DataBoundConstructor\n    public DefaultIssueSelector() {}\n\n    /**\n     * See {@link #addIssuesRecursive(Run, JiraSite, TaskListener, Set)}\n     */\n    @Override\n    public Set<String> findIssueIds(\n            @NonNull final Run<?, ?> run, @NonNull final JiraSite site, @NonNull final TaskListener listener) {\n        HashSet<String> issuesIds = new LinkedHashSet<>();\n        addIssuesRecursive(run, site, listener, issuesIds);\n        return issuesIds;\n    }\n\n    @Extension\n    @Symbol(\"DefaultSelector\")\n    public static final class DescriptorImpl extends Descriptor<AbstractIssueSelector> {\n\n        @Override\n        public String getDisplayName() {\n            return Messages.DefaultIssueSelector_DisplayName();\n        }\n    }\n\n    protected Logger getLogger() {\n        return LOGGER;\n    }\n\n    /**\n     * Finds the strings that match Jira issue ID patterns. This method returns\n     * all likely candidates and doesn't check if such ID actually exists or\n     * not. We don't want to use {@link JiraSite#existsIssue(String)} here so\n     * that new projects in Jira can be detected.\n     *\n     */\n    protected static void findIssues(Run<?, ?> build, Set<String> issueIds, Pattern pattern, TaskListener listener) {\n        for (ChangeLogSet<? extends Entry> set : RunScmChangeExtractor.getChanges(build)) {\n            for (Entry change : set) {\n                LOGGER.fine(\"Looking for Jira ID in \" + change.getMsg());\n                Matcher m = pattern.matcher(change.getMsg());\n\n                while (m.find()) {\n                    if (m.groupCount() >= 1) {\n                        String content = StringUtils.upperCase(m.group(1));\n                        issueIds.add(content);\n                    } else {\n                        listener.getLogger()\n                                .println(\"Warning: The Jira pattern \" + pattern + \" doesn't define a capturing group!\");\n                    }\n                }\n            }\n        }\n    }\n\n    /**\n     * Calls {@link #findIssues(Run, Set, Pattern, TaskListener)} with\n     * {@link JiraSite#getIssuePattern()} as pattern\n     */\n    protected void addIssuesFromChangeLog(Run<?, ?> build, JiraSite site, TaskListener listener, Set<String> issueIds) {\n        Pattern pattern = site.getIssuePattern();\n        findIssues(build, issueIds, pattern, listener);\n    }\n\n    /**\n     * Adds issues to issueIds. Adds issues carried over from previous build,\n     * issues from current build and from dependent builds\n     * {@link #addIssuesCarriedOverFromPreviousBuild(Run, JiraSite, TaskListener, Set)}\n     * {@link #addIssuesFromCurrentBuild(Run, JiraSite, TaskListener, Set)}\n     * {@link #addIssuesFromDependentBuilds(Run, JiraSite, TaskListener, Set)}\n     */\n    protected void addIssuesRecursive(Run<?, ?> build, JiraSite site, TaskListener listener, Set<String> issuesIds) {\n        addIssuesCarriedOverFromPreviousBuild(build, site, listener, issuesIds);\n        addIssuesFromCurrentBuild(build, site, listener, issuesIds);\n        addIssuesFromDependentBuilds(build, site, listener, issuesIds);\n    }\n\n    /**\n     * Adds issues to issueIds from the current build. Issues from parameters\n     * are added as well as issues matching pattern\n     * {@link #addIssuesFromChangeLog(Run, JiraSite, TaskListener, Set)}\n     * {@link #addIssuesFromParameters(Run, JiraSite, TaskListener, Set)}\n     */\n    protected void addIssuesFromCurrentBuild(\n            Run<?, ?> build, JiraSite site, TaskListener listener, Set<String> issueIds) {\n        addIssuesFromChangeLog(build, site, listener, issueIds);\n        addIssuesFromParameters(build, site, listener, issueIds);\n    }\n\n    /**\n     * Adds issues to issueIds by examining dependency changes from last build.\n     * For each dependency change\n     * {@link #addIssuesRecursive(Run, JiraSite, TaskListener, Set)} is called.\n     */\n    protected void addIssuesFromDependentBuilds(\n            Run<?, ?> build, JiraSite site, TaskListener listener, Set<String> issueIds) {\n        Pattern pattern = site.getIssuePattern();\n\n        for (DependencyChange depc :\n                RunScmChangeExtractor.getDependencyChanges(build).values()) {\n            for (AbstractBuild<?, ?> b : depc.getBuilds()) {\n                getLogger().finer(\"Searching for Jira issues in dependency \" + b + \" of \" + build);\n\n                // Fix JENKINS-44989\n                // The original code before refactoring just called \"findIssues\", not \"findIssueIdsRecursive\"\n                findIssues(b, issueIds, pattern, listener);\n            }\n        }\n    }\n\n    /**\n     * Adds issues to issueIds from parameters\n     */\n    protected void addIssuesFromParameters(\n            Run<?, ?> build, JiraSite site, TaskListener listener, Set<String> issueIds) {\n        // Now look for any JiraIssueParameterValue's set in the build\n        // Implements JENKINS-12312\n        ParametersAction parameters = build.getAction(ParametersAction.class);\n\n        if (parameters != null) {\n            for (ParameterValue val : parameters.getParameters()) {\n                if (val instanceof JiraIssueParameterValue) {\n                    String issueId = ((JiraIssueParameterValue) val).getValue().toString();\n                    if (issueIds.add(issueId)) {\n                        getLogger().finer(\"Added perforce issue \" + issueId + \" from build \" + build);\n                    }\n                }\n            }\n        }\n    }\n\n    /**\n     * Adds issues that were carried over from previous build to issueIds\n     */\n    protected void addIssuesCarriedOverFromPreviousBuild(\n            Run<?, ?> build, JiraSite site, TaskListener listener, Set<String> ids) {\n        Run<?, ?> prev = build.getPreviousCompletedBuild();\n        if (prev != null) {\n            JiraCarryOverAction a = prev.getAction(JiraCarryOverAction.class);\n            if (a != null) {\n                getLogger().finer(\"Searching for Jira issues in previously failed build \" + prev.number);\n                Collection<String> jobIDs = a.getIDs();\n                ids.addAll(jobIDs);\n                if (getLogger().isLoggable(Level.FINER)) {\n                    for (String jobId : a.getIDs()) {\n                        getLogger().finer(\"Adding job \" + jobId);\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/selector/ExplicitIssueSelector.java",
    "content": "package hudson.plugins.jira.selector;\n\nimport edu.umd.cs.findbugs.annotations.CheckForNull;\nimport hudson.EnvVars;\nimport hudson.Extension;\nimport hudson.model.Descriptor;\nimport hudson.model.Run;\nimport hudson.model.TaskListener;\nimport hudson.plugins.jira.EnvironmentExpander;\nimport hudson.plugins.jira.JiraSite;\nimport hudson.plugins.jira.Messages;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\nimport org.apache.commons.lang3.StringUtils;\nimport org.jenkinsci.Symbol;\nimport org.kohsuke.stapler.DataBoundConstructor;\n\npublic class ExplicitIssueSelector extends AbstractIssueSelector {\n\n    @CheckForNull\n    private List<String> jiraIssueKeys;\n\n    private String issueKeys;\n\n    @DataBoundConstructor\n    public ExplicitIssueSelector(String issueKeys) {\n        this.jiraIssueKeys =\n                StringUtils.isNotBlank(issueKeys) ? Arrays.asList(issueKeys.split(\",\")) : Collections.emptyList();\n        this.issueKeys = issueKeys;\n    }\n\n    public ExplicitIssueSelector(List<String> jiraIssueKeys) {\n        this.jiraIssueKeys = jiraIssueKeys;\n    }\n\n    public ExplicitIssueSelector() {\n        this.jiraIssueKeys = Collections.emptyList();\n    }\n\n    public void setIssueKeys(String issueKeys) {\n        this.issueKeys = issueKeys;\n        this.jiraIssueKeys =\n                StringUtils.isNotBlank(issueKeys) ? Arrays.asList(issueKeys.split(\",\")) : new ArrayList<>();\n    }\n\n    public String getIssueKeys() {\n        return issueKeys;\n    }\n\n    @Override\n    public Set<String> findIssueIds(Run<?, ?> run, JiraSite site, TaskListener listener) {\n        EnvVars envVars = EnvironmentExpander.getEnvVars(run, listener);\n\n        List<String> issueKeys = new ArrayList<>();\n        for (String issue : jiraIssueKeys) {\n            issueKeys.add(EnvironmentExpander.expandVariable(issue, envVars));\n        }\n\n        return new HashSet(issueKeys);\n    }\n\n    @Extension\n    @Symbol(\"ExplicitSelector\")\n    public static final class DescriptorImpl extends Descriptor<AbstractIssueSelector> {\n        @Override\n        public String getDisplayName() {\n            return Messages.IssueSelector_ExplicitIssueSelector_DisplayName();\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/selector/JqlIssueSelector.java",
    "content": "package hudson.plugins.jira.selector;\n\nimport static hudson.Util.fixNull;\n\nimport com.atlassian.jira.rest.client.api.RestClientException;\nimport com.atlassian.jira.rest.client.api.domain.Issue;\nimport hudson.Extension;\nimport hudson.model.Descriptor;\nimport hudson.model.Run;\nimport hudson.model.TaskListener;\nimport hudson.plugins.jira.EnvironmentExpander;\nimport hudson.plugins.jira.JiraSession;\nimport hudson.plugins.jira.JiraSite;\nimport hudson.plugins.jira.Messages;\nimport java.util.ArrayList;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\nimport org.jenkinsci.Symbol;\nimport org.kohsuke.stapler.DataBoundConstructor;\n\npublic class JqlIssueSelector extends AbstractIssueSelector {\n\n    private String jql;\n\n    @DataBoundConstructor\n    public JqlIssueSelector(String jql) {\n        this.jql = jql;\n    }\n\n    public void setJql(String jql) {\n        this.jql = jql;\n    }\n\n    public String getJql() {\n        return jql;\n    }\n\n    @Override\n    public Set<String> findIssueIds(Run<?, ?> run, JiraSite site, TaskListener listener) {\n        try {\n            JiraSession session = site.getSession(run.getParent());\n            if (session == null) {\n                throw new IllegalStateException(\"Remote access for Jira isn't configured in Jenkins\");\n            }\n\n            String expandedJql = EnvironmentExpander.expandVariable(jql, run, listener);\n\n            List<Issue> issues = session.getIssuesFromJqlSearch(expandedJql);\n\n            List<String> issueKeys = new ArrayList<>();\n\n            for (Issue issue : fixNull(issues)) {\n                issueKeys.add(issue.getKey());\n            }\n\n            // deduplication\n            return new HashSet<>(issueKeys);\n        } catch (RestClientException e) {\n            throw new IllegalStateException(\"Can't open rest session to Jira site \" + site, e);\n        }\n    }\n\n    @Extension\n    @Symbol(\"JqlSelector\")\n    public static final class DescriptorImpl extends Descriptor<AbstractIssueSelector> {\n\n        @Override\n        public String getDisplayName() {\n            return Messages.IssueSelector_JqlIssueSelector_DisplayName();\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/selector/perforce/JobIssueSelector.java",
    "content": "package hudson.plugins.jira.selector.perforce;\n\nimport hudson.model.Run;\nimport hudson.model.TaskListener;\nimport hudson.plugins.jira.JiraSite;\nimport hudson.plugins.jira.selector.DefaultIssueSelector;\nimport java.util.Set;\n\n/**\n * Base class for job selectors. Perforce offers mechanism to associate Jira\n * issues with change lists called jobs. The classes inheriting from this class\n * find issues by examining jobs associated with changes\n *\n * @author Jacek Tomaka\n * @since 2.3\n */\npublic abstract class JobIssueSelector extends DefaultIssueSelector {\n\n    /**\n     * See {@link #addJobIdsFromChangeLog(Run, JiraSite, TaskListener, Set)}\n     */\n    @Override\n    protected void addIssuesFromChangeLog(Run<?, ?> build, JiraSite site, TaskListener listener, Set<String> issueIds) {\n        addJobIdsFromChangeLog(build, site, listener, issueIds);\n    }\n\n    /**\n     * Adds job ids from change log to issueIds.\n     */\n    protected abstract void addJobIdsFromChangeLog(\n            Run<?, ?> build, JiraSite site, TaskListener listener, Set<String> issueIds);\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/selector/perforce/P4JobIssueSelector.java",
    "content": "package hudson.plugins.jira.selector.perforce;\n\nimport com.perforce.p4java.core.IFix;\nimport hudson.Extension;\nimport hudson.model.Descriptor;\nimport hudson.model.Run;\nimport hudson.model.TaskListener;\nimport hudson.plugins.jira.JiraSite;\nimport hudson.plugins.jira.Messages;\nimport hudson.plugins.jira.RunScmChangeExtractor;\nimport hudson.plugins.jira.selector.AbstractIssueSelector;\nimport hudson.scm.ChangeLogSet;\nimport hudson.scm.ChangeLogSet.Entry;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.logging.Logger;\nimport org.jenkinsci.Symbol;\nimport org.jenkinsci.plugins.p4.changes.P4ChangeEntry;\nimport org.kohsuke.stapler.DataBoundConstructor;\n\n/**\n * Job selector for Perforce Software SCM plugin (P4)\n *\n * @author Jacek Tomaka\n * @since 2.3\n */\npublic class P4JobIssueSelector extends JobIssueSelector {\n    private static final Logger LOGGER = Logger.getLogger(P4JobIssueSelector.class.getName());\n\n    @Extension(optional = true)\n    @Symbol(\"P4Selector\")\n    public static final class DescriptorImpl extends Descriptor<AbstractIssueSelector> {\n\n        @Override\n        public String getDisplayName() {\n            return Messages.P4JobIssueSelector_DisplayName();\n        }\n    }\n\n    @DataBoundConstructor\n    public P4JobIssueSelector() {}\n\n    @Override\n    protected void addJobIdsFromChangeLog(Run<?, ?> build, JiraSite site, TaskListener listener, Set<String> issueIds) {\n        getLogger().finer(\"Searching for Jira issues in Perforce jobs in \" + build);\n        for (ChangeLogSet<? extends Entry> set : RunScmChangeExtractor.getChanges(build)) {\n            for (Entry change : set) {\n                getLogger().fine(\"Looking for Jira IDs as Perforce Jobs in \" + change.getMsg());\n                if (P4ChangeEntry.class.isAssignableFrom(change.getClass())) {\n                    P4ChangeEntry p4ChangeEntry = (P4ChangeEntry) change;\n\n                    List<IFix> jobs = p4ChangeEntry.getJobs();\n                    if (jobs != null) {\n                        for (IFix job : jobs) {\n                            String jobId = job.getJobId();\n                            if (issueIds.add(jobId)) {\n                                getLogger().finer(\"Added Perforce job id \" + jobId + \" from build \" + build);\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    @Override\n    protected Logger getLogger() {\n        return LOGGER;\n    }\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/versionparameter/JiraVersionParameterDefinition.java",
    "content": "package hudson.plugins.jira.versionparameter;\n\nimport com.atlassian.jira.rest.client.api.RestClientException;\nimport com.atlassian.jira.rest.client.api.domain.Version;\nimport hudson.Extension;\nimport hudson.cli.CLICommand;\nimport hudson.model.Item;\nimport hudson.model.Job;\nimport hudson.model.ParameterDefinition;\nimport hudson.model.ParameterValue;\nimport hudson.model.ParametersDefinitionProperty;\nimport hudson.plugins.jira.JiraSession;\nimport hudson.plugins.jira.JiraSite;\nimport hudson.plugins.jira.Messages;\nimport hudson.util.ListBoxModel;\nimport java.io.IOException;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.regex.Pattern;\nimport java.util.stream.Collectors;\nimport net.sf.json.JSONObject;\nimport org.jenkinsci.Symbol;\nimport org.kohsuke.stapler.AncestorInPath;\nimport org.kohsuke.stapler.DataBoundConstructor;\nimport org.kohsuke.stapler.QueryParameter;\nimport org.kohsuke.stapler.Stapler;\nimport org.kohsuke.stapler.StaplerRequest2;\nimport org.kohsuke.stapler.interceptor.RequirePOST;\n\npublic class JiraVersionParameterDefinition extends ParameterDefinition {\n    private static final long serialVersionUID = 4232979892748310160L;\n\n    private String projectKey;\n    private boolean showReleased = false;\n    private boolean showArchived = false;\n    private boolean showUnreleased = false;\n    private Pattern pattern = null;\n\n    @DataBoundConstructor\n    public JiraVersionParameterDefinition(\n            String name,\n            String description,\n            String jiraProjectKey,\n            String jiraReleasePattern,\n            String jiraShowReleased,\n            String jiraShowArchived,\n            String jiraShowUnreleased) {\n        super(name);\n        setDescription(description);\n        setJiraProjectKey(jiraProjectKey);\n        setJiraReleasePattern(jiraReleasePattern);\n        setJiraShowReleased(jiraShowReleased);\n        setJiraShowArchived(jiraShowArchived);\n        setShowUnreleased(jiraShowUnreleased);\n    }\n\n    @Override\n    public ParameterValue createValue(StaplerRequest2 req) {\n        String[] values = req.getParameterValues(getName());\n        if (values == null || values.length != 1 || values[0].isEmpty()) {\n            return null;\n        }\n        return new JiraVersionParameterValue(getName(), values[0]);\n    }\n\n    @Override\n    public ParameterValue createValue(StaplerRequest2 req, JSONObject formData) {\n        JiraVersionParameterValue value = req.bindJSON(JiraVersionParameterValue.class, formData);\n        return value;\n    }\n\n    @Override\n    public ParameterValue createValue(CLICommand command, String value) throws IOException, InterruptedException {\n        return new JiraVersionParameterValue(getName(), value);\n    }\n\n    public List<JiraVersionParameterDefinition.Result> getVersions() throws IOException, RestClientException {\n        Job<?, ?> contextJob = Stapler.getCurrentRequest2().findAncestorObject(Job.class);\n        return getVersions(contextJob);\n    }\n\n    List<JiraVersionParameterDefinition.Result> getVersions(Job<?, ?> contextJob) {\n        JiraSite site = JiraSite.get(contextJob);\n        if (site == null) {\n            throw new IllegalStateException(\n                    \"Jira site needs to be configured in the project \" + contextJob.getFullDisplayName());\n        }\n\n        JiraSession session = site.getSession(contextJob);\n        if (session == null) {\n            throw new IllegalStateException(\"Remote access for Jira isn't configured in Jenkins\");\n        }\n\n        return session.getVersions(projectKey).stream()\n                .sorted(VersionComparator.INSTANCE)\n                .filter(this::match)\n                .map(Result::new)\n                .collect(Collectors.toList());\n    }\n\n    private boolean match(Version version) {\n        // Match regex if it exists\n        if (pattern != null) {\n            if (!pattern.matcher(version.getName()).matches()) {\n                return false;\n            }\n        }\n\n        boolean isReleased = version.isReleased();\n        boolean isArchived = version.isArchived();\n        boolean showAllVersions = !showReleased && !showUnreleased && !showArchived;\n\n        if (showAllVersions) {\n            return true;\n        }\n\n        if (showReleased && isReleased && !isArchived) {\n            return true;\n        }\n\n        if (showArchived && isArchived) {\n            return true;\n        }\n\n        if (showUnreleased && !isReleased && !isArchived) {\n            return true;\n        }\n\n        return false;\n    }\n\n    public String getJiraReleasePattern() {\n        if (pattern == null) {\n            return \"\";\n        }\n        return pattern.pattern();\n    }\n\n    public void setJiraReleasePattern(String pattern) {\n        if (pattern == null || pattern.isEmpty()) {\n            this.pattern = null;\n        } else {\n            this.pattern = Pattern.compile(pattern);\n        }\n    }\n\n    public String getJiraProjectKey() {\n        return projectKey;\n    }\n\n    public void setJiraProjectKey(String projectKey) {\n        this.projectKey = projectKey;\n    }\n\n    public String getJiraShowReleased() {\n        return Boolean.toString(showReleased);\n    }\n\n    public void setJiraShowReleased(String showReleased) {\n        this.showReleased = Boolean.parseBoolean(showReleased);\n    }\n\n    public String getJiraShowArchived() {\n        return Boolean.toString(showArchived);\n    }\n\n    public void setJiraShowArchived(String showArchived) {\n        this.showArchived = Boolean.parseBoolean(showArchived);\n    }\n\n    public String getJiraShowUnreleased() {\n        return Boolean.toString(showUnreleased);\n    }\n\n    public void setShowUnreleased(String jiraShowUnreleased) {\n        this.showUnreleased = Boolean.parseBoolean(jiraShowUnreleased);\n    }\n\n    @Extension\n    @Symbol(\"jiraReleaseVersion\")\n    public static class DescriptorImpl extends ParameterDescriptor {\n        @Override\n        public String getDisplayName() {\n            return \"Jira Release Version Parameter\";\n        }\n\n        @RequirePOST\n        public ListBoxModel doFillVersionItems(@AncestorInPath Job<?, ?> job, @QueryParameter String name) {\n            ListBoxModel items = new ListBoxModel();\n            if (job.hasPermission(Item.BUILD)) {\n                ParametersDefinitionProperty prop = job.getProperty(ParametersDefinitionProperty.class);\n                if (prop != null) {\n                    ParameterDefinition def = prop.getParameterDefinition(name);\n                    if (def instanceof JiraVersionParameterDefinition jiraVersionDef) {\n                        List<JiraVersionParameterDefinition.Result> issueValues = jiraVersionDef.getVersions(job);\n                        issueValues.forEach(it -> items.add(it.name));\n                    }\n                }\n            }\n            if (items.isEmpty()) {\n                items.add(Messages.JiraVersionParameterDefinition_NoVersionsMatchedSearch(), \"\");\n            }\n            return items;\n        }\n    }\n\n    public static class Result {\n        public final String name;\n        public final Long id;\n\n        public Result(final Version version) {\n            this.name = version.getName();\n            this.id = version.getId();\n        }\n\n        @Override\n        public boolean equals(Object o) {\n            if (o == null || getClass() != o.getClass()) return false;\n            Result result = (Result) o;\n            return Objects.equals(name, result.name) && Objects.equals(id, result.id);\n        }\n\n        @Override\n        public int hashCode() {\n            return Objects.hash(name, id);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/versionparameter/JiraVersionParameterValue.java",
    "content": "package hudson.plugins.jira.versionparameter;\n\nimport hudson.EnvVars;\nimport hudson.model.AbstractBuild;\nimport hudson.model.ParameterValue;\nimport hudson.model.Run;\nimport hudson.util.VariableResolver;\nimport org.kohsuke.stapler.DataBoundConstructor;\n\npublic class JiraVersionParameterValue extends ParameterValue {\n\n    /**\n     *\n     */\n    private static final long serialVersionUID = 7715888375360839484L;\n\n    private String version;\n\n    @DataBoundConstructor\n    public JiraVersionParameterValue(final String name, final String version) {\n        super(name);\n        if (version == null) {\n            throw new IllegalArgumentException(\"Version cannot be null\");\n        }\n        this.version = version;\n    }\n\n    @Override\n    public void buildEnvironment(final Run<?, ?> run, final EnvVars env) {\n        env.put(getName(), getVersion());\n    }\n\n    @Override\n    public VariableResolver<String> createVariableResolver(final AbstractBuild<?, ?> build) {\n        return name -> JiraVersionParameterValue.this.name.equals(name) ? getVersion() : null;\n    }\n\n    public void setVersion(final String version) {\n        this.version = version;\n    }\n\n    public String getVersion() {\n        return version;\n    }\n\n    @Override\n    public Object getValue() {\n        return getVersion();\n    }\n\n    @Override\n    public String toString() {\n        return \"(JiraVersionParameterValue) \" + getName() + \"='\" + version + \"'\";\n    }\n}\n"
  },
  {
    "path": "src/main/java/hudson/plugins/jira/versionparameter/VersionComparator.java",
    "content": "package hudson.plugins.jira.versionparameter;\n\nimport com.atlassian.jira.rest.client.api.domain.Version;\nimport java.util.Comparator;\nimport org.apache.maven.artifact.versioning.ComparableVersion;\n\n/**\n * This comparator can ordering the following formats versions:\n * 9.9.9.9.9\n * V-5.2.3\n * PDFREPORT-2.3.4\n * PDFREPORT-2.3\n * 1.12.2.3.4\n * 1.3.4\n * 1.1.1.2\n * 1.1.1.1\n */\npublic class VersionComparator implements Comparator<Version> {\n\n    public static final VersionComparator INSTANCE = new VersionComparator();\n\n    @Override\n    public int compare(Version rev1, Version rev2) {\n        ComparableVersion comparableVersion1 = new ComparableVersion(rev1.getName());\n        ComparableVersion comparableVersion2 = new ComparableVersion(rev2.getName());\n        int comparisonResult = comparableVersion2.compareTo(comparableVersion1);\n        if (comparisonResult > 0) {\n            return 1;\n        } else if (comparisonResult < 0) {\n            return -1;\n        } else return 0;\n    }\n}\n"
  },
  {
    "path": "src/main/resources/atlassian-httpclient-plugin-0.23.0.pom",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd\">\n\n    <modelVersion>4.0.0</modelVersion>\n    <parent>\n        <groupId>com.atlassian.httpclient</groupId>\n        <artifactId>atlassian-httpclient-parent</artifactId>\n        <version>0.23.0</version>\n    </parent>\n\n    <artifactId>atlassian-httpclient-plugin</artifactId>\n    <packaging>atlassian-plugin</packaging>\n\n    <name>Atlassian HTTP Client, Apache HTTP components impl</name>\n    <description>Implementation of the HTTP Client API based on Apache HTTP Components</description>\n\n    <licenses>\n        <license>\n            <name>Apache License 2.0</name>\n            <url>http://www.apache.org/licenses/LICENSE-2.0</url>\n            <distribution>repo</distribution>\n        </license>\n    </licenses>\n\n    <properties>\n        <httpclient.api.version>0.19</httpclient.api.version>\n        <http.port>9000</http.port>\n    </properties>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.atlassian.httpclient</groupId>\n            <artifactId>atlassian-httpclient-api</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n\n        <!-- Host provided dependencies -->\n        <dependency>\n            <groupId>com.atlassian.sal</groupId>\n            <artifactId>sal-api</artifactId>\n            <scope>provided</scope>\n            <optional>true</optional>\n        </dependency>\n        <dependency>\n            <groupId>com.atlassian.event</groupId>\n            <artifactId>atlassian-event</artifactId>\n            <scope>provided</scope>\n            <optional>true</optional>\n        </dependency>\n        <dependency>\n            <groupId>com.atlassian.analytics</groupId>\n            <artifactId>analytics-api</artifactId>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.slf4j</groupId>\n            <artifactId>slf4j-api</artifactId>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework</groupId>\n            <artifactId>spring-context</artifactId>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.atlassian.plugins</groupId>\n            <artifactId>atlassian-plugins-osgi</artifactId>\n            <version>${atlassian-plugins.version}</version>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>javax.servlet</groupId>\n            <artifactId>servlet-api</artifactId>\n            <scope>provided</scope>\n        </dependency>\n\n        <dependency>\n            <groupId>com.atlassian.fugue</groupId>\n            <artifactId>fugue</artifactId>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.atlassian.bundles</groupId>\n            <artifactId>jsr305</artifactId>\n            <scope>provided</scope>\n        </dependency>\n\n        <!-- Http client -->\n        <dependency>\n            <groupId>org.apache.httpcomponents</groupId>\n            <artifactId>httpasyncclient-cache</artifactId>\n            <version>${httpasyncclient.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>org.apache.httpcomponents</groupId>\n            <artifactId>httpclient-cache</artifactId>\n            <version>${httpcore.version}</version>\n            <exclusions>\n                <exclusion>\n                    <groupId>commons-logging</groupId>\n                    <artifactId>commons-logging</artifactId>\n                </exclusion>\n            </exclusions>\n        </dependency>\n        <dependency>\n            <groupId>org.apache.httpcomponents</groupId>\n            <artifactId>httpasyncclient</artifactId>\n            <version>${httpasyncclient.version}</version>\n            <exclusions>\n                <exclusion>\n                    <groupId>commons-logging</groupId>\n                    <artifactId>commons-logging</artifactId>\n                </exclusion>\n            </exclusions>\n        </dependency>\n        <dependency>\n            <groupId>org.apache.httpcomponents</groupId>\n            <artifactId>httpmime</artifactId>\n            <version>${httpclient.version}</version>\n            <exclusions>\n                <exclusion>\n                    <groupId>commons-logging</groupId>\n                    <artifactId>commons-logging</artifactId>\n                </exclusion>\n            </exclusions>\n        </dependency>\n\n        <!-- Test dependencies -->\n        <dependency>\n            <groupId>com.atlassian.junit</groupId>\n            <artifactId>atlassian-junit</artifactId>\n            <version>0.1</version>\n            <scope>test</scope>\n            <exclusions>\n                <exclusion>\n                    <groupId>org.eclipse.jetty</groupId>\n                    <artifactId>jetty-server</artifactId>\n                </exclusion>\n                <exclusion>\n                    <groupId>org.eclipse.jetty</groupId>\n                    <artifactId>jetty-servlet</artifactId>\n                </exclusion>\n            </exclusions>\n        </dependency>\n        <dependency>\n            <groupId>org.eclipse.jetty</groupId>\n            <artifactId>jetty-server</artifactId>\n            <version>7.6.8.v20121106</version>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.eclipse.jetty</groupId>\n            <artifactId>jetty-servlet</artifactId>\n            <version>7.6.8.v20121106</version>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.hamcrest</groupId>\n            <artifactId>hamcrest-core</artifactId>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.hamcrest</groupId>\n            <artifactId>hamcrest-library</artifactId>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.github.stefanbirkner</groupId>\n            <artifactId>system-rules</artifactId>\n            <version>1.5.0</version>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.fasterxml.jackson.core</groupId>\n            <artifactId>jackson-annotations</artifactId>\n            <version>2.5.2</version>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.fasterxml.jackson.core</groupId>\n            <artifactId>jackson-core</artifactId>\n            <version>2.5.2</version>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.fasterxml.jackson.core</groupId>\n            <artifactId>jackson-databind</artifactId>\n            <version>2.5.2</version>\n            <scope>test</scope>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>com.atlassian.maven.plugins</groupId>\n                <artifactId>maven-amps-plugin</artifactId>\n                <configuration>\n                    <extractDependencies>true</extractDependencies>\n                    <instructions>\n                        <Import-Package>\n                            com.atlassian.sal.api*;version=\"${sal.version}\",\n                            com.atlassian.util.concurrent*;version=\"${atlassian-util-concurrent-api.version}\",\n                            com.google.common*;version=\"${guava.version}\",\n                            javax.net.ssl,\n                            javax.servlet.*,\n                            org.osgi*,\n                            org.slf4j*;version=\"1.5\",\n                            org.springframework*,\n                            org.xml.sax*,\n                            com.atlassian.fugue*,\n                            *;resolution:=optional\n                        </Import-Package>\n                        <Export-Package>\n                            com.atlassian.httpclient.api*;version=\"${httpclient.api.version}\",\n                            com.atlassian.fugue*\n                        </Export-Package>\n                        <!-- Ensure plugin is spring powered - see https://extranet.atlassian.com/x/xBS9hQ -->\n                        <Spring-Context>*</Spring-Context>\n                    </instructions>\n                    <systemPropertyVariables>\n                        <http.port>${http.port}</http.port>\n                    </systemPropertyVariables>\n                </configuration>\n            </plugin>\n        </plugins>\n    </build>\n</project>\n"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraBuildAction/summary.jelly",
    "content": "<?jelly escape-by-default='true'?>\n<j:jelly xmlns:j=\"jelly:core\" xmlns:st=\"jelly:stapler\" xmlns:d=\"jelly:define\" xmlns:l=\"/lib/layout\" xmlns:t=\"/lib/hudson\" xmlns:f=\"/lib/form\" xmlns:i=\"jelly:fmt\">\n<!--\n  It turns out this seems to be rather redundant, as links in changelogs seem to be enough \n\n  <t:summary icon=\"orange-square.gif\">\n    Jira issues\n    <table>\n      <j:forEach var=\"issue\" items=\"${it.issues}\">\n        <tr>\n          <td><a href=\"${it.getUrl(issue)}\">${issue.key}</a></td>\n          <td>${issue.summary}</td>\n        </tr>\n      </j:forEach>\n    </table>\n  </t:summary>\n-->\n</j:jelly>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraCreateIssueNotifier/config.jelly",
    "content": "<?jelly escape-by-default='true'?>\n<j:jelly xmlns:j=\"jelly:core\" xmlns:f=\"/lib/form\">\n    <f:entry title=\"${%Jira Project Key}\" field=\"projectKey\">\n        <f:textbox/>\n    </f:entry>\n    <f:entry title=\"${%Assignee}\" field=\"assignee\">\n        <f:textbox/>\n    </f:entry>\n    <f:entry title=\"${%Description Of Test}\" field=\"testDescription\">\n        <f:textarea/>\n    </f:entry>\n    <f:entry title=\"${%Component Name}\" field=\"component\">\n        <f:textbox/>\n    </f:entry>\n    <f:entry title=\"${%Issue Priority}\" field=\"priorityId\">\n        <f:select/>\n    </f:entry>\n    <f:entry title=\"${%Issue Type}\" field=\"typeId\">\n        <f:select/>\n    </f:entry>\n    <f:entry title=\"${%Action Id On Success}\" field=\"actionIdOnSuccess\">\n        <f:number/>\n    </f:entry>\n</j:jelly>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraCreateIssueNotifier/help-actionIdOnSuccess.html",
    "content": "<div>\n    <strong>Optional</strong> <br/>\n    The Jira issue status will transition with the configured workflow action id when the build status returns success, for no transition set '0'.\n</div>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraCreateIssueNotifier/help-assignee.html",
    "content": "<div>\n    <strong>Optional</strong> <br/>\n    This will assign the issue to the given assignee (Jira username).\n</div>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraCreateIssueNotifier/help-component.html",
    "content": "<div>\n    <strong>Optional</strong> <br/>\n    Component names that the issue should be assigned to, separated by comma, e.g.: jira-component,jenkins-integration.<br/>\n    You can find the components in Jira under <strong>Projects</strong> tab -> &lt;PROJECT&gt; -> <strong>Components</strong>.\n</div>\n"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraCreateIssueNotifier/help-priorityId.html",
    "content": "<div>\n    <strong>Optional</strong> <br/>\n    This will be the priority of the Jira issue. For project default leave empty.\n</div>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraCreateIssueNotifier/help-projectKey.html",
    "content": "<div>\n    <strong>Required</strong> <br/>\n    Specify the projectkey in Uppercase, e.g.: OJRA.\n</div>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraCreateIssueNotifier/help-testDescription.html",
    "content": "<div>\n    <strong>Optional</strong> <br/>\n    This will be the description placed in the Jira issue.\n</div>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraCreateIssueNotifier/help-typeId.html",
    "content": "<div>\n    <strong>Optional</strong> <br/>\n    This will be the type of the Jira issue, defaults to Bug.\n</div>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraCreateReleaseNotes/config.jelly",
    "content": "<?jelly escape-by-default='true'?>\n<j:jelly xmlns:j=\"jelly:core\" xmlns:f=\"/lib/form\">\n  <f:entry title=\"${%Environment Variable}\" field=\"jiraEnvironmentVariable\">\n        <f:textbox/>\n  </f:entry>\n  <f:entry title=\"${%Jira Release}\" field=\"jiraRelease\">\n        <f:textbox/>\n  </f:entry>\n  <f:entry title=\"${%Jira Project Key}\" field=\"jiraProjectKey\">\n        <f:textbox/>\n  </f:entry>\n  <f:entry title=\"${%Additional Issue Filter}\" field=\"jiraFilter\">\n        <f:textbox/>\n  </f:entry>\n</j:jelly>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraCreateReleaseNotes/help-jiraEnvironmentVariable.html",
    "content": "<div>\n    <p>Specify the environment variable to which the release notes will be stored, defaults to RELEASE_NOTES.</p>\n    <p>This can be used in another build step which supports environments. </p>\n</div>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraCreateReleaseNotes/help-jiraFilter.html",
    "content": "<div>\n    <p>Apply additional filtering criteria to the issue filter. This will be concatenated with an AND operator.</p>\n    <p>Defaults To:</p>\n    <pre>status in (Released, Closed)</pre>\n</div>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraCreateReleaseNotes/help-jiraProjectKey.html",
    "content": "<div>\n    <p>Specify Jira project key. A project key is the all capitals part before the issue number in Jira.</p>\n    <p>(<strong>EXAMPLE</strong>-100)</p>\n</div>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraCreateReleaseNotes/help-jiraRelease.html",
    "content": "<div>\n    <p>Specify the name of the parameter which will contain the release version. This can reference a build parameter.</p>\n</div>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraEnvironmentVariableBuilder/config.jelly",
    "content": "<?jelly escape-by-default='true'?>\n<j:jelly xmlns:j=\"jelly:core\" xmlns:f=\"/lib/form\">\n    <j:if test=\"${descriptor.hasIssueSelectors()}\">\n        <f:dropdownDescriptorSelector field=\"issueSelector\" title=\"${%Issue selector}\"/>\n    </j:if>  \n</j:jelly>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraEnvironmentVariableBuilder/help.html",
    "content": "<div>\n  Extracts Jira information for the build to environment variables.\n  <div>Available variables:</div>\n  <ul>\n      <li>JIRA_ISSUES - A comma separated list of issues which are referenced in the version control system changelog</li>\n      <li>JIRA_ISSUES_SIZE - Size of the list described above</li>\n      <li>JIRA_URL - Primary URL for the Jira server </li>\n  </ul>\n  <p>\n      Typical usage:\n      <ol>\n          <li>Add this build step</li>\n          <li>Use the \"Progress Jira issues by workflow action\" or \"Move issues matching JQL to the specified version\" with JQL like:\n              <blockquote><pre>issue in (${JIRA_ISSUES})</pre></blockquote></li>\n      </ol>\n  </p>\n</div>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraFolderProperty/config.jelly",
    "content": "<?jelly escape-by-default='true'?>\n<j:jelly xmlns:j=\"jelly:core\" xmlns:st=\"jelly:stapler\" xmlns:d=\"jelly:define\" xmlns:l=\"/lib/layout\" xmlns:t=\"/lib/hudson\" xmlns:f=\"/lib/form\">\n  <f:entry title=\"${%Jira sites}\" description=\"\">\n    <f:repeatableProperty field=\"sites\"/>\n  </f:entry>\n</j:jelly>\n"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraGlobalConfiguration/config.jelly",
    "content": "<?jelly escape-by-default='true'?>\n<j:jelly xmlns:j=\"jelly:core\" xmlns:st=\"jelly:stapler\" xmlns:d=\"jelly:define\" xmlns:l=\"/lib/layout\" xmlns:t=\"/lib/hudson\" xmlns:f=\"/lib/form\">\n  <f:section title=\"Jira\">\n    <f:entry title=\"${%Jira sites}\" description=\"\">\n      <f:repeatableProperty field=\"sites\"/>\n    </f:entry>\n  </f:section>\n</j:jelly>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraIssueMigrator/config.jelly",
    "content": "<?jelly escape-by-default='true'?>\n<j:jelly xmlns:j=\"jelly:core\" xmlns:f=\"/lib/form\">\n  <f:entry title=\"${%Target Jira Release}\" field=\"jiraRelease\">\n        <f:textbox/>\n  </f:entry>\n  <f:entry title=\"${%Add Target Release}\" field=\"addRelease\">\n        <f:checkbox />\n  </f:entry>\n  <f:entry title=\"${%Replace Jira Release}\" field=\"jiraReplaceVersion\">\n        <f:textbox/>\n  </f:entry>\n  <f:entry title=\"${%Jira Project Key}\" field=\"jiraProjectKey\">\n        <f:textbox/>\n  </f:entry>\n  <f:entry title=\"${%JQL Query}\" field=\"jiraQuery\">\n        <f:textbox/>\n  </f:entry>\n</j:jelly>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraIssueMigrator/help-addRelease.html",
    "content": "<div>\n    <p>Add Jira Release instead of replace. If checked, Replace Jira Release field will be ignored.</p>\n</div>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraIssueMigrator/help-jiraProjectKey.html",
    "content": "<div>\n<p>Specify the project key. A project key is the all capitals part before the issue number in Jira.</p>\n<p>(<strong>EXAMPLE</strong>-100)</p>\n</div>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraIssueMigrator/help-jiraQuery.html",
    "content": "<div>\n    <p>Issues which match this JQL Query will be moved to this release version.</p>\n    <p>This can contain <strong>$PARAM</strong> values which will be replaced by the build parameters.</p>\n    <p>Example:</p>\n    <pre>project = PROJECT and fixVersion = \"$RELEASE_VERSION\" and status not in (Resolved, Closed)</pre>\n</div>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraIssueMigrator/help-jiraRelease.html",
    "content": "<div>\n    <p>Specify the name of the parameter which will contain the release version. This can reference a build parameter.</p>\n</div>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraIssueMigrator/help-jiraReplaceVersion.html",
    "content": "<div>\n\t<strong>Optional</strong> <br/>\n    <p>If a a value is provided, then only this version will be replaced instead of all versions.\n    This is useful if you group issues using fixVersions and want to keep these extra versions during the migration.\n    If the value is blank, then all fixVersions will be replaced with the Release Version. <br/>\n    If you want to replace versions matching a pattern you can use regular expressions by surrounding the string with slashes, e.g. /.*SNAPSHOT$/\n    </p>\n</div>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraIssueUpdateBuilder/config.jelly",
    "content": "<!--\nCopyright 2012 MeetMe, Inc.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n     http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n-->\n<?jelly escape-by-default='true'?>\n<j:jelly xmlns:j=\"jelly:core\" xmlns:f=\"/lib/form\">\n  <f:entry title=\"${%JQL Query}\" field=\"jqlSearch\">\n        <f:textbox/>\n  </f:entry>\n  <f:entry title=\"${%Workflow Action}\" field=\"workflowActionName\">\n        <f:textbox/>\n  </f:entry>\n  <f:entry title=\"${%Comment}\" field=\"comment\">\n        <f:textarea/>\n  </f:entry>\n</j:jelly>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraIssueUpdateBuilder/help-comment.html",
    "content": "<div>\n  An optional comment to be added to the issue after updating the workflow. If left empty, no comment will be added.\n  <p />\n  This can contain <strong>$PARAM</strong> values which will be replaced by the build parameters.\n</div>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraIssueUpdateBuilder/help-jqlSearch.html",
    "content": "<div>\n  Issues which match this JQL Query will be progressed using the specified workflow action.\n  <p />\n  This can contain <strong>$PARAM</strong> values which will be replaced by the build parameters.\n\n  <p>\n    Example:\n    <blockquote><pre>project = JENKINS and fixVersion = \"$RELEASE_VERSION\" and status not in (Resolved, Closed)</pre></blockquote>\n    or (e.g., combined with a Jira Issue Parameter, selecting one issue from a JQL result set):\n    <blockquote><pre>issue = $ISSUE_ID</pre></blockquote>\n  </p>\n</div>\n"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraIssueUpdateBuilder/help-workflowActionName.html",
    "content": "<div>\n  The workflow action to be performed on the selected Jira issues.\n  <p />\n  Be mindful of the issues being selected by the JQL query, because not all actions are valid for all issue statuses.\n  <p />\n  <b>NOTE:</b> the Jenkins user must have access to perform the workflow step, as if the user were logged in and viewing the issue in a web browser.\n</div>\n"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraIssueUpdateBuilder/help.html",
    "content": "<div>\n  Performs a Jira workflow action for every issue that matches the JQL query.\n  A common use might be to consider a ticket \"confirmed\" in the last build step\n  of a job, or to mark an issue as \"merged\" if the job is used to merge changes\n  from one SCM repository to another.\n  <p />\n  Optionally, include a comment that will be attached to those tickets that are modified as a result of this build step.\n</div>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraIssueUpdater/config.jelly",
    "content": "<?jelly escape-by-default='true'?>\n<j:jelly xmlns:j=\"jelly:core\" xmlns:st=\"jelly:stapler\" xmlns:d=\"jelly:define\" xmlns:l=\"/lib/layout\" xmlns:t=\"/lib/hudson\" xmlns:f=\"/lib/form\" xmlns:i=\"jelly:fmt\">\n    <j:if test=\"${descriptor.hasIssueSelectors()}\">\n        <f:dropdownDescriptorSelector field=\"issueSelector\" title=\"${%Issue selector}\"/>\n    </j:if>\n</j:jelly>\n"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraProjectProperty/config.jelly",
    "content": "<?jelly escape-by-default='true'?>\n<j:jelly xmlns:j=\"jelly:core\" xmlns:st=\"jelly:stapler\" xmlns:d=\"jelly:define\" xmlns:l=\"/lib/layout\" xmlns:t=\"/lib/hudson\" xmlns:f=\"/lib/form\">\n  <f:entry title=\"${%Jira site}\" field=\"siteName\">\n    <f:select/>\n  </f:entry>\n</j:jelly>\n"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraReleaseVersionUpdater/config.jelly",
    "content": "<?jelly escape-by-default='true'?>\n<j:jelly xmlns:j=\"jelly:core\" xmlns:f=\"/lib/form\">\n  <f:entry title=\"${%Jira Release}\" field=\"jiraRelease\">\n        <f:textbox/>\n  </f:entry>\n  <f:entry title=\"${%Jira Project Key}\" field=\"jiraProjectKey\">\n        <f:textbox/>\n  </f:entry>\n  <f:entry title=\"${%Jira Description}\" field=\"jiraDescription\">\n        <f:textbox/>\n  </f:entry>\n</j:jelly>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraReleaseVersionUpdater/help-jiraDescription.html",
    "content": "<div>\n\t<strong>Optional</strong> <br/>\n\tSpecify a description of the release version. This can reference a build parameter.\n</div>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraReleaseVersionUpdater/help-jiraProjectKey.html",
    "content": "<div>\n<p>Specify the project key. A project key is the all capitals part before the issue number in Jira.</p>\n<p>(<strong>EXAMPLE</strong>-100)</p>\n</div>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraReleaseVersionUpdater/help-jiraRelease.html",
    "content": "<div>\nSpecify the name of the parameter which will contain the release version. This can reference a build parameter.\n</div>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraReleaseVersionUpdaterBuilder/config.jelly",
    "content": "<?jelly escape-by-default='true'?>\n<j:jelly xmlns:j=\"jelly:core\" xmlns:f=\"/lib/form\">\n  <f:entry title=\"${%Jira Release}\" field=\"jiraRelease\">\n        <f:textbox/>\n  </f:entry>\n  <f:entry title=\"${%Jira Project Key}\" field=\"jiraProjectKey\">\n        <f:textbox/>\n  </f:entry>\n  <f:entry title=\"${%Jira Description}\" field=\"jiraDescription\">\n          <f:textbox/>\n    </f:entry>\n</j:jelly>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraReleaseVersionUpdaterBuilder/help-jiraDescription.html",
    "content": "<div>\n\t<strong>Optional</strong> <br/>\n\tSpecify a description of the release version. This can reference a build parameter.\n</div>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraReleaseVersionUpdaterBuilder/help-jiraProjectKey.html",
    "content": "<div>\n<p>Specify the project key. A project key is the all capitals part before the issue number in Jira.</p>\n<p>(<strong>EXAMPLE</strong>-100)</p>\n</div>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraReleaseVersionUpdaterBuilder/help-jiraRelease.html",
    "content": "<div>\nSpecify the name of the parameter which will contain the release version. This can reference a build parameter.\n</div>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraSite/config.jelly",
    "content": "<?jelly escape-by-default='true'?>\n<j:jelly xmlns:j=\"jelly:core\" xmlns:st=\"jelly:stapler\" xmlns:d=\"jelly:define\" xmlns:l=\"/lib/layout\" xmlns:t=\"/lib/hudson\" xmlns:f=\"/lib/form\" xmlns:c=\"/lib/credentials\">\n  <f:entry title=\"URL\" field=\"url\">\n    <f:textbox />\n  </f:entry>\n  <f:entry title=\"Link URL\" field=\"alternativeUrl\" description=\"${%site.alternativeUrl}\">\n    <f:textbox />\n  </f:entry>\n  <f:invisibleEntry>\n    <f:checkbox title=\"${%Use HTTP authentication instead of normal login}\" field=\"useHTTPAuth\" />\n  </f:invisibleEntry>\n  <f:entry description=\"${%site.useBearerAuth}\">\n    <f:checkbox title=\"${%Use Bearer authentication instead of Basic authentication}\" field=\"useBearerAuth\" />\n  </f:entry>\n  <f:entry>\n    <f:checkbox title=\"${%Supports Wiki notation}\" field=\"supportsWikiStyleComment\" />\n  </f:entry>\n  <f:entry>\n    <f:checkbox title=\"${%Record Scm changes}\" field=\"recordScmChanges\" />\n  </f:entry>\n  <f:entry>\n    <f:checkbox title=\"${%Disable changelog annotations}\" field=\"disableChangelogAnnotations\" />\n  </f:entry>\n  <f:entry title=\"${%Issue Pattern}\" field=\"userPattern\">\n    <f:textbox />\n  </f:entry>\n  <f:entry>\n    <f:checkbox title=\"${%Update Relevant Jira Issues For All Build Results}\" field=\"updateJiraIssueForAllStatus\" />\n  </f:entry>\n  <f:entry title=\"${%Credentials}\" field=\"credentialsId\">\n    <c:select />\n  </f:entry>\n  <f:entry title=\"${%Connection timeout}\" field=\"timeout\">\n    <f:number default=\"10\" />\n  </f:entry>\n  <f:entry title=\"${%Read timeout}\" field=\"readTimeout\">\n    <f:number default=\"30\" />\n  </f:entry>\n  <f:entry title=\"${%Thread Executor Size}\" field=\"threadExecutorNumber\">\n    <f:number default=\"10\" />\n  </f:entry>\n  <f:entry title=\"${%Visible for Group}\" field=\"groupVisibility\">\n    <f:textbox />\n  </f:entry>\n  <f:entry title=\"${%Visible for Project Role}\" field=\"roleVisibility\">\n    <f:textbox />\n  </f:entry>\n  <f:entry>\n    <f:checkbox title=\"${%Add timestamp to Jira comments}\" field=\"appendChangeTimestamp\" />\n  </f:entry>\n  <f:entry title=\"${%Jira comments timestamp format}\" field=\"dateTimePattern\">\n    <f:textbox />\n  </f:entry>\n  <f:entry title=\"${%Max Issues From Jql Search}\" field=\"maxIssuesFromJqlSearch\">\n    <f:number default=\"100\" max=\"5000\" clazz=\"positive-number-required\" />\n  </f:entry>\n  <f:entry>\n    <f:validateButton title=\"${%Validate Settings}\"\n            method=\"validate\" with=\"url,credentialsId,groupVisibility,roleVisibility,useHTTPAuth,alternativeUrl,timeout,readTimeout,threadExecutorNumber,useBearerAuth\" />\n  </f:entry>\n  <f:entry title=\"\">\n    <div align=\"right\">\n      <f:repeatableDeleteButton />\n    </div>\n  </f:entry>\n</j:jelly>\n"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraSite/config.properties",
    "content": "site.alternativeUrl=Jira alternative URL\nsite.timeout=in seconds\nsite.useBearerAuth=Note: Bearer authentication is only supported in Jira Server, for Jira Cloud leave this unchecked"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraSite/config_de.properties",
    "content": "Jira\\ sites=Jira Instanzen\nSupports\\ Wiki\\ notation=Untersttzt Wiki-Notation\n"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraSite/config_fr.properties",
    "content": "Jira\\ sites=Sites Jira\nSupports\\ Wiki\\ notation= Support des annotations wiki.\nRecord\\ Scm\\ changes= Enregistrement des changements du scm.\n"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraSite/config_it.properties",
    "content": "site.alternativeUrl=URL alternativo di Jira\nsite.timeout=in secondi\nsite.useBearerAuth=Nota: L'autenticazione del portatore \\u00e8 supportata solo in Jira Server, perch\\u00e9 Jira Cloud lascia questo non controllato"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraSite/config_ja.properties",
    "content": "Jira\\ sites=Jira \\u30B5\\u30A4\\u30C8\nSupports\\ Wiki\\ notation=Wiki\\u8A18\\u6CD5\\u306E\\u30B5\\u30DD\\u30FC\\u30C8\nRecord\\ Scm\\ changes=SCM\\u306E\\u5909\\u66F4\\u3092\\u8A18\\u9332\nIssue\\ Pattern=\\u8AB2\\u984CID\\u30D1\\u30BF\\u30FC\\u30F3\nUpdate\\ Jira\\ Issues\\ For\\ All\\ Build\\ Results=\\u3059\\u3079\\u3066\\u306E\\u30D3\\u30EB\\u30C9\\u7D50\\u679C\\u306B\\u3064\\u3044\\u3066Jira\\u306E\\u8AB2\\u984C\\u3092\\u66F4\\u65B0\n"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraSite/config_nl.properties",
    "content": "# The MIT License\n#\n# Copyright (c) 2004-2010, Sun Microsystems, Inc.\n#\n# Permission is hereby granted, free of charge, to any person obtaining a copy\n# of this software and associated documentation files (the \"Software\"), to deal\n# in the Software without restriction, including without limitation the rights\n# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n# copies of the Software, and to permit persons to whom the Software is\n# furnished to do so, subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be included in\n# all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n# THE SOFTWARE.\n\nJira\\ sites=Jira-sites\nRecord\\ Scm\\ changes=Houd SCM-veranderingen bij\nSupports\\ Wiki\\ notation=Ondersteunt Wiki-notatie\n"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraSite/config_pl.properties",
    "content": "site.alternativeUrl=Alternatywny Jira URL\nsite.timeout=w sekundach\nsite.useBearerAuth=Uwaga: uwierzytelnianie Bearer jest obs\\u0142ugiwane tylko dla Jira Server. Je\\u015bli u\\u017cywasz Jira Cloud pozostaw to pole niezaznaczone"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraSite/help-alternativeUrl.html",
    "content": "<div>\n  Specify the root URL of your Jira installation for &quot;normal&quot; access, like <tt>https://issues.apache.org/jira/</tt>.\n</div>\n"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraSite/help-appendChangeTimestamp.html",
    "content": "<div>\n  If activated, SCM change entries date and time will be recorded in Jira.\n</div>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraSite/help-credentialsId.html",
    "content": "<div>\n  If the remote API support is enabled in this Jira, you can set\n  a valid account information. This would allow projects to update\n  their issues whenever builds integrate those changes, by using\n  this configured account.  Each project can choose whether to\n  actually update Jira via a selection in the publish section.\n  <br>\n\n</div>\n"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraSite/help-credentialsId_de.html",
    "content": "<div>\n  Falls die Remote-API Unterstützung in Jira aktiviert ist, kann man hier\n  die Login-Informationen angeben. Damit können Jenkins-Projekte ihre zugehörigen\n  Issues aktualisieren, falls Änderungen für sie in einen Build integriert wurden.\n  Ob die Aktualisierung tatsächlich passiert, kann per Projekt konfiguriert werden.\n  <br>\n \n</div>\n"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraSite/help-credentialsId_fr.html",
    "content": "<div>\n  Si l'api \"remote\" est activ&#233; dans Jira, vous pouvez configurer un compte valide.\n  Cel&#224; permettra aux projets de mettre  &#224; jour les entr&#233;es Jira \n  avec les \"builds\" Jenkins int&#233;grant le changement en utilisant le compte configur&#233;\n  Chaque projet peut choisir de mettre &#224; jour ou pas avec une s&#233;lection dans la partie \"publisher\"\n  <br>\n \n</div>\n"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraSite/help-credentialsId_ja.html",
    "content": "<div>\n  JiraでリモートAPIがサポートされている場合、正しいアカウント情報を設定します。\n  設定すると、ビルドが変更を統合した際にissueを更新します。\n  また、プロジェクトの設定画面で、どのJiraを更新するか選択することができます。\n  <br>\n \n</div>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraSite/help-dateTimePattern.html",
    "content": "<div>\nSee <a href=\"https://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html\">javadoc</a> for SimpleDateFormat for help.\nIf not set, DateFormat.SHORT for the current locale will be used.\n</div>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraSite/help-disableChangelogAnnotations.html",
    "content": "<div>\n    Disable creating Jira hyperlinks in the changeset.\n</div>\n"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraSite/help-groupVisibility.html",
    "content": "<div>\n  Enter the name of the Jira group that has permission to view\n  the comment, leave the field empty to make the comment\n  available to all Jira users.\n  <br>\n</div>\n"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraSite/help-maxIssuesFromJqlSearch.html",
    "content": "<div>\nSpecifies the maximum number of issues to load from the JQL search query. If this number exceeds the limit configured in Jira, only the maximum allowed by Jira will be retrieved.\n</div>\n"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraSite/help-readTimeout.html",
    "content": "<div>\nRead timeout for Jira REST API calls (in seconds).\n</div>\n"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraSite/help-recordScmChanges.html",
    "content": "<div>\n  If activated, scm changes will be recorded in Jira : link to the scm repository browser and paths changes.\n</div>\n"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraSite/help-recordScmChanges_fr.html",
    "content": "<div>\n  Si activ&#233;, les changements du scm seront enregistr&#233;s dans Jira : avec un lien vers le \"browser\" de scm et les fichiers chang&#233;.\n</div>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraSite/help-recordScmChanges_ja.html",
    "content": "<div>\n  SCMリポジトリブラウザへのリンクやパスの変更が、SCMが変更されるとJiraに記録されます。\n</div>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraSite/help-roleVisibility.html",
    "content": "<div>\n  Enter the name of the Jira project role that has permission to view\n  the comment, leave the field empty to make the comment\n  available to all Jira users.\n  <br>\n</div>\n"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraSite/help-supportsWikiStyleComment.html",
    "content": "<div>\n  Check this box if this Jira supports Wiki notations in comments.\n  When checked, Jenkins will post comments that take advantage of the Wiki notation.\n  If left unchecked, Jenkins will only post plain-text comments.\n</div>\n"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraSite/help-supportsWikiStyleComment_de.html",
    "content": "<div>\r\n  Aktivieren Sie diese Checkbox, falls die Jira Instanz Wiki-Notation unterstützt.\r\n  Falls aktiv, wird Jenkins Kommentare in Wiki-Notation senden.\r\n  Andernfalls werden einfache Textkommentare gesendet.\r\n</div>\r\n"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraSite/help-supportsWikiStyleComment_fr.html",
    "content": "<div>\n  Cochez si votre Jira supporte la syntaxe WIKI dans les commentaires.\n  Si activ&#233;, Jenkins postera des commentaires qui utiliseront cette syntaxe.\n  Si non activ&#233;, Jenkins postera des commentaires au format texte.\n</div>\n"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraSite/help-supportsWikiStyleComment_ja.html",
    "content": "<div>\n  このJiraがコメントの記述にWiki記法をサポートしているなら、このチェックボックスをチェックしてください。\n  チェックすると、JenkinsはWiki記法を使用してコメントをポストします。\n  チェックしないと、Jenkinsはプレーンテキストのままコメントをポストします。　\n</div>\n"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraSite/help-threadExecutorNumber.html",
    "content": "<div>\nSize of the Thread Pool Executor to query Jira.\n<b>If you have a lot of builds using the Jira plugin, it might be a good idea to have a minimum size of 10.</b>\n</div>\n"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraSite/help-timeout.html",
    "content": "<div>\nConnection timeout for Jira REST API calls (in seconds).\n</div>\n"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraSite/help-updateJiraIssueForAllStatus.html",
    "content": "<div>\n    <p>If this is unchecked, issues will be only updated if the build is SUCCESSful or UNSTABLE.</p>\n    <p>If this is checked, related Jira issues will be always updated, regardless of the build result.</p>\n</div>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraSite/help-url.html",
    "content": "<div>\n  Specify the root URL of your Jira installation, like <tt>http://issues.apache.org/jira/</tt>.\n</div>\n"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraSite/help-url_de.html",
    "content": "<div>\n  Geben Sie die Root-URL der Jira Instanz an, z.B. <tt>http://issues.apache.org/jira/</tt>\n</div>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraSite/help-url_fr.html",
    "content": "<div>\n  URL racine de votre installation Jira, exemple : <tt>http://issues.apache.org/jira/</tt>\n</div>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraSite/help-url_ja.html",
    "content": "<div>\n  <tt>http://issues.apache.org/jira/</tt> のように、JiraのルートURLを指定してください。\n</div>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraSite/help-useHTTPAuth.html",
    "content": "<div>\n  This option forces Jenkins to connect to Jira using HTTP Basic Authentication, instead of logging in\n  over RPC.\n\n  <p>\n  It is not clear why someone would prefer this over normal login, and indeed <tt>getIssueWithFixVersion</tt>\n  and JQL search is known to break with this setting. Therefore I <b>recommend not enabling this</b> unless\n  you have specific reasons to prefer this (and please let us know why you want this so that we can fix\n  this documentation).\n</div>\n"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraSite/help-userPattern.html",
    "content": "<div>\n  You can define your own pattern to search for Jira issue ids in the SCM logs.<br />\n  If empty the default one is used : ([a-zA-Z][a-zA-Z0-9_]+-[1-9][0-9]*)([^.]|\\.[^0-9]|\\.$|$)<br />\n  Note that the pattern must contain one matching group (on index 1) which should match the actual Jira id.\n</div>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraSite/help-userPattern_de.html",
    "content": "<div>\n  Eigenes Suchmuster (regulärer Ausdruck) zum Erkennen von Jira IssueS definieren.<br />\n  Falls leer, wird das Standardmuster benutzt: ([a-zA-Z][a-zA-Z0-9_]+-[1-9][0-9]*)([^.]|\\.[^0-9]|\\.$|$)<br />\n  Bitte beachten, dass das Muster eine Matching-Gruppe (mit Index 1) enthalten muss, welche die Jira Id enthalten muss!\n</div>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraSite/help-userPattern_fr.html",
    "content": "<div>\n  Vous pouvez d&#233;finir ici votre pattern pour rechercher les Ids Jira dans les commentaires\n   du commit dans le scm.\n  Si vide celui par d&#233;faut est utilis&#233; : ([a-zA-Z][a-zA-Z0-9_]+-[1-9][0-9]*)([^.]|\\.[^0-9]|\\.$|$)\n  <br>\n \n</div>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraSite/help-userPattern_ja.html",
    "content": "<div>\n  SCMのログからJiraの課題IDを検索するパターンを定義することができます。\n  設定しないとデフォルトのパターン ([a-zA-Z][a-zA-Z0-9_]+-[1-9][0-9]*)([^.]|\\.[^0-9]|\\.$|$) が使用されます。\n  <br>\n \n</div>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraVersionCreator/config.jelly",
    "content": "<?jelly escape-by-default='true'?>\n<j:jelly xmlns:j=\"jelly:core\" xmlns:f=\"/lib/form\">\n  <f:entry title=\"${%Jira Version}\" field=\"jiraVersion\">\n        <f:textbox/>\n  </f:entry>\n  <f:entry title=\"${%Jira Project Key}\" field=\"jiraProjectKey\">\n        <f:textbox/>\n  </f:entry>\n  <f:entry title=\"${%Fail if version already exists}\" field=\"failIfAlreadyExists\">\n        <f:checkbox default=\"true\"/>\n  </f:entry>\n</j:jelly>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraVersionCreator/help-failIfAlreadyExists.html",
    "content": "<div>\n    When checked (default), the build will fail if the version already exists in Jira. \n    When unchecked, the build will continue successfully even if the version already exists.\n</div>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraVersionCreator/help-jiraProjectKey.html",
    "content": "<div>\n<p>Specify the project key. A project key is the all capitals part before the issue number in Jira.</p>\n<p>(<strong>EXAMPLE</strong>-100)</p>\n</div>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraVersionCreator/help-jiraVersion.html",
    "content": "<div>\nSpecify the name of the parameter which will contain the release version. This can reference a build parameter.\n</div>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraVersionCreatorBuilder/config.jelly",
    "content": "<?jelly escape-by-default='true'?>\n<j:jelly xmlns:j=\"jelly:core\" xmlns:f=\"/lib/form\">\n  <f:entry title=\"${%Jira Version}\" field=\"jiraVersion\">\n        <f:textbox/>\n  </f:entry>\n  <f:entry title=\"${%Jira Project Key}\" field=\"jiraProjectKey\">\n        <f:textbox/>\n  </f:entry>\n  <f:entry title=\"${%Fail if version already exists}\" field=\"failIfAlreadyExists\">\n        <f:checkbox default=\"true\"/>\n  </f:entry>\n</j:jelly>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraVersionCreatorBuilder/help-failIfAlreadyExists.html",
    "content": "<div>\n    When checked (default), the build will fail if the version already exists in Jira. \n    When unchecked, the build will continue successfully even if the version already exists.\n</div>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraVersionCreatorBuilder/help-jiraProjectKey.html",
    "content": "<div>\n<p>Specify the project key. A project key is the all capitals part before the issue number in Jira.</p>\n<p>(<strong>EXAMPLE</strong>-100)</p>\n</div>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/JiraVersionCreatorBuilder/help-jiraVersion.html",
    "content": "<div>\nSpecify the name of the parameter which will contain the release version. This can reference a build parameter.\n</div>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/MavenJiraIssueUpdater/config.jelly",
    "content": "<?jelly escape-by-default='true'?>\n<j:jelly xmlns:j=\"jelly:core\" />"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/Messages.properties",
    "content": "JiraIssueUpdater.DisplayName=Jira: Update relevant issues\nJiraFolderProperty.DisplayName=Associated Jira\nJiraProjectProperty.DisplayName=Associated Jira\nJiraProjectProperty.JiraUrlMandatory=Jira URL is a mandatory field\nJiraProjectProperty.NotAJiraUrl=This is a valid URL but it doesn''t look like Jira\nJiraProjectProperty.NoWsdlAvailable=This looks like a Jira instance but no WSDL is available. Perhaps SOAP support is not enabled yet?\nJiraBuildAction.DisplayName=Jira issues\nDefaultIssueSelector.DisplayName=Default selector\nPerforceJobIssueSelector.DisplayName=Perforce job selector\nP4JobIssueSelector.DisplayName=Perforce Software (P4) job selector\nJiraReleaseVersionBuilder.DisplayName=Jira: Mark a version as Released\nJiraReleaseVersionMigrator.DisplayName=Jira: Move issues matching JQL to the specified version\nJiraIssueUpdateBuilder.DisplayName=Jira: Progress issues by workflow action\nJiraIssueUpdateBuilder.NoJqlSearch=Please set the JQL used to select the issues to update.\nJiraIssueUpdateBuilder.NoWorkflowAction=A workflow action is required.\nJiraIssueUpdateBuilder.UpdatingWithAction=[Jira] Updating issues using workflow action {0}.\nJiraIssueUpdateBuilder.Failed=[Jira] An error occurred while progressing issues:\nJiraIssueUpdateBuilder.UnknownWorkflowAction=[Jira] Unable to update issue {0}: invalid workflow action \"{1}\". Perhaps the Jenkins user does not have permission to perform the action on the Jira issue?\nJiraIssueUpdateBuilder.SomeIssuesFailed=[Jira] At least one issue failed to update.  See log above for more details.\nJiraVersionCreator.DisplayName=Jira: Create new version\nVersionReleaser.AlreadyReleased=[Jira] The version {0} is already released in project {1}, so nothing to do.\nVersionReleaser.MarkingReleased=[Jira] Marking version {0} in project {1} as released.\nJiraVersionCreator.VersionExists=[Jira] A version with name {0} already exists in project {1}, so nothing to do.\nJiraVersionCreator.CreatingVersion=[Jira] Creating version {0} in project {1}.\nJiraEnvironmentVariableBuilder.DisplayName=Jira: Add related environment variables to build\nJiraEnvironmentVariableBuilder.NoJiraSite=[Jira] No Jira site is configured for this project. This must be a project configuration error\nJiraEnvironmentVariableBuilder.Updating=[Jira] Setting {0} to {1}.\nCommentStep.Descriptor.DisplayName=Jira: Add a comment to issue(s)\nSearchIssuesStep.Descriptor.DisplayName=Jira: Search issues\nIssueSelectorStep.Descriptor.DisplayName=Jira: Issue selector\nJiraCreateIssueNotifier.DisplayName=Jira: Create issue\nIssueSelector.ExplicitIssueSelector.DisplayName=Explicit selector\nIssueSelector.JqlIssueSelector.DisplayName=JQL selector\nJiraVersionCreatorBuilder.DisplayName=Jira: Create new version\nJiraIssueFieldUpdater.DisplayName=Jira: Issue custom field updater\nJiraIssueFieldUpdater.NoIssueFieldID=Issue field ID required\nJiraIssueFieldUpdater.NotAtIssueFieldID=Not an issue field ID\nIssueFieldUpdaterStep.UpdatingIssue=[Jira][IssueFieldUpdaterStep] Updating issue {0}\nFailedToUpdateIssue=[Jira] Failed to update issue {0}.\nFailedToUpdateIssueWithCarryOver=[Jira] Failed to update issue {0}. Carrying over to next build.\nUpdatingIssue=[Jira] Updating issue {0}\nFailedToConnect=[Jira] Failed to connect to Jira\nNoJenkinsUrl=[Jira] Jenkins URL is not configured yet. Go to system configuration to set this value\nNoJiraSite=[Jira] No Jira site is configured for this project. This must be a project configuration error\nNoRemoteAccess=[Jira] The system configuration does not allow remote Jira access\nErrorCommentingIssues=[Jira] Could not comment on some issues: {0}\nJiraSite.threadExecutorMinimunSize = Thread Executor Size must be at least {0} (higher values are recommended)\nJiraSite.timeoutMinimunValue = Connection timeout must be at least {0}\nJiraSite.readTimeoutMinimunValue = Read timeout must be at least {0}\nJiraIssueParameterDefinition.NoIssueMatchedSearch = No issues matched the search\nJiraVersionParameterDefinition.NoVersionsMatchedSearch = No version matched the search"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/Messages_de.properties",
    "content": "JiraProjectProperty.DisplayName=Zugeh\\u00f6rige Jira-Instanz\nJiraProjectProperty.JiraUrlMandatory=Jira URL ist ein Pflichtfeld.\nJiraProjectProperty.NotAJiraUrl=Dies ist eine g\\u00f6ltige URL, aber anscheinend keine Jira URL.\nJiraBuildAction.DisplayName=Jira Issues\nFailedToConnect=Verbindung zu Jira fehlgeschlagen.\nNoJenkinsUrl=Jenkins URL ist noch nicht konfiguriert. Bitte geben Sie sie in der Systemkonfiguration ein.\nNoJiraSite=Keine Jira Instanz konfiguriert f\\u00fcr dieses Projekt.\nNoRemoteAccess=Die Jira Konfiguration erlaubt keinen Remote-Zugriff auf Jira.\nErrorCommentingIssues=Konnte einige Vorg\\a00e4nge nicht kommentieren: {0}\n"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/Messages_fr.properties",
    "content": "JiraIssueUpdater.DisplayName=Jira : Mise \\u00e0 jour des demandes\nJiraProjectProperty.DisplayName=Jira associ\\u00e9\nJiraProjectProperty.JiraUrlMandatory=L'URL Jira est un champ obligatoire\nJiraProjectProperty.NotAJiraUrl=C''est une URL valide mais cel\\u00e0 n'a pas l'air d'\\u00eatre une URL Jira\nJiraProjectProperty.NoWsdlAvailable=Ceci ressemble \\u00e0 une instance Jira mais WSDL n''est pas disponible. Peut \\u00eatre que le support SOAP n''est pas actif ?\nJiraBuildAction.DisplayName=Demandes Jira\nDefaultIssueSelector.DisplayName=S\\u00e9lecteur par d\\u00e9faut\nPerforceJobIssueSelector.DisplayName=S\\u00e9lecteur Perforce job\nP4JobIssueSelector.DisplayName=S\\u00e9lecteur Perforce Software (P4) job\nJiraReleaseVersionBuilder.DisplayName=Jira : Marquer une version r\\u00e9leas\\u00e9e\nJiraReleaseVersionMigrator.DisplayName=Jira : D\\u00e9placer les demandes correspondant au JQL vers la version sp\\u00e9cifi\\u00e9e\nJiraIssueUpdateBuilder.DisplayName=Jira : Appliquer une transition de flux de travaux aux demandes.\nJiraIssueUpdateBuilder.NoJqlSearch=Merci de sp\\u00e9cifier la JQL \\u00e0 utiliser pour s\\u00e9lectionner les demandes \\u00e0 mettre \\u00e0 jour.\nJiraIssueUpdateBuilder.NoWorkflowAction=Une action du flux de travaux est requise.\nJiraIssueUpdateBuilder.UpdatingWithAction=[Jira] Mise \\u00e0 jour des demandes suivant l''action du flux de travaux {0}.\nJiraIssueUpdateBuilder.Failed=[Jira] Une erreur est survenue durant la mise \\u00e0 jour des demandes :\nJiraIssueUpdateBuilder.UnknownWorkflowAction=[Jira] Impossible de trouver la demande {0} : \"{1}\" n''est pas une action de flux de travaux valide. Jenkins n''a peut \\u00eatre pas le droit d''ex\\u00e9cuter cette action sur cette demande ?\nJiraIssueUpdateBuilder.SomeIssuesFailed=[Jira] Au moins une demande n''a pas \\u00e9t\\u00e9 mise \\u00e0 jour. Consultez les logs pour plus de d\\u00e9tails.\nJiraVersionCreator.DisplayName=Jira : Cr\\u00e9er une nouvelle version\nVersionReleaser.AlreadyReleased=[Jira] La version {0} est d\\u00e9j\\u00e0 releas\\u00e9e dans le projet {1}, il n''y a donc rien \\u00e0 faire.\nVersionReleaser.MarkingReleased=[Jira] La version {0} du projet {1} a \\u00e9t\\u00e9 marqu\\u00e9e comme releas\\u00e9e.\nJiraVersionCreator.VersionExists=[Jira] Une version nomm\\u00e9e {0} existe d\\u00e9j\\u00e0 dans le projet {1}, il n''y a donc rien \\u00e0 faire.\nJiraVersionCreator.CreatingVersion=[Jira] Cr\\u00e9ation de la version {0} dans le projet {1}.\nJiraEnvironmentVariableBuilder.DisplayName=Jira : Ajout des variables d''environnement \\u00e0 la construction\nJiraEnvironmentVariableBuilder.NoJiraSite=[Jira] Aucun site Jira n''est configur\\u00e9 pour ce projet. C''est certainement une erreur de configuration du projet\nJiraEnvironmentVariableBuilder.Updating=[Jira] D\\u00e9finition de la variable {0} avec la valeur {1}.\nCommentStep.Descriptor.DisplayName=Jira : Ajout un commentaire aux demandes\nSearchIssuesStep.Descriptor.DisplayName=Jira : Rechercher des demandes\nIssueSelectorStep.Descriptor.DisplayName=Jira : S\\u00e9lecteur de demande\nJiraCreateIssueNotifier.DisplayName=Jira : Cr\\u00e9er une demande\nIssueSelector.ExplicitIssueSelector.DisplayName=Selecteur explicite\nIssueSelector.JqlIssueSelector.DisplayName=S\\u00e9lecteur JQL\nJiraVersionCreatorBuilder.DisplayName=Jira : Cr\\u00e9er une nouvelle version\nJiraIssueFieldUpdater.DisplayName=Jira : Mise \\u00e0 jour d''un champ personnalis\\u00e9\nJiraIssueFieldUpdater.NoIssueFieldID=ID du champ personnalis\\u00e9 requis\nJiraIssueFieldUpdater.NotAtIssueFieldID=Ce n''est pas un ID de champ\nIssueFieldUpdaterStep.UpdatingIssue=[Jira][IssueFieldUpdaterStep] Mise \\u00e0 jour de la demande {0}\nFailedToUpdateIssue=[Jira] Echec de la mise \\u00e0 jour de la demande {0}.\nFailedToUpdateIssueWithCarryOver=[Jira] La mise \\u00e0 jour de la demande {0} a echou\\u00e9e. Report sur la prochaine construction.\nUpdatingIssue=[Jira] Mise \\u00e0 jour de la demande {0}\nFailedToConnect=Impossible de se connecter \\u00e0 Jira\nNoJenkinsUrl=L''URL de Jenkins n''est pas encore configur\\u00e9. Allez dans la configuration de Jenkins pour la mettre \\u00e0 jour\nNoJiraSite=Aucun site Jira n''est configur\\u00e9 pour ce projet. Cel\\u00e0 doit \\u00eatre une erreur de configuration du projet.\nNoRemoteAccess=La configuration ne permet l'acc\\u00e8s distant aux demandes Jira.\nErrorCommentingIssues=Impossible de commenter certaines demandes : {0}\n"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/Messages_it.properties",
    "content": "JiraIssueUpdater.DisplayName=Jira: Aggiornare i problemi rilevanti\nJiraFolderProperty.DisplayName=Jira Associato\nJiraProjectProperty.DisplayName=Jira Associato\nJiraProjectProperty.JiraUrlMandatory=Jira URL \\u00e8 un campo obbligatorio\nJiraProjectProperty.NotAJiraUrl=Questo \\u00e8 un URL valido ma non appare come Jira\nJiraProjectProperty.NoWsdlAvailable=Questa sembra un'istanza Jira, ma non \\u00e8 disponibile nessuna WSDL. Forse il supporto SOAP non \\u00e8 ancora abilitato?\nJiraBuildAction.DisplayName=Problemi da Jira\nDefaultIssueSelector.DisplayName=Selettore predefinito\nPerforceJobIssueSelector.DisplayName=Selettore lavoro di Perforce\nP4JobIssueSelector.DisplayName=Perforce Software (P4) lavoro selettore\nJiraReleaseVersionBuilder.DisplayName=Jira: Segna una versione come pubblicata\nJiraReleaseVersionMigrator.DisplayName=Jira: Sposta i problemi corrispondenti a JQL nella versione specificata\nJiraIssueUpdateBuilder.DisplayName=Jira: Problemi di avanzamento per azione del flusso di lavoro\nJiraIssueUpdateBuilder.NoJqlSearch=Imposta il JQL usato per selezionare i problemi da aggiornare.\nJiraIssueUpdateBuilder.NoWorkflowAction=\\u00c8 necessaria un'azione del flusso di lavoro.\nJiraIssueUpdateBuilder.UpdatingWithAction=[Jira] Aggiornamento problemi utilizzando l'azione del flusso di lavoro {0}.\nJiraIssueUpdateBuilder.Failed=[Jira] Si \\u00e8 verificato un errore durante la progressione dei problemi:\nJiraIssueUpdateBuilder.UnknownWorkflowAction=[Jira] Impossibile aggiornare il problema {0}: azione del flusso di lavoro non valida \"{1}\". Forse l'utente Jenkins non ha il permesso di eseguire l'azione sul problema Jira?\nJiraIssueUpdateBuilder.SomeIssuesFailed=[Jira] Non \\u00e8 stato possibile aggiornare almeno un problema. Vedi il log qui sopra per maggiori dettagli.\nJiraVersionCreator.DisplayName=Jira: Crea una nuova versione\nVersionReleaser.AlreadyReleased=[Jira] La versione {0} \\u00e8 gi\\u00e0 rilasciata nel progetto {1}, quindi niente da fare.\nVersionReleaser.MarkingReleased=[Jira] Marcatura versione {0} nel progetto {1} come rilasciato.\nJiraVersionCreator.VersionExists=[Jira] La versione {0} \\u00e8 gi\\u00e0 rilasciata nel progetto {1}, quindi niente da fare.\nJiraVersionCreator.CreatingVersion=[Jira] Creare la versione {0} nel progetto {1}.\nJiraEnvironmentVariableBuilder.DisplayName=Jira: Aggiungi le variabili di ambiente correlate da costruire\nJiraEnvironmentVariableBuilder.NoJiraSite=[Jira] Nessun sito Jira \\u00e8 configurato per questo progetto. Deve essere un errore di configurazione del progetto\nJiraEnvironmentVariableBuilder.Updating=[Jira] Impostazione {0} a {1}.\nCommentStep.Descriptor.DisplayName=Jira: Aggiungi un commento ai problemi\nSearchIssuesStep.Descriptor.DisplayName=Jira: Cerca problemi\nIssueSelectorStep.Descriptor.DisplayName=Jira: Selettore dei problemi\nJiraCreateIssueNotifier.DisplayName=Jira: Crea il problema\nIssueSelector.ExplicitIssueSelector.DisplayName=Selettore esplicito\nIssueSelector.JqlIssueSelector.DisplayName=Selettore JQL\nJiraVersionCreatorBuilder.DisplayName=Jira: Crea una nuova versione\nJiraIssueFieldUpdater.DisplayName=Jira: Problema aggiornamento campo personalizzato\nJiraIssueFieldUpdater.NoIssueFieldID=Numero ID campo richiesto\nJiraIssueFieldUpdater.NotAtIssueFieldID=Non \\u00e8 un ID campo di errore\nIssueFieldUpdaterStep.UpdatingIssue=[Jira][IssueFieldUpdaterStep] Aggiornamento del problema {0}\nFailedToUpdateIssue=[Jira] non \\u00e8 stato possibile aggiornare il problema {0}.\nFailedToUpdateIssueWithCarryOver=[Jira] non \\u00e8 stato possibile aggiornare il problema {0}. Portare alla prossima costruzione.\nUpdatingIssue=[Jira] Aggiornamento del problema {0}\nFailedToConnect=[Jira] non \\u00e8 riuscito a connettersi a Jira\nNoJenkinsUrl=[Jira] Jenkins URL non \\u00e8 ancora configurato. Vai alla configurazione di sistema per impostare questo valore\nNoJiraSite=[Jira] Nessun sito Jira \\u00e8 configurato per questo progetto. Deve essere un errore di configurazione del progetto\nNoRemoteAccess=[Jira] La configurazione di sistema non consente l'accesso remoto a Jira\nErrorCommentingIssues=[Jira] Impossibile commentare alcuni problemi: {0}\nJiraSite.threadExecutorMinimunSize = La dimensione dell'esecutore del thread deve essere almeno {0} (sono raccomandati valori superiori)\nJiraSite.timeoutMinimunValue = Il timeout della connessione deve essere almeno {0}\nJiraSite.readTimeoutMinimunValue = Il timeout della connessione deve essere almeno {0}\n"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/Messages_ja.properties",
    "content": "JiraProjectProperty.DisplayName=\\u9023\\u643A\\u3059\\u308BJira\nJiraProjectProperty.JiraUrlMandatory=Jira\\u306EURL\\u306F\\u5FC5\\u9808\\u3067\\u3059\\u3002\nJiraProjectProperty.NotAJiraUrl=\\u6B63\\u3057\\u3044URL\\u3067\\u3059\\u304C\\u3001Jira\\u306EURL\\u3067\\u306F\\u306A\\u3044\\u3088\\u3046\\u3067\\u3059\\u3002\nJiraBuildAction.DisplayName=Jira issues\nFailedToConnect=Jira\\u306B\\u63A5\\u7D9A\\u3067\\u304D\\u307E\\u305B\\u3093\\u3067\\u3057\\u305F\\u3002\nNoJenkinsUrl=Jenkins URL\\u304C\\u307E\\u3060\\u8A2D\\u5B9A\\u3055\\u308C\\u3066\\u3044\\u307E\\u305B\\u3093\\u3002\\u30B7\\u30B9\\u30C6\\u30E0\\u306E\\u8A2D\\u5B9A\\u753B\\u9762\\u3067\\u3001Jenkins URL\\u3092\\u8A2D\\u5B9A\\u3057\\u3066\\u304F\\u3060\\u3055\\u3044\\u3002\nNoJiraSite=\\u3053\\u306E\\u30D7\\u30ED\\u30B8\\u30A7\\u30AF\\u30C8\\u3067\\u306FJira\\u30B5\\u30A4\\u30C8\\u304C\\u8A2D\\u5B9A\\u3055\\u308C\\u3066\\u3044\\u307E\\u305B\\u3093\\u3002\\u30D7\\u30ED\\u30B8\\u30A7\\u30AF\\u30C8\\u306E\\u8A2D\\u5B9A\\u306B\\u30A8\\u30E9\\u30FC\\u304C\\u3042\\u308A\\u307E\\u3059\\u3002\nNoRemoteAccess=\\u30B7\\u30B9\\u30C6\\u30E0\\u306E\\u8A2D\\u5B9A\\u3067\\u3001\\u30EA\\u30E2\\u30FC\\u30C8\\u306EJira\\u3078\\u306E\\u30A2\\u30AF\\u30BB\\u30B9\\u304C\\u8A31\\u53EF\\u3055\\u308C\\u3066\\u3044\\u307E\\u305B\\u3093\\u3002\n"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/Messages_pl.properties",
    "content": "JiraIssueUpdater.DisplayName=Jira: Aktualizuj powi\\u0105zane zg\\u0142oszenia\nJiraFolderProperty.DisplayName=Powi\\u0105zana instancja Jira\nJiraProjectProperty.DisplayName=Powi\\u0105zana instancja Jira\nJiraProjectProperty.JiraUrlMandatory=Adres Jira URL jest wymagany\nJiraProjectProperty.NotAJiraUrl=To jest poprawny URL, ale nie wygl\\u0105da jak adres serwera Jira\nJiraProjectProperty.NoWsdlAvailable=\\u017badne WSDL nie jest dost\\u0119pne, czy obs\\u0142uga SOAP zosta\\u0142a w\\u0142\\u0105czona?\nJiraBuildAction.DisplayName=Zg\\u0142oszenia Jira\nDefaultIssueSelector.DisplayName=Domy\\u015blny selektor\nPerforceJobIssueSelector.DisplayName=Selektor Perforce\nP4JobIssueSelector.DisplayName=Selektor Perforce (P4)\nJiraReleaseVersionBuilder.DisplayName=Jira: Oznacz wersj\\u0119 jako wydan\\u0105\nJiraReleaseVersionMigrator.DisplayName=Jira: Przenie\\u015b zg\\u0142oszenia pasuj\\u0105ce do JQL do okre\\u015blonej wersji\nJiraIssueUpdateBuilder.DisplayName=Jira: Uruchom proces dla zg\\u0142osze\\u0144\nJiraIssueUpdateBuilder.NoJqlSearch=Ustaw JQL u\\u017cywany do znalezienia zg\\u0142osze\\u0144 do aktualizacji.\nJiraIssueUpdateBuilder.NoWorkflowAction=Wyb\\u00f3r akcji jest wymagany.\nJiraIssueUpdateBuilder.UpdatingWithAction=[Jira] Aktualizowanie zg\\u0142osze\\u0144 przy u\\u017cyciu akcji {0}.\nJiraIssueUpdateBuilder.Failed=[Jira] Wyst\\u0105pi\\u0142 b\\u0142\\u0105d podczas procesowania zg\\u0142osze\\u0144:\nJiraIssueUpdateBuilder.UnknownWorkflowAction=[Jira] Nie mo\\u017cna zaktualizowa\\u0107 zg\\u0142oszenia {0}: nieprawid\\u0142owa akcja \"{1}\". By\\u0107 mo\\u017ce u\\u017cytkownik Jenkins nie ma uprawnie\\u0144 do wykonywania dzia\\u0142a\\u0144 w Jira?\nJiraIssueUpdateBuilder.SomeIssuesFailed=[Jira] Nie uda\\u0142o si\\u0119 zaktualizowa\\u0107 co najmniej jednego problemu. Zobacz log powy\\u017cej, aby uzyska\\u0107 wi\\u0119cej informacji.\nJiraVersionCreator.DisplayName=[Jira] Utw\\u00f3rz now\\u0105 wersj\\u0119\nVersionReleaser.AlreadyReleased=[Jira] Wersja {0} jest ju\\u017c wydana w projekcie {1} - brak akcji.\nVersionReleaser.MarkingReleased=[Jira] Oznaczanie wersji {0} w projekcie {1} jako wydana.\nJiraVersionCreator.VersionExists=[Jira] Wersja o nazwie {0} ju\\u017c istnieje w projekcie {1} - brak akcji.\nJiraVersionCreator.CreatingVersion=[Jira] Tworzenie wersji {0} w projekcie {1}.\nJiraEnvironmentVariableBuilder.DisplayName=[Jira] Dodaj powi\\u0105zane zmienne \\u015brodowiskowe do budowania\nJiraEnvironmentVariableBuilder.NoJiraSite=[Jira] \\u017badna strona nie jest skonfigurowana dla tego projektu. To musi by\\u0107 b\\u0142\\u0105d konfiguracji projektu\nJiraEnvironmentVariableBuilder.Updating=[Jira] Ustawiam {0} na {1}.\nCommentStep.Descriptor.DisplayName=Jira: Dodaj komentarz do zg\\u0142osze\\u0144\nSearchIssuesStep.Descriptor.DisplayName=Jira: Szukaj zg\\u0142osze\\u0144\nIssueSelectorStep.Descriptor.DisplayName=Jira: Selektor zg\\u0142osze\\u0144\nJiraCreateIssueNotifier.DisplayName=Jira: Utw\\u00f3rz zg\\u0142oszenie\nIssueSelector.ExplicitIssueSelector.DisplayName=Selektor wybi\\u00f3rczy\nIssueSelector.JqlIssueSelector.DisplayName=[Jira] Selektor JQL\nJiraVersionCreatorBuilder.DisplayName=[Jira] Utw\\u00f3rz now\\u0105 wersj\\u0119\nJiraIssueFieldUpdater.DisplayName=Jira: Aktualizacja p\\u00f3l niestandardowych\nJiraIssueFieldUpdater.NoIssueFieldID=ID pola niestandardowego jest wymagany\nJiraIssueFieldUpdater.NotAtIssueFieldID=To nie jest ID pola niestandardowego\nIssueFieldUpdaterStep.UpdatingIssue=[Jira][IssueFieldUpdaterStep] Aktualizacja zg\\u0142oszenia {0}\nFailedToUpdateIssue=[Jira] Nie uda\\u0142o si\\u0119 zaktualizowa\\u0107 zg\\u0142oszenia {0}.\nFailedToUpdateIssueWithCarryOver=[Jira] Nie uda\\u0142o si\\u0119 zaktualizowa\\u0107 problemu {0}. Przeniesienie do nast\\u0119pnego build'u.\nUpdatingIssue=[Jira] Aktualizacja zg\\u0142oszenia {0}\nFailedToConnect=[Jira] Nie uda\\u0142o si\\u0119 po\\u0142\\u0105czy\\u0107 z serwerem\nNoJenkinsUrl=[Jira] URL Jenkins nie jest skonfigurowany. Przejd\\u017a do konfiguracji systemu, aby ustawi\\u0107 t\\u0119 warto\\u015b\\u0107\nNoJiraSite=[Jira] Instancja Jira nie zosta\\u0142a ustawiona - sprawd\\u017a konfiguracj\\u0119 projektu\nNoRemoteAccess=[Jira] Konfiguracja nie pozwala na zdalny dost\\u0119p do Jira\nErrorCommentingIssues=[Jira] Nie mo\\u017cna skomentowa\\u0107 niekt\\u00f3rych zg\\u0142osze\\u0144: {0}\nJiraSite.threadExecutorMinimunSize = Thread Executor Size musi by\\u0107 co najmniej {0} (zalecane s\\u0105 wy\\u017csze warto\\u015bci)\nJiraSite.timeoutMinimunValue = Limit czasu po\\u0142\\u0105czenia musi wynosi\\u0107 co najmniej {0}\nJiraSite.readTimeoutMinimunValue = Limit czasu odczytu musi wynosi\\u0107 co najmniej {0}\n"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/listissuesparameter/JiraIssueParameterDefinition/config.jelly",
    "content": "<!--\nCopyright 2011-2012 Insider Guides, Inc., MeetMe, Inc.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n     http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n-->\n<!-- this is the page fragment displayed to set up a job -->\n<?jelly escape-by-default='true'?>\n<j:jelly xmlns:j=\"jelly:core\" xmlns:f=\"/lib/form\">\n    <f:entry title=\"${%Name}\" field=\"name\">\n        <f:textbox />\n    </f:entry>\n    <f:entry title=\"${%Description}\" field=\"description\">\n        <f:textbox />\n    </f:entry>\n    <f:entry title=\"${%JQL Search Query}\" field=\"jiraIssueFilter\">\n        <f:textbox />\n    </f:entry>\n        <f:entry title=\"${%Alternate Summary Fields}\" field=\"altSummaryFields\"\n        description=\"Optional comma-separated list of field names\">\n        <f:textbox />\n    </f:entry>\n</j:jelly>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/listissuesparameter/JiraIssueParameterDefinition/help-altSummaryFields.html",
    "content": "<div>\n<p>Optionally, specify a comma-delimited list of fields to use instead of the issue summary as the title in the dropdown.\nFields will be concatenated with spaces.</p>\n<p>Example:</p>\n<strong>Field1,Field2</strong>\n</div>\n"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/listissuesparameter/JiraIssueParameterDefinition/help-jiraIssueFilter.html",
    "content": "<div>\nSpecify the JQL search on Jira instance. For a build, Jenkins will run this query, populate a drop-down list box,\nthen ask the user to select one.\n</div>\n"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/listissuesparameter/JiraIssueParameterDefinition/index.jelly",
    "content": "<!--\nCopyright 2011-2012 Insider Guides, Inc., MeetMe, Inc.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n     http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n-->\n<!-- this is the page fragment displayed when triggering a new build -->\n<?jelly escape-by-default='true'?>\n<j:jelly xmlns:j=\"jelly:core\" xmlns:f=\"/lib/form\">\n    <j:set var=\"escapeEntryTitleAndDescription\" value=\"false\"/>\n    <j:set var=\"descriptor\" value=\"${it.descriptor}\"/>\n    <f:entry field=\"value\" title=\"${h.escape(it.name)}\" description=\"${it.formattedDescription}\">\n        <div name=\"parameter\">\n            <input type=\"hidden\" name=\"name\" value=\"${it.name}\"/>\n            <f:select/>\n        </div>\n    </f:entry>\n</j:jelly>\n"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/listissuesparameter/JiraIssueParameterValue/value.jelly",
    "content": "<!--\nCopyright 2011-2012 Insider Guides, Inc., MeetMe, Inc.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n     http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n-->\n<?jelly escape-by-default='true'?>\n<j:jelly xmlns:j=\"jelly:core\" xmlns:f=\"/lib/form\">\n  <j:set var=\"escapeEntryTitleAndDescription\" value=\"false\"/>\n  <f:entry title=\"${h.escape(it.name)}\">\n    <j:set var=\"readOnlyMode\" value=\"true\"/>\n    <f:textbox name=\"${it.name}\" value=\"${it.value}\"/>\n  </f:entry>\n</j:jelly>\n"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/pipeline/CommentStep/config.jelly",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<?jelly escape-by-default='true'?>\n<j:jelly xmlns:j=\"jelly:core\" xmlns:f=\"/lib/form\">\n    <f:entry field=\"issueKey\" title=\"Issue key\">\n        <f:textbox />\n    </f:entry>\n    <f:entry field=\"body\" title=\"Comment body\">\n        <f:textbox />\n    </f:entry>    \n</j:jelly>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/pipeline/IssueFieldUpdateStep/config.jelly",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<?jelly escape-by-default='true'?>\n<j:jelly xmlns:j=\"jelly:core\" xmlns:f=\"/lib/form\">\n    <f:dropdownDescriptorSelector field=\"issueSelector\" title=\"${%Issue selector}\"/>\n    <f:entry field=\"fieldId\" title=\"Custom field ID\">\n        <f:textbox />\n    </f:entry>\n    <f:entry field=\"fieldValue\" title=\"Field value\">\n        <f:textbox />\n    </f:entry>    \n</j:jelly>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/pipeline/IssueFieldUpdateStep/help-fieldId.html",
    "content": "<div>\n    Issue custom field ID. For example, 10100\n</div>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/pipeline/IssueFieldUpdateStep/help.html",
    "content": "<div>\n    Updates custom Jira issue field.\n</div>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/pipeline/IssueSelectorStep/config.jelly",
    "content": "<?jelly escape-by-default='true'?>\n<j:jelly xmlns:j=\"jelly:core\" xmlns:f=\"/lib/form\">\n    <f:dropdownDescriptorSelector field=\"issueSelector\" title=\"IssueSelector\" descriptors=\"${descriptor.applicableDescriptors}\"/>\n</j:jelly>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/pipeline/SearchIssuesStep/config.jelly",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<?jelly escape-by-default='true'?>\n<j:jelly xmlns:j=\"jelly:core\" xmlns:f=\"/lib/form\">\n    <f:entry field=\"jql\" title=\"JQL query\">\n        <f:textbox />\n    </f:entry>\n</j:jelly>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/selector/DefaultIssueSelector/config.jelly",
    "content": "<?jelly escape-by-default='true'?>\n<j:jelly xmlns:j=\"jelly:core\" xmlns:f=\"/lib/form\">\n    <f:block>\n        Selects all issues found in change lists or parameters\n        of current build or in any of changed dependencies.\n        Selection is using globally configured pattern that is evaluated against commit message.\n    </f:block>\n</j:jelly>\n"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/selector/ExplicitIssueSelector/config.jelly",
    "content": "<?jelly escape-by-default='true'?>\n<j:jelly xmlns:j=\"jelly:core\" xmlns:f=\"/lib/form\">\n    <f:entry field=\"issueKeys\" title=\"Jira Issues List of keys\">\n        <f:textbox />\n    </f:entry>\n</j:jelly>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/selector/JqlIssueSelector/config.jelly",
    "content": "<?jelly escape-by-default='true'?>\n<j:jelly xmlns:j=\"jelly:core\" xmlns:f=\"/lib/form\">\n    <f:entry field=\"jql\" title=\"JQL\">\n        <f:textbox />\n    </f:entry>\n</j:jelly>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/selector/perforce/P4JobIssueSelector/config.jelly",
    "content": "<?jelly escape-by-default='true'?>\n<j:jelly xmlns:j=\"jelly:core\" xmlns:f=\"/lib/form\">\n    <f:block>\n        Selects jobs associated with Perforce change lists or parameters of current build or in any of changed dependencies.\n        It works with Perforce Software (P4) SCM plugin. \n    </f:block>\n</j:jelly>\n"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/selector/perforce/PerforceJobIssueSelector/config.jelly",
    "content": "<?jelly escape-by-default='true'?>\n<j:jelly xmlns:j=\"jelly:core\" xmlns:f=\"/lib/form\">\n    <f:block>\n        Selects jobs associated with Perforce change lists or parameters of current build or in any of changed dependencies.\n        It works with Perforce SCM plugin. \n    </f:block>\n</j:jelly>\n"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/versionparameter/JiraVersionParameterDefinition/config.jelly",
    "content": "<!-- this is the page fragment displayed to set up a job -->\n<?jelly escape-by-default='true'?>\n<j:jelly xmlns:j=\"jelly:core\" xmlns:f=\"/lib/form\">\n    <f:entry title=\"${%Name}\" field=\"name\">\n        <f:textbox />\n    </f:entry>\n    <f:entry title=\"${%Description}\" field=\"description\">\n        <f:textbox />\n    </f:entry>\n    <f:entry title=\"${%Project Key}\" field=\"jiraProjectKey\">\n        <f:textbox />\n    </f:entry>\n    <f:entry title=\"${%Show Released Versions}\" field=\"jiraShowReleased\">\n        <f:checkbox />\n    </f:entry>\n    <f:entry title=\"${%Show Unreleased Versions}\" field=\"jiraShowUnreleased\">\n        <f:checkbox />\n    </f:entry>\n\t<f:entry title=\"${%Show Archived Versions}\" field=\"jiraShowArchived\">\n        <f:checkbox />\n    </f:entry>\n\t<f:entry title=\"${%Release Name Pattern}\" field=\"jiraReleasePattern\">\n        <f:textbox />\n    </f:entry>\n</j:jelly>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/versionparameter/JiraVersionParameterDefinition/help-jiraProjectKey.html",
    "content": "<div>\n<p>Specify the project key. A project key is the all capitals part before the issue number in Jira.</p>\n<p>(<strong>EXAMPLE</strong>-100)</p>\n</div>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/versionparameter/JiraVersionParameterDefinition/help-jiraReleasePattern.html",
    "content": "<div>\n<p>Specify a regular expression which release names have to match to be listed. Leave this blank to match all issues.</p>\n<p>Example:</p>\n<strong>v[0-9]+([.][0-9]+)+</strong> will match v1.0.1, v123, v12.0.1\n</div>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/versionparameter/JiraVersionParameterDefinition/help-jiraShowReleased.html",
    "content": "<div>\n    All versions are displayed by default when all checkboxes are unchecked.\n</div>"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/versionparameter/JiraVersionParameterDefinition/index.jelly",
    "content": "<!-- this is the page fragment displayed when triggering a new build -->\n<?jelly escape-by-default='true'?>\n<j:jelly xmlns:j=\"jelly:core\" xmlns:f=\"/lib/form\">\n    <j:set var=\"escapeEntryTitleAndDescription\" value=\"false\"/>\n    <j:set var=\"descriptor\" value=\"${it.descriptor}\"/>\n    <f:entry field=\"version\" title=\"${h.escape(it.name)}\" description=\"${it.formattedDescription}\">\n        <div name=\"parameter\">\n            <input type=\"hidden\" name=\"name\" value=\"${it.name}\"/>\n            <f:select/>\n        </div>\n    </f:entry>\n</j:jelly>\n"
  },
  {
    "path": "src/main/resources/hudson/plugins/jira/versionparameter/JiraVersionParameterValue/value.jelly",
    "content": "<?jelly escape-by-default='true'?>\n<j:jelly xmlns:j=\"jelly:core\" xmlns:f=\"/lib/form\">\n  <j:set var=\"escapeEntryTitleAndDescription\" value=\"false\"/>\n  <f:entry title=\"${h.escape(it.name)}\">\n    <j:set var=\"readOnlyMode\" value=\"true\"/>\n    <f:textbox name=\"${it.name}\" value=\"${it.version}\"/>\n  </f:entry>\n</j:jelly>\n"
  },
  {
    "path": "src/main/resources/index.jelly",
    "content": "<?jelly escape-by-default='true'?>\n<div>\n  This plugin integrates Jenkins to <a href=\"http://www.atlassian.com/software/jira/\">Atlassian Jira</a>.\n</div>\n"
  },
  {
    "path": "src/main/webapp/help-jira-create-issue.html",
    "content": "<div>\n    Creates a Jira issue in the project with the given key. <br />\n    <ul>\n        <li> If current build failed, checks if issue was already created. If yes, adds a comment, if not, creates a new Jira issue.</li>\n        <li> If current build passed, checks if issue from p.1 is Closed or Done. If not, adds a comment.</li>\n    </ul>\n</div>\n"
  },
  {
    "path": "src/main/webapp/help-release-migrate.html",
    "content": "<div>\nMigrates a set of Jira Issues that match a JQL query to a new fix version.\n</div>"
  },
  {
    "path": "src/main/webapp/help-release.html",
    "content": "<div>\nModify the given version in Jira and set it to Released status.\n</div>"
  },
  {
    "path": "src/main/webapp/help-version-create.html",
    "content": "<div>\n    Creates a new version in Jira in the project with the given key. By default, if the version already exists, the build will fail. This behavior can be changed by unchecking the \"Fail if version already exists\" option.\n</div>\n"
  },
  {
    "path": "src/main/webapp/help.html",
    "content": "<div>\n  This causes Jenkins to remotely login to Jira (via its REST API) and\n  post a comment with the build number which integrates the given change.\n</div>\n"
  },
  {
    "path": "src/main/webapp/help_de.html",
    "content": "<div>\r\n  Lässt Jenkins einen Kommentar mit der Build-Nummer der Änderung zum zugehörigen Jira Issue hinzufügen.\r\n</div>\r\n"
  },
  {
    "path": "src/main/webapp/help_fr.html",
    "content": "<div>\n  Ce plugin va se connecter &#224; Jira (via l'API REST) et\n  ajouter un commentaire avec le num&#233;ro de \"build\" Jenkins qui contient le changement.\n</div>\n"
  },
  {
    "path": "src/main/webapp/help_ja.html",
    "content": "<div>\n  JenkinsがREST API経由でJiraにログインして、変更を統合したビルド番号とともにコメントをポストします。\n</div>\n"
  },
  {
    "path": "src/spotbugs/excludesFilter.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<FindBugsFilter>\n  <!--\n    Exclusions in this section have been triaged and determined to be\n    false positives.\n  -->\n  <Match>\n    <!-- Message string names are wrong style for Java but that's how Jenkins does it -->\n    <Bug pattern=\"NM_METHOD_NAMING_CONVENTION\" />\n    <Class name=\"hudson.plugins.jira.Messages\" />\n    <Or>\n      <Method name=\"ErrorCommentingIssues\" />\n      <Method name=\"FailedToConnect\" />\n      <Method name=\"FailedToUpdateIssue\" />\n      <Method name=\"FailedToUpdateIssueWithCarryOver\" />\n      <Method name=\"NoJenkinsUrl\" />\n      <Method name=\"NoJiraSite\" />\n      <Method name=\"NoRemoteAccess\" />\n      <Method name=\"UpdatingIssue\" />\n    </Or>\n  </Match>\n  <Match>\n    <Bug pattern=\"MS_SHOULD_BE_FINAL\" />\n    <Class name=\"hudson.plugins.jira.JiraMailAddressResolver\" />\n    <Field name=\"disabled\" />\n  </Match>\n\n  <!--\n    Here lies technical debt. Exclusions in this section have not yet\n    been triaged. When working on this section, pick an exclusion to\n    triage, then:\n\n    - Add a @SuppressFBWarnings(value = \"[...]\", justification = \"[...]\")\n      annotation if it is a false positive.  Indicate the reason why\n      it is a false positive, then remove the exclusion from this\n      section.\n\n    - If it is not a false positive, fix the bug, then remove the\n      exclusion from this section.\n   -->\n  <Match>\n    <Bug pattern=\"NP_PARAMETER_MUST_BE_NONNULL_BUT_MARKED_AS_NULLABLE\" />\n    <Class name=\"hudson.plugins.jira.JiraReleaseVersionUpdaterBuilder$DescriptorImpl\" />\n  </Match>\n  <Match>\n    <Bug pattern=\"LI_LAZY_INIT_STATIC\" />\n    <Class name=\"hudson.plugins.jira.JiraSite\" />\n    <Field name=\"executorService\" />\n  </Match>\n  <Match>\n    <Bug pattern=\"NP_NONNULL_PARAM_VIOLATION\" />\n    <Class name=\"hudson.plugins.jira.JiraSession\" />\n    <Method name=\"createIssue\" />\n  </Match>\n  <Match>\n    <Bug pattern=\"NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE\" />\n    <Class name=\"hudson.plugins.jira.Updater\" />\n    <Method name=\"perform\" />\n  </Match>\n  <Match>\n    <Bug pattern=\"NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE\" />\n    <Class name=\"hudson.plugins.jira.Updater\" />\n    <Method name=\"perform\" />\n  </Match>\n</FindBugsFilter>\n"
  },
  {
    "path": "src/test/java/JiraConfig.java",
    "content": "import java.util.ResourceBundle;\n\npublic final class JiraConfig {\n\n    private static final ResourceBundle CONFIG = ResourceBundle.getBundle(\"jira\");\n\n    public static String getUrl() {\n        return CONFIG.getString(\"url\");\n    }\n\n    public static String getUsername() {\n        return CONFIG.getString(\"username\");\n    }\n\n    public static String getPassword() {\n        return CONFIG.getString(\"password\");\n    }\n\n    public static String getToken() {\n        return CONFIG.getString(\"token\");\n    }\n}\n"
  },
  {
    "path": "src/test/java/JiraTester.java",
    "content": "import com.atlassian.jira.rest.client.api.domain.Component;\nimport com.atlassian.jira.rest.client.api.domain.Issue;\nimport com.atlassian.jira.rest.client.api.domain.IssueType;\nimport com.atlassian.jira.rest.client.api.domain.Status;\nimport com.atlassian.jira.rest.client.api.domain.Transition;\nimport com.atlassian.jira.rest.client.api.domain.User;\nimport hudson.plugins.jira.JiraRestService;\nimport hudson.plugins.jira.JiraSite;\nimport hudson.plugins.jira.JiraSite.ExtendedAsynchronousJiraRestClientFactory;\nimport hudson.plugins.jira.extension.ExtendedJiraRestClient;\nimport hudson.plugins.jira.extension.ExtendedVersion;\nimport java.net.URI;\nimport java.net.URL;\nimport java.util.List;\n\n/**\n * Test bed to play with Jira.\n *\n * @author Kohsuke Kawaguchi\n */\npublic class JiraTester {\n    public static void main(String[] args) throws Exception {\n\n        final URI uri = new URL(JiraConfig.getUrl()).toURI();\n        final ExtendedJiraRestClient jiraRestClient = new ExtendedAsynchronousJiraRestClientFactory()\n                .createWithBasicHttpAuthentication(uri, JiraConfig.getUsername(), JiraConfig.getPassword());\n\n        final JiraRestService restService = new JiraRestService(\n                uri, jiraRestClient, JiraConfig.getUsername(), JiraConfig.getPassword(), JiraSite.DEFAULT_TIMEOUT);\n\n        final String projectKey = \"TESTPROJECT\";\n        final String issueId = \"TESTPROJECT-425\";\n        final Integer actionId = 21;\n\n        final Issue issue = restService.getIssue(issueId);\n        System.out.println(\"issue:\" + issue);\n\n        final List<Transition> availableActions = restService.getAvailableActions(issueId);\n        for (Transition action : availableActions) {\n            System.out.println(\"Action:\" + action);\n        }\n\n        for (IssueType issueType : restService.getIssueTypes()) {\n            System.out.println(\" issue type: \" + issueType);\n        }\n\n        //        restService.addVersion(\"TESTPROJECT\", \"0.0.2\");\n\n        final List<Component> components = restService.getComponents(projectKey);\n        for (Component component : components) {\n            System.out.println(\"component: \" + component);\n        }\n\n        //        BasicComponent backendComponent = null;\n        //        final Iterable<BasicComponent> components1 = Lists.newArrayList(backendComponent);\n        //        restService.createIssue(\"TESTPROJECT\", \"This is a test issue created using Jira jenkins plugin. Please\n        // ignore it.\", \"TESTUSER\", components1, \"test issue from Jira jenkins plugin\");\n\n        final List<Issue> searchResults = restService.getIssuesFromJqlSearch(\"project = \\\"TESTPROJECT\\\"\", 100);\n        for (Issue searchResult : searchResults) {\n            System.out.println(\"JQL search result: \" + searchResult);\n        }\n\n        final List<String> projectsKeys = restService.getProjectsKeys();\n        for (String projectsKey : projectsKeys) {\n            System.out.println(\"project key: \" + projectsKey);\n        }\n\n        final List<Status> statuses = restService.getStatuses();\n        for (Status status : statuses) {\n            System.out.println(\"status:\" + status);\n        }\n\n        final User user = restService.getUser(\"TESTUSER\");\n        System.out.println(\"user: \" + user);\n\n        final List<ExtendedVersion> versions = restService.getVersions(projectKey);\n        for (ExtendedVersion version : versions) {\n            System.out.println(\"version: \" + version);\n        }\n\n        //        Version releaseVersion = new Version(version.getSelf(), version.getId(), version.getName(),\n        //                version.getDescription(), version.isArchived(), true, new DateTime());\n        //        System.out.println(\" >>>> release version 0.0.2\");\n        //        restService.releaseVersion(\"TESTPROJECT\", releaseVersion);\n\n        //        System.out.println(\" >>> update issue TESTPROJECT-425\");\n        //        restService.updateIssue(issueId, Collections.singletonList(releaseVersion));\n\n        //        final Issue updatedIssue = restService.progressWorkflowAction(issueId, actionId);\n        //        System.out.println(\"Updated issue:\" + updatedIssue);\n\n        for (int i = 0; i < 10; i++) {\n            callUniq(restService);\n        }\n\n        for (int i = 0; i < 10; i++) {\n            callDuplicate(restService);\n        }\n    }\n\n    private static void callUniq(final JiraRestService restService) throws Exception {\n        long start = System.currentTimeMillis();\n        List<Issue> issues = restService.getIssuesFromJqlSearch(\n                \"key in ('JENKINS-53320','JENKINS-51057')\", JiraSite.MAX_ALLOWED_ISSUES_FROM_JQL);\n        long end = System.currentTimeMillis();\n        System.out.println(\"time uniq \" + (end - start));\n    }\n\n    private static void callDuplicate(final JiraRestService restService) throws Exception {\n        long start = System.currentTimeMillis();\n        List<Issue> issues = restService.getIssuesFromJqlSearch(\n                \"key in ('JENKINS-53320','JENKINS-53320','JENKINS-53320','JENKINS-53320','JENKINS-53320','JENKINS-51057','JENKINS-51057','JENKINS-51057','JENKINS-51057','JENKINS-51057')\",\n                JiraSite.MAX_ALLOWED_ISSUES_FROM_JQL);\n        long end = System.currentTimeMillis();\n        System.out.println(\"time duplicate \" + (end - start));\n    }\n}\n"
  },
  {
    "path": "src/test/java/JiraTesterBearerAuth.java",
    "content": "import com.atlassian.jira.rest.client.api.domain.Component;\nimport com.atlassian.jira.rest.client.api.domain.Issue;\nimport com.atlassian.jira.rest.client.api.domain.IssueType;\nimport com.atlassian.jira.rest.client.api.domain.Status;\nimport com.atlassian.jira.rest.client.api.domain.Transition;\nimport com.atlassian.jira.rest.client.api.domain.User;\nimport hudson.plugins.jira.JiraRestService;\nimport hudson.plugins.jira.JiraSite;\nimport hudson.plugins.jira.JiraSite.ExtendedAsynchronousJiraRestClientFactory;\nimport hudson.plugins.jira.auth.BearerHttpAuthenticationHandler;\nimport hudson.plugins.jira.extension.ExtendedJiraRestClient;\nimport hudson.plugins.jira.extension.ExtendedVersion;\nimport java.net.URI;\nimport java.net.URL;\nimport java.util.List;\n\n/**\n * Test bed to play with Jira.\n *\n * @author Elia Bracci\n */\npublic class JiraTesterBearerAuth {\n    public static void main(String[] args) throws Exception {\n\n        final URI uri = new URL(JiraConfig.getUrl()).toURI();\n        final BearerHttpAuthenticationHandler handler = new BearerHttpAuthenticationHandler(JiraConfig.getToken());\n        final ExtendedJiraRestClient jiraRestClient =\n                new ExtendedAsynchronousJiraRestClientFactory().createWithAuthenticationHandler(uri, handler);\n\n        final JiraRestService restService =\n                new JiraRestService(uri, jiraRestClient, JiraConfig.getToken(), JiraSite.DEFAULT_TIMEOUT);\n\n        final String projectKey = \"TESTPROJECT\";\n        final String issueId = \"TESTPROJECT-425\";\n        final Integer actionId = 21;\n\n        final Issue issue = restService.getIssue(issueId);\n        System.out.println(\"issue:\" + issue);\n\n        final List<Transition> availableActions = restService.getAvailableActions(issueId);\n        for (Transition action : availableActions) {\n            System.out.println(\"Action:\" + action);\n        }\n\n        for (IssueType issueType : restService.getIssueTypes()) {\n            System.out.println(\" issue type: \" + issueType);\n        }\n\n        //        restService.addVersion(\"TESTPROJECT\", \"0.0.2\");\n\n        final List<Component> components = restService.getComponents(projectKey);\n        for (Component component : components) {\n            System.out.println(\"component: \" + component);\n        }\n\n        //        BasicComponent backendComponent = null;\n        //        final Iterable<BasicComponent> components1 = Lists.newArrayList(backendComponent);\n        //        restService.createIssue(\"TESTPROJECT\", \"This is a test issue created using Jira jenkins plugin. Please\n        // ignore it.\", \"TESTUSER\", components1, \"test issue from Jira jenkins plugin\");\n\n        final List<Issue> searchResults = restService.getIssuesFromJqlSearch(\"project = \\\"TESTPROJECT\\\"\", 100);\n        for (Issue searchResult : searchResults) {\n            System.out.println(\"JQL search result: \" + searchResult);\n        }\n\n        final List<String> projectsKeys = restService.getProjectsKeys();\n        for (String projectsKey : projectsKeys) {\n            System.out.println(\"project key: \" + projectsKey);\n        }\n\n        final List<Status> statuses = restService.getStatuses();\n        for (Status status : statuses) {\n            System.out.println(\"status:\" + status);\n        }\n\n        final User user = restService.getUser(\"TESTUSER\");\n        System.out.println(\"user: \" + user);\n\n        final List<ExtendedVersion> versions = restService.getVersions(projectKey);\n        for (ExtendedVersion version : versions) {\n            System.out.println(\"version: \" + version);\n        }\n\n        //        Version releaseVersion = new Version(version.getSelf(), version.getId(), version.getName(),\n        //                version.getDescription(), version.isArchived(), true, new DateTime());\n        //        System.out.println(\" >>>> release version 0.0.2\");\n        //        restService.releaseVersion(\"TESTPROJECT\", releaseVersion);\n\n        //        System.out.println(\" >>> update issue TESTPROJECT-425\");\n        //        restService.updateIssue(issueId, Collections.singletonList(releaseVersion));\n\n        //        final Issue updatedIssue = restService.progressWorkflowAction(issueId, actionId);\n        //        System.out.println(\"Updated issue:\" + updatedIssue);\n\n        for (int i = 0; i < 10; i++) {\n            callUniq(restService);\n        }\n\n        for (int i = 0; i < 10; i++) {\n            callDuplicate(restService);\n        }\n    }\n\n    private static void callUniq(final JiraRestService restService) throws Exception {\n        long start = System.currentTimeMillis();\n        List<Issue> issues = restService.getIssuesFromJqlSearch(\"key in ('JENKINS-53320','JENKINS-51057')\", 100);\n        long end = System.currentTimeMillis();\n        System.out.println(\"time uniq \" + (end - start));\n    }\n\n    private static void callDuplicate(final JiraRestService restService) throws Exception {\n        long start = System.currentTimeMillis();\n        List<Issue> issues = restService.getIssuesFromJqlSearch(\n                \"key in ('JENKINS-53320','JENKINS-53320','JENKINS-53320','JENKINS-53320','JENKINS-53320','JENKINS-51057','JENKINS-51057','JENKINS-51057','JENKINS-51057','JENKINS-51057')\",\n                100);\n        long end = System.currentTimeMillis();\n        System.out.println(\"time duplicate \" + (end - start));\n    }\n}\n"
  },
  {
    "path": "src/test/java/com/atlassian/httpclient/apache/httpcomponents/ApacheAsyncHttpClientTest.java",
    "content": "package com.atlassian.httpclient.apache.httpcomponents;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport com.atlassian.httpclient.api.Response;\nimport com.atlassian.httpclient.api.factory.HttpClientOptions;\nimport com.atlassian.sal.api.ApplicationProperties;\nimport com.atlassian.sal.api.UrlMode;\nimport com.atlassian.sal.api.executor.ThreadLocalContextManager;\nimport edu.umd.cs.findbugs.annotations.NonNull;\nimport edu.umd.cs.findbugs.annotations.Nullable;\nimport hudson.ProxyConfiguration;\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Path;\nimport java.util.Base64;\nimport java.util.Date;\nimport java.util.Optional;\nimport java.util.concurrent.TimeUnit;\nimport jenkins.model.Jenkins;\nimport org.apache.commons.io.IOUtils;\nimport org.apache.commons.lang3.StringUtils;\nimport org.eclipse.jetty.http.HttpHeader;\nimport org.eclipse.jetty.http.HttpStatus;\nimport org.eclipse.jetty.io.Content;\nimport org.eclipse.jetty.server.ConnectionFactory;\nimport org.eclipse.jetty.server.Handler;\nimport org.eclipse.jetty.server.HttpConnectionFactory;\nimport org.eclipse.jetty.server.Server;\nimport org.eclipse.jetty.server.ServerConnector;\nimport org.eclipse.jetty.util.Callback;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Test;\nimport org.jvnet.hudson.test.JenkinsRule;\nimport org.jvnet.hudson.test.junit.jupiter.WithJenkins;\n\n@WithJenkins\npublic class ApacheAsyncHttpClientTest {\n\n    private final ConnectionFactory connectionFactory = new HttpConnectionFactory();\n\n    private Server server;\n\n    private ServerConnector connector;\n\n    static final String CONTENT_RESPONSE = \"Sounds Good\";\n\n    public void prepare(Handler handler) throws Exception {\n        server = new Server();\n        connector = new ServerConnector(server, connectionFactory);\n        server.addConnector(connector);\n        server.setHandler(handler);\n        server.start();\n    }\n\n    @AfterEach\n    void dispose() throws Exception {\n        if (server != null) {\n            server.stop();\n        }\n    }\n\n    @Test\n    void simple_get(JenkinsRule r) throws Exception {\n        TestHandler testHandler = new TestHandler();\n        prepare(testHandler);\n\n        ApacheAsyncHttpClient httpClient = new ApacheAsyncHttpClient(\n                null, buildApplicationProperties(), new NoOpThreadLocalContextManager(), new HttpClientOptions());\n\n        Response response = httpClient\n                .newRequest(\"http://localhost:\" + connector.getLocalPort() + \"/foo\")\n                .get()\n                .get(10, TimeUnit.SECONDS);\n        assertEquals(200, response.getStatusCode());\n        assertEquals(CONTENT_RESPONSE, IOUtils.toString(response.getEntityStream()));\n    }\n\n    @Test\n    void simple_post(JenkinsRule r) throws Exception {\n        TestHandler testHandler = new TestHandler();\n        prepare(testHandler);\n\n        ApacheAsyncHttpClient httpClient = new ApacheAsyncHttpClient(\n                null, buildApplicationProperties(), new NoOpThreadLocalContextManager(), new HttpClientOptions());\n\n        Response response = httpClient\n                .newRequest(\"http://localhost:\" + connector.getLocalPort() + \"/foo\")\n                .setEntity(\"FOO\")\n                .setContentType(\"text\")\n                .post()\n                .get(10, TimeUnit.SECONDS);\n        assertEquals(200, response.getStatusCode());\n        assertEquals(CONTENT_RESPONSE, IOUtils.toString(response.getEntityStream()));\n        assertEquals(\"FOO\", testHandler.postReceived);\n    }\n\n    @Test\n    void simple_get_with_non_proxy_host(JenkinsRule r) throws Exception {\n        ProxyTestHandler testHandler = new ProxyTestHandler();\n        prepare(testHandler);\n\n        Jenkins.get().proxy =\n                new ProxyConfiguration(\"localhost\", connector.getLocalPort(), \"foo\", \"bar\", \"www.apache.org\");\n\n        ApacheAsyncHttpClient httpClient = new ApacheAsyncHttpClient(\n                null, buildApplicationProperties(), new NoOpThreadLocalContextManager(), new HttpClientOptions());\n\n        Response response = httpClient.newRequest(\"http://www.apache.org\").get().get(30, TimeUnit.SECONDS);\n        assertEquals(200, response.getStatusCode());\n        // assertEquals(CONTENT_RESPONSE, IOUtils.toString(response.getEntityStream()));\n    }\n\n    @Test\n    void simple_get_with_proxy(JenkinsRule r) throws Exception {\n        ProxyTestHandler testHandler = new ProxyTestHandler();\n        prepare(testHandler);\n\n        Jenkins.get().proxy = new ProxyConfiguration(\"localhost\", connector.getLocalPort(), \"foo\", \"bar\");\n\n        ApacheAsyncHttpClient httpClient = new ApacheAsyncHttpClient(\n                null, buildApplicationProperties(), new NoOpThreadLocalContextManager(), new HttpClientOptions());\n\n        Response response = httpClient.newRequest(\"http://jenkins.io\").get().get(30, TimeUnit.SECONDS);\n        assertEquals(200, response.getStatusCode());\n        assertEquals(CONTENT_RESPONSE, IOUtils.toString(response.getEntityStream()));\n    }\n\n    @Test\n    void simple_post_with_proxy(JenkinsRule r) throws Exception {\n        ProxyTestHandler testHandler = new ProxyTestHandler();\n        prepare(testHandler);\n\n        Jenkins.get().proxy = new ProxyConfiguration(\"localhost\", connector.getLocalPort(), \"foo\", \"bar\");\n\n        ApacheAsyncHttpClient httpClient = new ApacheAsyncHttpClient(\n                null, buildApplicationProperties(), new NoOpThreadLocalContextManager(), new HttpClientOptions());\n\n        Response response = httpClient\n                .newRequest(\"http://jenkins.io\")\n                .setEntity(\"FOO\")\n                .setContentType(\"text\")\n                .post()\n                .get(30, TimeUnit.SECONDS);\n        // we are sure to hit the proxy first :-)\n        assertEquals(200, response.getStatusCode());\n        assertEquals(CONTENT_RESPONSE, IOUtils.toString(response.getEntityStream()));\n        assertEquals(\"FOO\", testHandler.postReceived);\n    }\n\n    public static class ProxyTestHandler extends Handler.Abstract {\n\n        String postReceived;\n\n        final String user = \"foo\";\n\n        final String password = \"bar\";\n\n        final String serverHost = \"server\";\n\n        final String realm = \"test_realm\";\n\n        @Override\n        public boolean handle(\n                org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response, Callback callback)\n                throws IOException {\n\n            final String credentials = Base64.getEncoder().encodeToString((user + \":\" + password).getBytes(\"UTF-8\"));\n\n            String authorization = request.getHeaders().get(HttpHeader.PROXY_AUTHORIZATION.asString());\n            if (authorization == null) {\n                response.setStatus(HttpStatus.PROXY_AUTHENTICATION_REQUIRED_407);\n                response.getHeaders().add(HttpHeader.PROXY_AUTHENTICATE.asString(), \"Basic realm=\\\"\" + realm + \"\\\"\");\n                callback.succeeded();\n                return true;\n            } else {\n                String prefix = \"Basic \";\n                if (authorization.startsWith(prefix)) {\n                    String attempt = authorization.substring(prefix.length());\n                    if (!credentials.equals(attempt)) {\n                        callback.succeeded();\n                        return true;\n                    }\n                }\n            }\n\n            if (StringUtils.equalsIgnoreCase(\"post\", request.getMethod())) {\n                postReceived = Content.Source.asString(request, StandardCharsets.UTF_8);\n            }\n            Content.Sink.write(response, true, CONTENT_RESPONSE, callback);\n            return true;\n        }\n    }\n\n    public static class TestHandler extends Handler.Abstract {\n\n        String postReceived;\n\n        @Override\n        public boolean handle(\n                org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response, Callback callback)\n                throws IOException {\n            if (StringUtils.equalsIgnoreCase(\"post\", request.getMethod())) {\n                postReceived = Content.Source.asString(request, StandardCharsets.UTF_8);\n            }\n            Content.Sink.write(response, true, CONTENT_RESPONSE, callback);\n            return true;\n        }\n    }\n\n    private ApplicationProperties buildApplicationProperties() {\n        ApplicationProperties applicationProperties = new ApplicationProperties() {\n            @Override\n            public String getBaseUrl() {\n                return null;\n            }\n\n            @NonNull\n            @Override\n            public String getBaseUrl(UrlMode urlMode) {\n                return null;\n            }\n\n            @NonNull\n            @Override\n            public String getDisplayName() {\n                return \"Foo\";\n            }\n\n            @NonNull\n            @Override\n            public String getPlatformId() {\n                return null;\n            }\n\n            @NonNull\n            @Override\n            public String getVersion() {\n                return \"1\";\n            }\n\n            @NonNull\n            @Override\n            public Date getBuildDate() {\n                return null;\n            }\n\n            @NonNull\n            @Override\n            public String getBuildNumber() {\n                return \"1\";\n            }\n\n            @Nullable\n            @Override\n            public File getHomeDirectory() {\n                return null;\n            }\n\n            @Override\n            public String getPropertyValue(String s) {\n                return null;\n            }\n\n            @NonNull\n            @Override\n            public String getApplicationFileEncoding() {\n                return System.getProperty(\"file.encoding\");\n            }\n\n            @NonNull\n            @Override\n            public Optional<Path> getLocalHomeDirectory() {\n                return Optional.empty();\n            }\n\n            @NonNull\n            @Override\n            public Optional<Path> getSharedHomeDirectory() {\n                return Optional.empty();\n            }\n        };\n        return applicationProperties;\n    }\n\n    private static final class NoOpThreadLocalContextManager<C> implements ThreadLocalContextManager<C> {\n        @Override\n        public C getThreadLocalContext() {\n            return null;\n        }\n\n        @Override\n        public void setThreadLocalContext(C context) {}\n\n        @Override\n        public void clearThreadLocalContext() {}\n    }\n}\n"
  },
  {
    "path": "src/test/java/com/atlassian/httpclient/apache/httpcomponents/CompletableFuturePromiseHttpPromiseAsyncClientTest.java",
    "content": "package com.atlassian.httpclient.apache.httpcomponents;\n\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport com.atlassian.sal.api.executor.ThreadLocalContextManager;\nimport java.io.IOException;\nimport java.util.concurrent.Executor;\nimport java.util.concurrent.Future;\nimport org.apache.http.HttpResponse;\nimport org.apache.http.client.methods.HttpUriRequest;\nimport org.apache.http.concurrent.FutureCallback;\nimport org.apache.http.impl.nio.client.CloseableHttpAsyncClient;\nimport org.apache.http.protocol.HttpContext;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.InjectMocks;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\nimport org.mockito.stubbing.Answer;\n\n@ExtendWith(MockitoExtension.class)\nclass CompletableFuturePromiseHttpPromiseAsyncClientTest {\n\n    @Mock\n    private CloseableHttpAsyncClient client;\n\n    @Mock\n    private ThreadLocalContextManager<Object> threadLocalContextManager;\n\n    @Mock\n    private Executor executor;\n\n    @Mock\n    private HttpUriRequest request;\n\n    @Mock\n    private HttpResponse response;\n\n    @Mock\n    private HttpContext context;\n\n    @InjectMocks\n    private CompletableFuturePromiseHttpPromiseAsyncClient<Object> asyncClient;\n\n    @Test\n    void ensureCloseHttpclientOnCompletion() throws IOException {\n        when(client.execute(eq(request), eq(context), any())).then((Answer<Future<HttpResponse>>) invocation -> {\n            invocation.getArgument(2, FutureCallback.class).completed(response);\n            return mock(Future.class);\n        });\n\n        asyncClient.execute(request, context);\n\n        verify(client).close();\n    }\n\n    @Test\n    void ensureCloseHttpclientOnFailure() throws IOException {\n        when(client.execute(eq(request), eq(context), any())).then((Answer<Future<HttpResponse>>) invocation -> {\n            invocation.getArgument(2, FutureCallback.class).failed(null);\n            return mock(Future.class);\n        });\n\n        asyncClient.execute(request, context);\n\n        verify(client).close();\n    }\n\n    @Test\n    void ensureCloseHttpclientOnCancellation() throws IOException {\n        when(client.execute(eq(request), eq(context), any())).then((Answer<Future<HttpResponse>>) invocation -> {\n            invocation.getArgument(2, FutureCallback.class).cancelled();\n            return mock(Future.class);\n        });\n\n        asyncClient.execute(request, context);\n\n        verify(client).close();\n    }\n}\n"
  },
  {
    "path": "src/test/java/hudson/plugins/jira/BuildListenerResultMethodMock.java",
    "content": "package hudson.plugins.jira;\n\nimport hudson.model.Result;\nimport org.mockito.invocation.InvocationOnMock;\nimport org.mockito.stubbing.Answer;\n\npublic class BuildListenerResultMethodMock implements Answer<Void> {\n\n    private Result result;\n\n    @Override\n    public Void answer(InvocationOnMock invocation) throws Throwable {\n        this.result = invocation.getArgument(0, Result.class);\n        return null;\n    }\n\n    public Result getResult() {\n        return result;\n    }\n}\n"
  },
  {
    "path": "src/test/java/hudson/plugins/jira/ChangingWorkflowTest.java",
    "content": "package hudson.plugins.jira;\n\nimport static org.apache.commons.lang3.RandomStringUtils.randomNumeric;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.hamcrest.Matchers.nullValue;\nimport static org.mockito.ArgumentMatchers.anyBoolean;\nimport static org.mockito.ArgumentMatchers.anyString;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.ArgumentMatchers.isNull;\nimport static org.mockito.Mockito.any;\nimport static org.mockito.Mockito.anyInt;\nimport static org.mockito.Mockito.doReturn;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.spy;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport com.atlassian.jira.rest.client.api.domain.Issue;\nimport com.atlassian.jira.rest.client.api.domain.Transition;\nimport hudson.model.Item;\nimport java.io.PrintStream;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.concurrent.TimeoutException;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\n\n/**\n * User: lanwen\n * Date: 10.09.13\n * Time: 0:57\n */\n@ExtendWith(MockitoExtension.class)\npublic class ChangingWorkflowTest {\n\n    public static final String NON_EMPTY_COMMENT = \"Non empty comment\";\n    private final String ISSUE_JQL = \"jql\";\n    private final String NON_EMPTY_WORKFLOW_LOWERCASE = \"workflow\";\n\n    @Mock\n    private JiraSite site;\n\n    @Mock\n    private JiraRestService restService;\n\n    @Mock\n    private JiraSession mockSession;\n\n    @Mock\n    private Item mockItem;\n\n    private JiraSession spySession;\n\n    @BeforeEach\n    void setupSpy() {\n        spySession = spy(new JiraSession(site, restService));\n    }\n\n    @Test\n    void onGetActionItInvokesServiceMethod() {\n        spySession.getActionIdForIssue(ISSUE_JQL, NON_EMPTY_WORKFLOW_LOWERCASE);\n        verify(restService, times(1)).getAvailableActions(eq(ISSUE_JQL));\n    }\n\n    @Test\n    void getActionIdReturnsNullWhenServiceReturnsNull() {\n        doReturn(null).when(restService).getAvailableActions(ISSUE_JQL);\n        assertThat(spySession.getActionIdForIssue(ISSUE_JQL, NON_EMPTY_WORKFLOW_LOWERCASE), nullValue());\n    }\n\n    @Test\n    void getActionIdIteratesOverAllActionsEvenOneOfNamesIsNull() {\n        Transition action1 = mock(Transition.class);\n        Transition action2 = mock(Transition.class);\n\n        doReturn(null).when(action1).getName();\n        doReturn(\"name\").when(action2).getName();\n\n        doReturn(Arrays.asList(action1, action2)).when(restService).getAvailableActions(ISSUE_JQL);\n        assertThat(spySession.getActionIdForIssue(ISSUE_JQL, NON_EMPTY_WORKFLOW_LOWERCASE), nullValue());\n\n        verify(action1, times(1)).getName();\n        verify(action2, times(2)).getName(); // one for null check, other for equals\n    }\n\n    @Test\n    void getActionIdReturnsNullWhenNullWorkflowUsed() {\n        String workflowAction = null;\n        Transition action1 = mock(Transition.class);\n        when(action1.getName()).thenReturn(\"name\");\n\n        when(restService.getAvailableActions(ISSUE_JQL)).thenReturn(Collections.singletonList(action1));\n        assertThat(spySession.getActionIdForIssue(ISSUE_JQL, workflowAction), nullValue());\n    }\n\n    @Test\n    void getActionIdReturnsIdWhenFoundIgnorecaseWorkflow() {\n        String id = randomNumeric(5);\n        Transition action1 = mock(Transition.class);\n        when(action1.getName()).thenReturn(NON_EMPTY_WORKFLOW_LOWERCASE.toUpperCase());\n        when(restService.getAvailableActions(ISSUE_JQL)).thenReturn(Arrays.asList(action1));\n        when(action1.getId()).thenReturn(Integer.valueOf(id));\n\n        assertThat(\n                spySession.getActionIdForIssue(ISSUE_JQL, NON_EMPTY_WORKFLOW_LOWERCASE), equalTo(Integer.valueOf(id)));\n    }\n\n    @Test\n    void addCommentsOnNonEmptyWorkflowAndNonEmptyComment() throws Exception {\n        when(site.getSession(any(), anyBoolean())).thenCallRealMethod();\n        when(site.getSession(any())).thenCallRealMethod();\n        when(site.createSession(any(), anyBoolean())).thenReturn(mockSession);\n        site.getSession(mockItem);\n\n        when(mockSession.getIssuesFromJqlSearch(anyString())).thenReturn(Arrays.asList(mock(Issue.class)));\n\n        when(site.progressMatchingIssues(anyString(), any(), anyString(), any(PrintStream.class)))\n                .thenCallRealMethod();\n        site.progressMatchingIssues(\n                ISSUE_JQL, NON_EMPTY_WORKFLOW_LOWERCASE, NON_EMPTY_COMMENT, mock(PrintStream.class));\n\n        verify(mockSession, times(1)).addComment(any(), eq(NON_EMPTY_COMMENT), isNull(), isNull());\n        verify(mockSession, times(1)).progressWorkflowAction(any(), anyInt());\n    }\n\n    @Test\n    void addCommentsOnNullWorkflowAndNonEmptyComment() throws Exception {\n        when(site.getSession(any())).thenCallRealMethod();\n        when(site.getSession(any(), anyBoolean())).thenCallRealMethod();\n        when(site.createSession(any(), anyBoolean())).thenReturn(mockSession);\n        site.getSession(mockItem);\n\n        when(mockSession.getIssuesFromJqlSearch(anyString())).thenReturn(Arrays.asList(mock(Issue.class)));\n\n        when(site.progressMatchingIssues(anyString(), any(), anyString(), any(PrintStream.class)))\n                .thenCallRealMethod();\n        site.progressMatchingIssues(ISSUE_JQL, \"\", NON_EMPTY_COMMENT, mock(PrintStream.class));\n\n        verify(mockSession, times(1)).addComment(any(), eq(NON_EMPTY_COMMENT), isNull(), isNull());\n    }\n\n    @Test\n    void dontAddCommentsOnNullWorkflowAndNullComment() throws TimeoutException {\n        site.progressMatchingIssues(ISSUE_JQL, null, null, mock(PrintStream.class));\n        verify(mockSession, never()).addComment(anyString(), anyString(), isNull(), isNull());\n    }\n}\n"
  },
  {
    "path": "src/test/java/hudson/plugins/jira/CliParameterTest.java",
    "content": "package hudson.plugins.jira;\n\nimport static hudson.cli.CLICommandInvoker.Matcher.succeeded;\nimport static org.hamcrest.MatcherAssert.assertThat;\n\nimport hudson.cli.BuildCommand;\nimport hudson.cli.CLICommandInvoker;\nimport hudson.model.FreeStyleProject;\nimport hudson.model.ParametersDefinitionProperty;\nimport hudson.plugins.jira.listissuesparameter.JiraIssueParameterDefinition;\nimport hudson.plugins.jira.versionparameter.JiraVersionParameterDefinition;\nimport java.io.IOException;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.jvnet.hudson.test.JenkinsRule;\nimport org.jvnet.hudson.test.junit.jupiter.WithJenkins;\n\n@WithJenkins\nclass CliParameterTest {\n\n    private JenkinsRule jenkins;\n\n    FreeStyleProject project;\n\n    @BeforeEach\n    void setup(JenkinsRule jenkins) throws IOException {\n        this.jenkins = jenkins;\n        project = jenkins.createFreeStyleProject();\n    }\n\n    @Test\n    void jiraIssueParameterViaCli() throws Exception {\n        project.addProperty(new ParametersDefinitionProperty(\n                new JiraIssueParameterDefinition(\"jiraissue\", \"description\", \"filter\")));\n\n        CLICommandInvoker invoker = new CLICommandInvoker(jenkins, new BuildCommand());\n        CLICommandInvoker.Result result = invoker.invokeWithArgs(project.getName(), \"-s\", \"-p\", \"jiraissue=TEST-1\");\n        assertThat(result, succeeded());\n    }\n\n    @Test\n    void jiraVersionParameterViaCli() throws Exception {\n        project.addProperty(new ParametersDefinitionProperty(new JiraVersionParameterDefinition(\n                \"jiraversion\", \"description\", \"PROJ\", \"RELEASE\", \"true\", \"false\", \"false\")));\n\n        CLICommandInvoker invoker = new CLICommandInvoker(jenkins, new BuildCommand());\n        CLICommandInvoker.Result result = invoker.invokeWithArgs(project.getName(), \"-s\", \"-p\", \"jiraversion=1.0\");\n        assertThat(result, succeeded());\n    }\n}\n"
  },
  {
    "path": "src/test/java/hudson/plugins/jira/ConfigAsCodeTest.java",
    "content": "package hudson.plugins.jira;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.hasSize;\nimport static org.hamcrest.Matchers.is;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\n\nimport io.jenkins.plugins.casc.ConfigurationContext;\nimport io.jenkins.plugins.casc.Configurator;\nimport io.jenkins.plugins.casc.ConfiguratorRegistry;\nimport io.jenkins.plugins.casc.misc.ConfiguredWithCode;\nimport io.jenkins.plugins.casc.misc.JenkinsConfiguredWithCodeRule;\nimport io.jenkins.plugins.casc.misc.junit.jupiter.WithJenkinsConfiguredWithCode;\nimport io.jenkins.plugins.casc.model.CNode;\nimport io.jenkins.plugins.casc.model.Mapping;\nimport java.util.List;\nimport java.util.Objects;\nimport org.junit.jupiter.api.Test;\n\n@WithJenkinsConfiguredWithCode\nclass ConfigAsCodeTest {\n\n    @Test\n    @ConfiguredWithCode(\"multiple-sites.yml\")\n    void shouldSupportConfigurationAsCode(JenkinsConfiguredWithCodeRule r) throws Exception {\n        List<JiraSite> sites = JiraGlobalConfiguration.get().getSites();\n        assertThat(sites, hasSize(2));\n        assertEquals(\n                \"https://issues.jenkins-ci.org/\",\n                Objects.requireNonNull(sites.get(0).getUrl()).toExternalForm());\n        assertEquals(\n                \"https://jira.com/\",\n                Objects.requireNonNull(sites.get(1).getUrl()).toExternalForm());\n    }\n\n    @Test\n    @ConfiguredWithCode(\"single-site.yml\")\n    void shouldExportConfigurationAsCode(JenkinsConfiguredWithCodeRule r) throws Exception {\n        ConfiguratorRegistry registry = ConfiguratorRegistry.get();\n        ConfigurationContext context = new ConfigurationContext(registry);\n        final Configurator c = context.lookupOrFail(JiraGlobalConfiguration.class);\n        final CNode node = c.describe(JiraGlobalConfiguration.get(), context);\n        assertNotNull(node);\n        final Mapping mapping = node.asMapping();\n        Mapping sites = mapping.get(\"sites\").asSequence().get(0).asMapping();\n\n        assertThat(sites.getScalarValue(\"url\"), is(\"https://jira.com/\"));\n    }\n}\n"
  },
  {
    "path": "src/test/java/hudson/plugins/jira/CredentialsHelperTest.java",
    "content": "package hudson.plugins.jira;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.empty;\nimport static org.hamcrest.Matchers.hasSize;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\n\nimport com.cloudbees.plugins.credentials.CredentialsProvider;\nimport com.cloudbees.plugins.credentials.CredentialsScope;\nimport com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials;\nimport com.cloudbees.plugins.credentials.domains.Domain;\nimport com.cloudbees.plugins.credentials.domains.DomainSpecification;\nimport com.cloudbees.plugins.credentials.domains.HostnameSpecification;\nimport com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl;\nimport hudson.model.Descriptor.FormException;\nimport java.io.IOException;\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport java.util.Arrays;\nimport org.junit.jupiter.api.Test;\nimport org.jvnet.hudson.test.JenkinsRule;\nimport org.jvnet.hudson.test.junit.jupiter.WithJenkins;\n\n/**\n * @author Zhenlei Huang\n */\n@WithJenkins\nclass CredentialsHelperTest {\n\n    @Test\n    void lookupSystemCredentials(JenkinsRule r) throws IOException, FormException {\n        assertNull(CredentialsHelper.lookupSystemCredentials(\"nonexistent-credentials-id\", null));\n\n        StandardUsernamePasswordCredentials c =\n                new UsernamePasswordCredentialsImpl(CredentialsScope.SYSTEM, null, null, \"username\", \"password\");\n        CredentialsProvider.lookupStores(r.jenkins).iterator().next().addCredentials(Domain.global(), c);\n\n        assertEquals(c, CredentialsHelper.lookupSystemCredentials(c.getId(), null));\n        assertEquals(c, CredentialsHelper.lookupSystemCredentials(c.getId(), new URL(\"http://example.org\")));\n    }\n\n    @Test\n    void lookupSystemCredentialsWithDomainRestriction(JenkinsRule r) throws IOException, FormException {\n        Domain domain = new Domain(\n                \"example\",\n                \"test domain\",\n                Arrays.<DomainSpecification>asList(new HostnameSpecification(\"example.org\", null)));\n        StandardUsernamePasswordCredentials c =\n                new UsernamePasswordCredentialsImpl(CredentialsScope.SYSTEM, null, null, \"username\", \"password\");\n        CredentialsProvider.lookupStores(r.jenkins).iterator().next().addDomain(domain, c);\n\n        assertEquals(c, CredentialsHelper.lookupSystemCredentials(c.getId(), null));\n        assertEquals(c, CredentialsHelper.lookupSystemCredentials(c.getId(), new URL(\"http://example.org\")));\n        assertNull(CredentialsHelper.lookupSystemCredentials(c.getId(), new URL(\"http://nonexistent.url\")));\n    }\n\n    @Test\n    void migrateCredentials(JenkinsRule r) throws MalformedURLException, FormException {\n        assertThat(\n                CredentialsProvider.lookupStores(r.jenkins).iterator().next().getCredentials(Domain.global()), empty());\n\n        StandardUsernamePasswordCredentials c =\n                CredentialsHelper.migrateCredentials(\"username\", \"password\", new URL(\"http://example.org\"));\n\n        assertEquals(\"Migrated by Jira Plugin\", c.getDescription());\n        assertThat(\n                CredentialsProvider.lookupStores(r.jenkins).iterator().next().getCredentials(Domain.global()),\n                hasSize(1));\n    }\n\n    @Test\n    void migrateCredentialsWithExsitingCredentials(JenkinsRule r) throws IOException, FormException {\n        Domain domain = new Domain(\n                \"example\",\n                \"test domain\",\n                Arrays.<DomainSpecification>asList(new HostnameSpecification(\"example.org\", null)));\n        StandardUsernamePasswordCredentials c =\n                new UsernamePasswordCredentialsImpl(CredentialsScope.SYSTEM, null, null, \"username\", \"password\");\n        CredentialsProvider.lookupStores(r.jenkins).iterator().next().addDomain(domain, c);\n\n        StandardUsernamePasswordCredentials cred =\n                CredentialsHelper.migrateCredentials(\"username\", \"password\", new URL(\"http://example.org\"));\n\n        assertEquals(c, cred);\n    }\n}\n"
  },
  {
    "path": "src/test/java/hudson/plugins/jira/DescriptorImplTest.java",
    "content": "package hudson.plugins.jira;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.hasSize;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.spy;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport com.atlassian.jira.rest.client.api.RestClientException;\nimport com.atlassian.jira.rest.client.api.domain.Permissions;\nimport com.cloudbees.plugins.credentials.CredentialsNameProvider;\nimport com.cloudbees.plugins.credentials.CredentialsProvider;\nimport com.cloudbees.plugins.credentials.CredentialsScope;\nimport com.cloudbees.plugins.credentials.CredentialsStore;\nimport com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials;\nimport com.cloudbees.plugins.credentials.domains.Domain;\nimport com.cloudbees.plugins.credentials.domains.HostnameSpecification;\nimport com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl;\nimport hudson.model.AbstractBuild;\nimport hudson.model.AbstractProject;\nimport hudson.model.Descriptor.FormException;\nimport hudson.model.FreeStyleBuild;\nimport hudson.model.FreeStyleProject;\nimport hudson.model.Item;\nimport hudson.model.Run;\nimport hudson.model.User;\nimport hudson.security.ACL;\nimport hudson.security.ACLContext;\nimport hudson.util.FormValidation;\nimport hudson.util.ListBoxModel;\nimport java.io.IOException;\nimport java.net.URL;\nimport java.util.Arrays;\nimport jenkins.model.Jenkins;\nimport org.junit.jupiter.api.Test;\nimport org.jvnet.hudson.test.JenkinsRule;\nimport org.jvnet.hudson.test.MockAuthorizationStrategy;\nimport org.jvnet.hudson.test.MockFolder;\nimport org.jvnet.hudson.test.junit.jupiter.WithJenkins;\nimport org.mockito.Mockito;\n\n/**\n * Created by warden on 14.09.15.\n */\n@WithJenkins\nclass DescriptorImplTest {\n\n    AbstractBuild build = Mockito.mock(FreeStyleBuild.class);\n    Run run = mock(Run.class);\n    AbstractProject project = mock(FreeStyleProject.class);\n\n    JiraSite site = mock(JiraSite.class);\n    JiraSession session = mock(JiraSession.class);\n\n    JiraSite.DescriptorImpl descriptor = spy(new JiraSite.DescriptorImpl());\n    JiraSite.Builder builder = spy(new JiraSite.Builder());\n\n    @Test\n    void doFillCredentialsIdItems(JenkinsRule r) throws IOException, FormException {\n\n        MockFolder dummy = r.createFolder(\"dummy\");\n        r.jenkins.setSecurityRealm(r.createDummySecurityRealm());\n        MockAuthorizationStrategy as = new MockAuthorizationStrategy();\n        as.grant(Jenkins.ADMINISTER).everywhere().to(\"admin\");\n        as.grant(Item.READ).onItems(dummy).to(\"alice\");\n        as.grant(Item.CONFIGURE).onItems(dummy).to(\"dev\");\n        r.jenkins.setAuthorizationStrategy(as);\n\n        Domain domain =\n                new Domain(\"example\", \"test domain\", Arrays.asList(new HostnameSpecification(\"example.org\", null)));\n        StandardUsernamePasswordCredentials c1 =\n                new UsernamePasswordCredentialsImpl(CredentialsScope.SYSTEM, null, null, \"username\", \"password\");\n        CredentialsStore credentialsStore =\n                CredentialsProvider.lookupStores(r.jenkins).iterator().next();\n        credentialsStore.addDomain(domain, c1);\n\n        StandardUsernamePasswordCredentials c2 =\n                new UsernamePasswordCredentialsImpl(CredentialsScope.GLOBAL, null, null, \"uid\", \"pwd\");\n\n        credentialsStore = CredentialsProvider.lookupStores(dummy).iterator().next();\n        credentialsStore.addCredentials(domain, c2);\n\n        JiraSite.DescriptorImpl descriptor = r.jenkins.getDescriptorByType(JiraSite.DescriptorImpl.class);\n\n        try (ACLContext ignored = ACL.as(User.getById(\"admin\", true))) {\n            ListBoxModel options = descriptor.doFillCredentialsIdItems(null, null, \"http://example.org\");\n            assertThat(options.toString(), options, hasSize(3));\n            assertTrue(listBoxModelContainsName(options, CredentialsNameProvider.name(c1)), options.toString());\n\n            options = descriptor.doFillCredentialsIdItems(null, null, \"http://nonexistent.url\");\n            assertThat(options.toString(), options, hasSize(1));\n            assertEquals(\"\", options.get(0).value);\n\n            options = descriptor.doFillCredentialsIdItems(dummy, null, \"http://example.org\");\n            assertThat(options.toString(), options, hasSize(2));\n            assertTrue(listBoxModelContainsName(options, CredentialsNameProvider.name(c2)), options.toString());\n        }\n\n        try (ACLContext ignored = ACL.as(User.getById(\"alice\", true))) {\n            ListBoxModel options = descriptor.doFillCredentialsIdItems(dummy, null, \"http://example.org\");\n            assertThat(options.toString(), options, hasSize(1));\n        }\n        try (ACLContext ignored = ACL.as(User.getById(\"dev\", true))) {\n            ListBoxModel options = descriptor.doFillCredentialsIdItems(dummy, null, \"http://example.org\");\n            assertThat(options.toString(), options, hasSize(2));\n        }\n    }\n\n    private boolean listBoxModelContainsName(ListBoxModel options, String name) {\n        return options.stream()\n                .filter(option -> name.equals(option.name))\n                .findFirst()\n                .isPresent();\n    }\n\n    @Test\n    void validateFormConnectionErrors(JenkinsRule r) throws Exception {\n\n        builder.withMainURL(new URL(\"http://test.com\"));\n\n        when(descriptor.getBuilder()).thenReturn(builder);\n        when(builder.build()).thenReturn(site);\n        when(build.getParent()).thenReturn(project);\n        when(site.getSession(project, true)).thenReturn(session);\n        when(session.getMyPermissions()).thenThrow(RestClientException.class);\n\n        FormValidation validation = descriptor.doValidate(\n                \"http://localhost:8080\",\n                null,\n                null,\n                null,\n                false,\n                null,\n                JiraSite.DEFAULT_TIMEOUT,\n                JiraSite.DEFAULT_READ_TIMEOUT,\n                JiraSite.DEFAULT_THREAD_EXECUTOR_NUMBER,\n                false,\n                project);\n\n        assertEquals(FormValidation.Kind.ERROR, validation.kind);\n        verify(site).getSession(project, true);\n\n        validation = descriptor.doValidate(\n                \"http://localhost:8080\",\n                null,\n                null,\n                null,\n                false,\n                null,\n                -1,\n                JiraSite.DEFAULT_READ_TIMEOUT,\n                JiraSite.DEFAULT_THREAD_EXECUTOR_NUMBER,\n                false,\n                project);\n        assertEquals(Messages.JiraSite_timeoutMinimunValue(\"1\"), validation.getLocalizedMessage());\n        assertEquals(FormValidation.Kind.ERROR, validation.kind);\n        verify(site).getSession(project, true);\n\n        validation = descriptor.doValidate(\n                \"http://localhost:8080\",\n                null,\n                null,\n                null,\n                false,\n                null,\n                JiraSite.DEFAULT_TIMEOUT,\n                -1,\n                JiraSite.DEFAULT_THREAD_EXECUTOR_NUMBER,\n                false,\n                project);\n\n        assertEquals(Messages.JiraSite_readTimeoutMinimunValue(\"1\"), validation.getMessage());\n        assertEquals(FormValidation.Kind.ERROR, validation.kind);\n        verify(site).getSession(project, true);\n\n        validation = descriptor.doValidate(\n                \"http://localhost:8080\",\n                null,\n                null,\n                null,\n                false,\n                null,\n                JiraSite.DEFAULT_TIMEOUT,\n                JiraSite.DEFAULT_READ_TIMEOUT,\n                -1,\n                false,\n                project);\n        assertEquals(Messages.JiraSite_threadExecutorMinimunSize(\"1\"), validation.getMessage());\n        assertEquals(FormValidation.Kind.ERROR, validation.kind);\n        verify(site).getSession(project, true);\n    }\n\n    @Test\n    void validateFormConnectionOK(JenkinsRule r) throws Exception {\n        builder.withMainURL(new URL(\"http://test.com\"));\n\n        when(descriptor.getBuilder()).thenReturn(builder);\n        when(builder.build()).thenReturn(site);\n        when(site.getSession(project, true)).thenReturn(session);\n        when(session.getMyPermissions()).thenReturn(mock(Permissions.class));\n\n        FormValidation validation = descriptor.doValidate(\n                \"http://localhost:8080\",\n                null,\n                null,\n                null,\n                false,\n                null,\n                JiraSite.DEFAULT_TIMEOUT,\n                JiraSite.DEFAULT_READ_TIMEOUT,\n                JiraSite.DEFAULT_THREAD_EXECUTOR_NUMBER,\n                false,\n                project);\n\n        verify(builder).build();\n        verify(site).getSession(project, true);\n        assertEquals(FormValidation.Kind.OK, validation.kind);\n    }\n}\n"
  },
  {
    "path": "src/test/java/hudson/plugins/jira/EmptyFriendlyURLConverterTest.java",
    "content": "package hudson.plugins.jira;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.nullValue;\n\nimport java.net.URL;\nimport org.apache.commons.lang3.StringUtils;\nimport org.hamcrest.Matchers;\nimport org.junit.jupiter.api.Test;\nimport org.jvnet.hudson.test.JenkinsRule;\nimport org.jvnet.hudson.test.WithoutJenkins;\nimport org.jvnet.hudson.test.junit.jupiter.WithJenkins;\n\n/**\n * @author lanwen (Merkushev Kirill)\n */\n@WithJenkins\npublic class EmptyFriendlyURLConverterTest {\n\n    public static final String SOME_VALID_URL = \"http://localhost/\";\n\n    @Test\n    @WithoutJenkins\n    void shouldHandleURLClass() throws Exception {\n        URL someUrl = new URL(SOME_VALID_URL);\n        assertThat(new EmptyFriendlyURLConverter().convert(URL.class, someUrl), Matchers.is(someUrl));\n    }\n\n    @Test\n    @WithoutJenkins\n    void shouldHandleStringClass() throws Exception {\n        assertThat(\n                new EmptyFriendlyURLConverter().convert(URL.class, SOME_VALID_URL),\n                Matchers.is(new URL(SOME_VALID_URL)));\n    }\n\n    @Test\n    @WithoutJenkins\n    void shouldHandleNull() throws Exception {\n        assertThat(new EmptyFriendlyURLConverter().convert(URL.class, null), nullValue());\n    }\n\n    @Test\n    @WithoutJenkins\n    void shouldHandleEmptyString() throws Exception {\n        assertThat(new EmptyFriendlyURLConverter().convert(URL.class, StringUtils.EMPTY), nullValue());\n    }\n\n    @Test\n    @WithoutJenkins\n    void shouldHandleNullAsString() throws Exception {\n        assertThat(new EmptyFriendlyURLConverter().convert(URL.class, \"null\"), nullValue());\n    }\n\n    /**\n     * Requires jenkins rule because of LOGGER usage starts descriptor creating\n     */\n    @Test\n    void shouldHandleMalformedUrlAsString(JenkinsRule j) throws Exception {\n        assertThat(new EmptyFriendlyURLConverter().convert(URL.class, \"bla\"), nullValue());\n    }\n}\n"
  },
  {
    "path": "src/test/java/hudson/plugins/jira/EnvironmentExpanderTest.java",
    "content": "package hudson.plugins.jira;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.core.IsEqual.equalTo;\nimport static org.mockito.Mockito.doReturn;\nimport static org.mockito.Mockito.mock;\n\nimport hudson.EnvVars;\nimport hudson.model.AbstractBuild;\nimport hudson.model.BuildListener;\nimport hudson.model.FreeStyleBuild;\nimport java.io.IOException;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.Mockito;\n\nclass EnvironmentExpanderTest {\n\n    private static final String VARIABLE = \"${ISSUE_ID}\";\n    private static final String ENVIRONMENT_KEY = \"ISSUE_ID\";\n    private static final String ENVIRONMENT_VALUE = \"EXAMPLE-1\";\n\n    EnvVars env;\n\n    BuildListener buildListener = mock(BuildListener.class);\n    AbstractBuild currentBuild = mock(FreeStyleBuild.class);\n\n    @BeforeEach\n    void createCharacteristicEnvironment() throws IOException, InterruptedException {\n        env = new EnvVars();\n        env.put(\"BUILD_NUMBER\", \"1\");\n        env.put(\"BUILD_URL\", \"/some/url/to/job\");\n        env.put(\"JOB_NAME\", \"EnvironmentExpander Test Job\");\n\n        doReturn(env).when(currentBuild).getEnvironment(Mockito.any());\n    }\n\n    @Test\n    void returnVariableWhenValueNotFound() {\n        String value = EnvironmentExpander.expandVariable(VARIABLE, env);\n        assertThat(value, equalTo(VARIABLE));\n    }\n\n    @Test\n    void returnValueWhenFound() {\n        env.put(ENVIRONMENT_KEY, ENVIRONMENT_VALUE);\n\n        String value = EnvironmentExpander.expandVariable(VARIABLE, env);\n        assertThat(value, equalTo(ENVIRONMENT_VALUE));\n\n        env.remove(ENVIRONMENT_KEY);\n    }\n\n    @Test\n    void returnVariableFromNullRunEnvironment() {\n        String value = EnvironmentExpander.expandVariable(VARIABLE, null, null);\n        assertThat(value, equalTo(VARIABLE));\n    }\n\n    @Test\n    void returnValueFromRunEnvironment() {\n        env.put(ENVIRONMENT_KEY, ENVIRONMENT_VALUE);\n\n        String value = EnvironmentExpander.expandVariable(VARIABLE, currentBuild, buildListener);\n        assertThat(value, equalTo(ENVIRONMENT_VALUE));\n\n        env.remove(ENVIRONMENT_KEY);\n    }\n}\n"
  },
  {
    "path": "src/test/java/hudson/plugins/jira/JiraBuildActionTest.java",
    "content": "package hudson.plugins.jira;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.is;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport hudson.model.Job;\nimport hudson.model.Run;\nimport org.junit.jupiter.api.Test;\nimport org.jvnet.hudson.test.JenkinsRule;\nimport org.jvnet.hudson.test.junit.jupiter.WithJenkins;\nimport org.jvnet.hudson.test.recipes.LocalData;\n\n/**\n * Place needed resources in src/test/resources\n *\n */\nclass JiraBuildActionTest {\n\n    /**\n     * Test if existing serialized JiraBuildAction information will be loaded after upgrading jira\n     * plugin version to with new JiraBuildAction class version introduced in PR-72.\n     */\n    @Test\n    @LocalData\n    @WithJenkins\n    void binaryCompatibility(JenkinsRule r) throws Exception {\n        assertEquals(\"Jenkins JiraBuildActionTest config\", r.jenkins.getSystemMessage());\n\n        Job job = r.getInstance().getItemByFullName(\"/project\", Job.class);\n        Run run = job.getBuildByNumber(2);\n        assertEquals(\"job/project/2/\", run.getUrl());\n\n        JiraBuildAction jba = run.getAction(JiraBuildAction.class);\n        assertThat(jba.getOwner().getDisplayName(), is(run.getDisplayName()));\n        assertThat(jba.getIssue(\"JIRA-123\").getSummary(), is(\"Issue summary\"));\n    }\n}\n"
  },
  {
    "path": "src/test/java/hudson/plugins/jira/JiraChangeLogAnnotatorTest.java",
    "content": "package hudson.plugins.jira;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.core.Is.is;\nimport static org.hamcrest.core.IsNot.not;\nimport static org.hamcrest.core.StringContains.containsString;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.doReturn;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.spy;\nimport static org.mockito.Mockito.when;\n\nimport hudson.MarkupText;\nimport hudson.model.Run;\nimport hudson.plugins.jira.model.JiraIssue;\nimport java.io.IOException;\nimport java.net.URL;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.concurrent.locks.ReentrantLock;\nimport java.util.regex.Pattern;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.jvnet.hudson.test.Issue;\nimport org.mockito.Mock;\nimport org.mockito.Mockito;\nimport org.mockito.junit.jupiter.MockitoExtension;\nimport org.mockito.stubbing.Answer;\n\n/**\n * @author Kohsuke Kawaguchi\n */\n@ExtendWith(MockitoExtension.class)\nclass JiraChangeLogAnnotatorTest {\n    private static final String TITLE = \"title with $sign to confuse TextMarkup.replace\";\n\n    @Mock(strictness = Mock.Strictness.LENIENT)\n    private JiraSite site;\n\n    @Mock\n    private Run run;\n\n    @Mock(strictness = Mock.Strictness.LENIENT)\n    private JiraSession session;\n\n    @BeforeEach\n    void before() throws Exception {\n        when(session.getProjectKeys()).thenReturn(new HashSet(Arrays.asList(\"DUMMY\", \"JENKINS\")));\n        when(site.getSession(any())).thenReturn(session);\n        when(site.getProjectUpdateLock()).thenReturn(new ReentrantLock());\n\n        when(site.getUrl(Mockito.anyString())).thenAnswer((Answer<URL>) invocation -> {\n            String id = invocation.getArguments()[0].toString();\n            return new URL(\"http://dummy/\" + id);\n        });\n        when(site.getProjectKeys(run.getParent())).thenCallRealMethod();\n        when(site.getIssuePattern()).thenCallRealMethod();\n    }\n\n    @Test\n    void annotate() {\n        when(run.getAction(JiraBuildAction.class))\n                .thenReturn(new JiraBuildAction(Collections.singleton(new JiraIssue(\"DUMMY-1\", TITLE))));\n\n        MarkupText text = new MarkupText(\"marking up DUMMY-1.\");\n        JiraChangeLogAnnotator annotator = spy(new JiraChangeLogAnnotator());\n        doReturn(site).when(annotator).getSiteForProject(Mockito.any());\n\n        annotator.annotate(run, null, text);\n\n        // make sure '$' didn't confuse the JiraChangeLogAnnotator\n        assertThat(text.toString(false), containsString(TITLE));\n        assertThat(text.toString(false), containsString(\"href\"));\n    }\n\n    @Test\n    void annotateDisabledOnSiteLevel() {\n        JiraChangeLogAnnotator annotator = spy(new JiraChangeLogAnnotator());\n\n        doReturn(site).when(annotator).getSiteForProject(Mockito.any());\n        doReturn(true).when(site).getDisableChangelogAnnotations();\n\n        MarkupText text = new MarkupText(\"marking up DUMMY-1.\");\n        annotator.annotate(run, null, text);\n\n        assertThat(text.toString(false), not(containsString(\"href\")));\n    }\n\n    @Test\n    void annotateWf() {\n        when(run.getAction(JiraBuildAction.class))\n                .thenReturn(new JiraBuildAction(Collections.singleton(new JiraIssue(\"DUMMY-1\", TITLE))));\n\n        MarkupText text = new MarkupText(\"marking up DUMMY-1.\");\n        JiraChangeLogAnnotator annotator = spy(new JiraChangeLogAnnotator());\n        doReturn(site).when(annotator).getSiteForProject(Mockito.any());\n\n        annotator.annotate(run, null, text);\n\n        // make sure '$' didn't confuse the JiraChangeLogAnnotator\n        assertThat(text.toString(false), containsString(TITLE));\n    }\n\n    /**\n     * Jenkins' MarkupText#findTokens() doesn't work in our case if\n     * the whole pattern matches the following word boundary character\n     * (but not matching group 1).\n     * Regression test for this.\n     */\n    @Test\n    void wordBoundaryProblem() {\n        JiraChangeLogAnnotator annotator = spy(new JiraChangeLogAnnotator());\n        doReturn(site).when(annotator).getSiteForProject(Mockito.any());\n\n        // old changelog annotator used MarkupText#findTokens\n        // That broke because of the space after the issue id.\n        MarkupText text = new MarkupText(\"DUMMY-4071 Text \");\n        annotator.annotate(run, null, text);\n        assertThat(text.toString(false), is(\"<a href='http://dummy/DUMMY-4071'>DUMMY-4071</a> Text \"));\n\n        text = new MarkupText(\"DUMMY-1,comment\");\n        annotator.annotate(run, null, text);\n        assertThat(text.toString(false), is(\"<a href='http://dummy/DUMMY-1'>DUMMY-1</a>,comment\"));\n\n        text = new MarkupText(\"DUMMY-1.comment\");\n        annotator.annotate(run, null, text);\n        assertThat(text.toString(false), is(\"<a href='http://dummy/DUMMY-1'>DUMMY-1</a>.comment\"));\n\n        text = new MarkupText(\"DUMMY-1!comment\");\n        annotator.annotate(run, null, text);\n        assertThat(text.toString(false), is(\"<a href='http://dummy/DUMMY-1'>DUMMY-1</a>!comment\"));\n\n        text = new MarkupText(\"DUMMY-1\\tcomment\");\n        annotator.annotate(run, null, text);\n        assertThat(text.toString(false), is(\"<a href='http://dummy/DUMMY-1'>DUMMY-1</a>\\tcomment\"));\n    }\n\n    @Test\n    void matchMultipleIssueIds() {\n        JiraChangeLogAnnotator annotator = spy(new JiraChangeLogAnnotator());\n        doReturn(site).when(annotator).getSiteForProject(Mockito.any());\n\n        MarkupText text = new MarkupText(\"DUMMY-1 Text DUMMY-2,DUMMY-3 DUMMY-4!\");\n        annotator.annotate(run, null, text);\n\n        assertThat(\n                text.toString(false),\n                is(\"<a href='http://dummy/DUMMY-1'>DUMMY-1</a> Text \" + \"<a href='http://dummy/DUMMY-2'>DUMMY-2</a>,\"\n                        + \"<a href='http://dummy/DUMMY-3'>DUMMY-3</a> \"\n                        + \"<a href='http://dummy/DUMMY-4'>DUMMY-4</a>!\"));\n    }\n\n    @Test\n    void hasProjectForIssueIsCaseInsensitive() {\n        JiraChangeLogAnnotator annotator = spy(new JiraChangeLogAnnotator());\n        doReturn(site).when(annotator).getSiteForProject(Mockito.any());\n\n        MarkupText text = new MarkupText(\"fixed DUMMY-42\");\n        annotator.annotate(mock(Run.class), null, text);\n\n        assertThat(annotator.hasProjectForIssue(\"JENKINS-123\", site, run), is(true));\n        assertThat(annotator.hasProjectForIssue(\"jenKiNs-123\", site, run), is(true));\n        assertThat(annotator.hasProjectForIssue(\"dummy-4711\", site, run), is(true));\n        assertThat(annotator.hasProjectForIssue(\"OThEr-4711\", site, run), is(false));\n    }\n\n    @Test\n    @Issue(\"4132\")\n    void caseInsensitiveAnnotate() {\n        JiraChangeLogAnnotator annotator = spy(new JiraChangeLogAnnotator());\n        doReturn(site).when(annotator).getSiteForProject(Mockito.any());\n\n        MarkupText text = new MarkupText(\"fixed DUMMY-42\");\n        annotator.annotate(mock(Run.class), null, text);\n\n        assertThat(text.toString(false), is(\"fixed <a href='http://dummy/DUMMY-42'>DUMMY-42</a>\"));\n    }\n\n    /**\n     * Tests that missing issues - i.e. issues not saved to build -\n     * are fetched from remote.\n     */\n    @Test\n    @Issue(\"5252\")\n    void getIssueDetailsForMissingIssues() throws IOException {\n        Run run = mock(Run.class);\n\n        JiraChangeLogAnnotator annotator = spy(new JiraChangeLogAnnotator());\n        doReturn(site).when(annotator).getSiteForProject(Mockito.any());\n\n        JiraIssue issue = new JiraIssue(\"DUMMY-42\", TITLE);\n        when(site.getIssue(Mockito.anyString())).thenReturn(issue);\n\n        MarkupText text = new MarkupText(\"fixed DUMMY-42\");\n        annotator.annotate(run, null, text);\n        assertThat(text.toString(false), containsString(TITLE));\n    }\n\n    /**\n     * Tests that no exception is thrown if user issue pattern is invalid (contains\n     * no groups)\n     */\n    @Test\n    void invalidUserPattern() {\n        JiraChangeLogAnnotator annotator = spy(new JiraChangeLogAnnotator());\n\n        when(site.getIssuePattern()).thenReturn(Pattern.compile(\"[a-zA-Z][a-zA-Z0-9_]+-[1-9][0-9]*\"));\n        doReturn(site).when(annotator).getSiteForProject(Mockito.any());\n\n        MarkupText text = new MarkupText(\"fixed DUMMY-42\");\n        annotator.annotate(mock(Run.class), null, text);\n\n        assertThat(text.toString(false), not(containsString(TITLE)));\n    }\n\n    /**\n     * Tests that only the 1st matching group is hyperlinked and not the whole\n     * pattern.\n     * Previous implementation did so.\n     */\n    @Test\n    void matchOnlyMatchGroup1() throws IOException {\n        JiraChangeLogAnnotator annotator = spy(new JiraChangeLogAnnotator());\n        doReturn(site).when(annotator).getSiteForProject(Mockito.any());\n        when(site.getIssuePattern()).thenReturn(Pattern.compile(\"([a-zA-Z][a-zA-Z0-9_]+-[1-9][0-9]*)abc\"));\n\n        MarkupText text = new MarkupText(\"fixed DUMMY-42abc\");\n        annotator.annotate(mock(Run.class), null, text);\n\n        assertThat(text.toString(false), is(\"fixed <a href='http://dummy/DUMMY-42'>DUMMY-42</a>abc\"));\n\n        // check again when issue != null:\n        JiraIssue issue = new JiraIssue(\"DUMMY-42\", TITLE);\n        when(site.getIssue(Mockito.anyString())).thenReturn(issue);\n        text = new MarkupText(\"fixed DUMMY-42abc\");\n        annotator.annotate(mock(Run.class), null, text);\n        assertThat(\n                text.toString(false),\n                is(\n                        \"fixed <a href='http://dummy/DUMMY-42' tooltip='title with $sign to confuse TextMarkup.replace'>DUMMY-42</a>abc\"));\n    }\n\n    /**\n     * Tests that setting the \"alternative Url\" property actually\n     * changes the link also.\n     */\n    @Test\n    void alternativeURLAnnotate() throws Exception {\n        when(site.getAlternativeUrl(Mockito.anyString())).thenAnswer((Answer<URL>) invocation -> {\n            String id = invocation.getArguments()[0].toString();\n            return new URL(\"http://altdummy/\" + id);\n        });\n\n        Run run = mock(Run.class);\n\n        when(run.getAction(JiraBuildAction.class))\n                .thenReturn(new JiraBuildAction(Collections.singleton(new JiraIssue(\"DUMMY-1\", TITLE))));\n        MarkupText text = new MarkupText(\"marking up DUMMY-1.\");\n        JiraChangeLogAnnotator annotator = spy(new JiraChangeLogAnnotator());\n        doReturn(site).when(annotator).getSiteForProject(Mockito.any());\n\n        annotator.annotate(run, null, text);\n\n        assertThat(text.toString(false), containsString(\"<a href='http://altdummy/DUMMY-1'\"));\n    }\n}\n"
  },
  {
    "path": "src/test/java/hudson/plugins/jira/JiraCreateIssueNotifierTest.java",
    "content": "package hudson.plugins.jira;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.*;\n\nimport com.atlassian.jira.rest.client.api.RestClientException;\nimport com.atlassian.jira.rest.client.api.StatusCategory;\nimport com.atlassian.jira.rest.client.api.domain.Component;\nimport com.atlassian.jira.rest.client.api.domain.Issue;\nimport com.atlassian.jira.rest.client.api.domain.IssueType;\nimport com.atlassian.jira.rest.client.api.domain.Priority;\nimport com.atlassian.jira.rest.client.api.domain.Status;\nimport com.cloudbees.hudson.plugins.folder.Folder;\nimport com.cloudbees.plugins.credentials.CredentialsScope;\nimport com.cloudbees.plugins.credentials.CredentialsStore;\nimport com.cloudbees.plugins.credentials.SystemCredentialsProvider;\nimport com.cloudbees.plugins.credentials.domains.Domain;\nimport com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl;\nimport hudson.EnvVars;\nimport hudson.Launcher;\nimport hudson.model.AbstractBuild;\nimport hudson.model.BuildListener;\nimport hudson.model.FreeStyleBuild;\nimport hudson.model.FreeStyleProject;\nimport hudson.model.Result;\nimport hudson.util.ListBoxModel;\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.PrintStream;\nimport java.net.URL;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport org.hamcrest.Matchers;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.jvnet.hudson.test.JenkinsRule;\nimport org.jvnet.hudson.test.WithoutJenkins;\nimport org.jvnet.hudson.test.junit.jupiter.WithJenkins;\nimport org.mockito.Mockito;\n\n@WithJenkins\npublic class JiraCreateIssueNotifierTest {\n\n    private static final String JIRA_PROJECT = \"PROJECT\";\n    private static final String COMPONENT = \"some, componentA\";\n    private static final String ASSIGNEE = \"user.name\";\n    private static final String DESCRIPTION = \"Some description\";\n    private static final String DESCRIPTION_PARAM = \"${DESCRIPTION}\";\n\n    List<Component> jiraComponents = new ArrayList<>();\n\n    Launcher launcher = mock(Launcher.class);\n    BuildListener buildListener = mock(BuildListener.class);\n    PrintStream logger = mock(PrintStream.class);\n    JiraSite site = mock(JiraSite.class);\n    JiraSession session = mock(JiraSession.class);\n    EnvVars env;\n\n    FreeStyleProject project = mock(FreeStyleProject.class);\n    AbstractBuild previousBuild = mock(FreeStyleBuild.class);\n    AbstractBuild currentBuild = mock(FreeStyleBuild.class);\n    File temporaryDirectory;\n\n    @TempDir\n    public File temporaryFolder;\n\n    @BeforeEach\n    void createCommonMocks() throws IOException, InterruptedException {\n        env = new EnvVars();\n        env.put(\"BUILD_NUMBER\", \"10\");\n        env.put(\"BUILD_URL\", \"/some/url/to/job\");\n        env.put(\"JOB_NAME\", \"Some job\");\n        env.put(\"DESCRIPTION\", DESCRIPTION);\n\n        jiraComponents.add(new Component(null, null, \"componentA\", null, null));\n\n        when(currentBuild.getParent()).thenReturn(project);\n        when(site.getSession(currentBuild.getParent())).thenReturn(session);\n        when(site.getSession(previousBuild.getParent())).thenReturn(session);\n\n        doReturn(env).when(currentBuild).getEnvironment(Mockito.any());\n\n        temporaryDirectory = newFolder(temporaryFolder, \"junit\");\n\n        when(project.getBuildDir()).thenReturn(temporaryDirectory);\n        when(currentBuild.getProject()).thenReturn(project);\n        when(currentBuild.getEnvironment(buildListener)).thenReturn(env);\n        when(currentBuild.getPreviousCompletedBuild()).thenReturn(previousBuild);\n        when(buildListener.getLogger()).thenReturn(logger);\n\n        when(session.getComponents(Mockito.anyString())).thenReturn(jiraComponents);\n    }\n\n    @Test\n    @WithoutJenkins\n    void performSuccessFailure() throws Exception {\n\n        when(previousBuild.getResult()).thenReturn(Result.SUCCESS);\n        when(currentBuild.getResult()).thenReturn(Result.FAILURE);\n\n        JiraCreateIssueNotifier notifier = spy(new JiraCreateIssueNotifier(JIRA_PROJECT, \"\", \"\", \"\", 1L, 1L, 1));\n        doReturn(site).when(notifier).getSiteForProject(Mockito.any());\n\n        Issue issue = mock(Issue.class);\n        when(session.createIssue(\n                        Mockito.anyString(),\n                        Mockito.anyString(),\n                        Mockito.anyString(),\n                        Mockito.anyIterable(),\n                        Mockito.anyString(),\n                        Mockito.anyLong(),\n                        Mockito.anyLong()))\n                .thenReturn(issue);\n\n        assertTrue(notifier.perform(currentBuild, launcher, buildListener));\n    }\n\n    @Test\n    @WithoutJenkins\n    void performSuccessFailureWithEnv() throws Exception {\n        when(previousBuild.getResult()).thenReturn(Result.SUCCESS);\n        when(currentBuild.getResult()).thenReturn(Result.FAILURE);\n\n        JiraCreateIssueNotifier notifier =\n                spy(new JiraCreateIssueNotifier(JIRA_PROJECT, DESCRIPTION_PARAM, \"\", \"\", 1L, 1L, 1));\n        doReturn(site).when(notifier).getSiteForProject(Mockito.any());\n\n        Issue issue = mock(Issue.class);\n        when(session.createIssue(\n                        Mockito.anyString(),\n                        contains(DESCRIPTION),\n                        Mockito.anyString(),\n                        Mockito.anyIterable(),\n                        Mockito.anyString(),\n                        Mockito.anyLong(),\n                        Mockito.anyLong()))\n                .thenReturn(issue);\n\n        assertTrue(notifier.perform(currentBuild, launcher, buildListener));\n    }\n\n    @Test\n    @WithoutJenkins\n    void performFailureFailure() throws Exception {\n        JiraCreateIssueNotifier notifier =\n                spy(new JiraCreateIssueNotifier(JIRA_PROJECT, DESCRIPTION, ASSIGNEE, COMPONENT, 1L, 1L, 1));\n        doReturn(site).when(notifier).getSiteForProject(Mockito.any());\n\n        Issue issue = mock(Issue.class);\n\n        Status status = new Status(null, null, \"1\", \"Open\", null, null);\n        when(session.createIssue(\n                        Mockito.anyString(),\n                        contains(DESCRIPTION),\n                        Mockito.anyString(),\n                        Mockito.anyIterable(),\n                        Mockito.anyString(),\n                        Mockito.anyLong(),\n                        Mockito.anyLong()))\n                .thenReturn(issue);\n        when(session.getIssueByKey(Mockito.anyString())).thenReturn(issue);\n        when(issue.getStatus()).thenReturn(status);\n\n        assertEquals(0, temporaryDirectory.list().length);\n\n        when(previousBuild.getResult()).thenReturn(Result.SUCCESS);\n        when(currentBuild.getResult()).thenReturn(Result.FAILURE);\n        assertTrue(notifier.perform(currentBuild, launcher, buildListener));\n\n        assertEquals(1, temporaryDirectory.list().length);\n\n        when(previousBuild.getResult()).thenReturn(Result.FAILURE);\n        when(currentBuild.getResult()).thenReturn(Result.FAILURE);\n        assertTrue(notifier.perform(currentBuild, launcher, buildListener));\n\n        assertEquals(1, temporaryDirectory.list().length);\n\n        when(issue.getStatus())\n                .thenReturn(new Status(\n                        null, null, \"6\", JiraCreateIssueNotifier.finishedStatuses.Closed.toString(), null, null));\n        assertTrue(notifier.perform(currentBuild, launcher, buildListener));\n\n        assertEquals(1, temporaryDirectory.list().length);\n    }\n\n    @Test\n    @WithoutJenkins\n    void performFailureSuccessIssueOpen() throws Exception {\n        Long typeId = 1L;\n        Long priorityId = 0L;\n        Integer actionIdOnSuccess = 5;\n\n        JiraCreateIssueNotifier notifier =\n                spy(new JiraCreateIssueNotifier(JIRA_PROJECT, \"\", \"\", \"\", typeId, priorityId, actionIdOnSuccess));\n\n        assertEquals(typeId, notifier.getTypeId());\n        assertEquals(priorityId, notifier.getPriorityId());\n        assertEquals(actionIdOnSuccess, notifier.getActionIdOnSuccess());\n\n        doReturn(site).when(notifier).getSiteForProject(Mockito.any());\n\n        Issue issue = mock(Issue.class);\n        Status status = new Status(null, null, \"1\", \"Open\", null, null);\n        when(session.createIssue(\n                        Mockito.anyString(),\n                        Mockito.anyString(),\n                        Mockito.anyString(),\n                        Mockito.anyIterable(),\n                        Mockito.anyString(),\n                        Mockito.eq(typeId),\n                        Mockito.isNull()))\n                .thenReturn(issue);\n        when(issue.getStatus()).thenReturn(status);\n        when(session.getIssueByKey(Mockito.anyString())).thenReturn(issue);\n\n        assertEquals(0, temporaryDirectory.list().length);\n\n        when(previousBuild.getResult()).thenReturn(Result.SUCCESS);\n        when(currentBuild.getResult()).thenReturn(Result.FAILURE);\n        assertTrue(notifier.perform(currentBuild, launcher, buildListener));\n\n        assertEquals(1, temporaryDirectory.list().length);\n\n        when(previousBuild.getResult()).thenReturn(Result.FAILURE);\n        when(currentBuild.getResult()).thenReturn(Result.SUCCESS);\n        assertTrue(notifier.perform(currentBuild, launcher, buildListener));\n\n        verify(session).progressWorkflowAction(\"null\", actionIdOnSuccess);\n\n        assertEquals(1, temporaryDirectory.list().length);\n    }\n\n    @Test\n    @WithoutJenkins\n    void performFailureSuccessIssueClosedWithComponents() throws Exception {\n        JiraCreateIssueNotifier notifier = spy(new JiraCreateIssueNotifier(JIRA_PROJECT, \"\", \"\", \"\", 1L, 1L, 1));\n        doReturn(site).when(notifier).getSiteForProject(Mockito.any());\n\n        Issue issue = mock(Issue.class);\n        Status status =\n                new Status(null, null, JiraCreateIssueNotifier.finishedStatuses.Closed.toString(), null, null, null);\n\n        when(session.createIssue(\n                        Mockito.anyString(),\n                        Mockito.anyString(),\n                        Mockito.anyString(),\n                        Mockito.anyIterable(),\n                        Mockito.anyString(),\n                        Mockito.anyLong(),\n                        Mockito.anyLong()))\n                .thenReturn(issue);\n        when(issue.getStatus()).thenReturn(status);\n        when(session.getIssueByKey(Mockito.anyString())).thenReturn(issue);\n\n        assertEquals(0, temporaryDirectory.list().length);\n\n        when(previousBuild.getResult()).thenReturn(Result.SUCCESS);\n        when(currentBuild.getResult()).thenReturn(Result.FAILURE);\n        assertTrue(notifier.perform(currentBuild, launcher, buildListener));\n\n        assertEquals(1, temporaryDirectory.list().length);\n\n        when(previousBuild.getResult()).thenReturn(Result.FAILURE);\n        when(currentBuild.getResult()).thenReturn(Result.SUCCESS);\n        assertTrue(notifier.perform(currentBuild, launcher, buildListener));\n\n        // file should be deleted\n        assertEquals(0, temporaryDirectory.list().length);\n    }\n\n    @Test\n    @WithoutJenkins\n    void isDone() {\n        assertTrue(JiraCreateIssueNotifier.isDone(new Status(null, null, \"Closed\", null, null, null)));\n        assertTrue(JiraCreateIssueNotifier.isDone(new Status(null, null, \"Done\", null, null, null)));\n        assertTrue(JiraCreateIssueNotifier.isDone(new Status(null, null, \"Resolved\", null, null, null)));\n        assertTrue(JiraCreateIssueNotifier.isDone(\n                new Status(null, null, \"Abandoned\", null, null, new StatusCategory(null, \"Done\", null, \"done\", null))));\n        assertFalse(JiraCreateIssueNotifier.isDone(\n                new Status(null, null, \"Abandoned\", null, null, new StatusCategory(null, \"ToDo\", null, \"todo\", null))));\n    }\n\n    @Test\n    void doFillPriorityIdItems(JenkinsRule j) throws Exception {\n\n        String credId_1 = \"cred-1-id\";\n        String credId_2 = \"cred-2-id\";\n\n        String pwd1 = \"pwd1\";\n        String pwd2 = \"pwd2\";\n\n        UsernamePasswordCredentialsImpl cred1 =\n                new UsernamePasswordCredentialsImpl(CredentialsScope.GLOBAL, credId_1, null, \"user1\", pwd1);\n        UsernamePasswordCredentialsImpl cred2 =\n                new UsernamePasswordCredentialsImpl(CredentialsScope.GLOBAL, credId_2, null, \"user2\", pwd2);\n\n        SystemCredentialsProvider systemProvider = SystemCredentialsProvider.getInstance();\n        systemProvider.getCredentials().add(cred1);\n        systemProvider.save();\n\n        { // test at project level\n            URL url = new URL(\"https://pacific-ale.com.au\");\n            JiraSite jiraSite = mock(JiraSite.class);\n            when(jiraSite.getUrl()).thenReturn(url);\n            when(jiraSite.getCredentialsId()).thenReturn(credId_1);\n            when(jiraSite.getName()).thenReturn(url.toExternalForm());\n            JiraSession jiraSession = mock(JiraSession.class);\n            when(jiraSession.getPriorities())\n                    .thenReturn(Collections.singletonList(new Priority(null, 2L, \"priority-1\", null, null, null)));\n            when(jiraSite.getSession(any())).thenReturn(jiraSession);\n\n            JiraGlobalConfiguration.get().setSites(Collections.singletonList(jiraSite));\n\n            FreeStyleProject p = j.jenkins.createProject(\n                    FreeStyleProject.class, \"p\" + j.jenkins.getItems().size());\n            ListBoxModel options = JiraCreateIssueNotifier.DESCRIPTOR.doFillPriorityIdItems(p);\n            assertNotNull(options);\n            assertThat(options.size(), Matchers.equalTo(2));\n            assertThat(options.get(1).value, Matchers.equalTo(\"2\"));\n            assertThat(options.get(1).name, Matchers.containsString(\"priority-1\"));\n            assertThat(options.get(1).name, Matchers.containsString(\"https://pacific-ale.com.au\"));\n        }\n\n        { // test at folder level\n            Folder folder = j.jenkins.createProject(\n                    Folder.class, \"folder\" + j.jenkins.getItems().size());\n\n            CredentialsStore folderStore = JiraFolderPropertyTest.getFolderStore(folder);\n            folderStore.addCredentials(Domain.global(), cred2);\n\n            JiraFolderProperty foo = new JiraFolderProperty();\n\n            JiraSite jiraSite = mock(JiraSite.class);\n            URL url = new URL(\"https://pale-ale.com.au\");\n            when(jiraSite.getUrl()).thenReturn(url);\n            when(jiraSite.getCredentialsId()).thenReturn(credId_2);\n            when(jiraSite.getName()).thenReturn(url.toExternalForm());\n            JiraSession jiraSession = mock(JiraSession.class);\n            when(jiraSession.getPriorities())\n                    .thenReturn(Collections.singletonList(new Priority(null, 3L, \"priority-2\", null, null, null)));\n            when(jiraSite.getSession(any())).thenReturn(jiraSession);\n\n            foo.setSites(Collections.singletonList(jiraSite));\n            folder.getProperties().add(foo);\n\n            ListBoxModel options = JiraCreateIssueNotifier.DESCRIPTOR.doFillPriorityIdItems(folder);\n            assertNotNull(options);\n            assertEquals(2, options.size());\n            assertEquals(\"3\", options.get(1).value);\n            assertTrue(options.get(1).name.contains(\"priority-2\"));\n            assertTrue(options.get(1).name.contains(\"https://pale-ale.com.au\"));\n        }\n    }\n\n    @Test\n    void doFillTypeItems(JenkinsRule j) throws Exception {\n\n        String credId_1 = \"cred-1-id\";\n        String credId_2 = \"cred-2-id\";\n\n        String pwd1 = \"pwd1\";\n        String pwd2 = \"pwd2\";\n\n        UsernamePasswordCredentialsImpl cred1 =\n                new UsernamePasswordCredentialsImpl(CredentialsScope.GLOBAL, credId_1, null, \"user1\", pwd1);\n        UsernamePasswordCredentialsImpl cred2 =\n                new UsernamePasswordCredentialsImpl(CredentialsScope.GLOBAL, credId_2, null, \"user2\", pwd2);\n\n        SystemCredentialsProvider systemProvider = SystemCredentialsProvider.getInstance();\n        systemProvider.getCredentials().add(cred1);\n        systemProvider.save();\n\n        { // test at project level\n            URL url = new URL(\"https://pacific-ale.com.au\");\n            JiraSite jiraSite = mock(JiraSite.class);\n            when(jiraSite.getUrl()).thenReturn(url);\n            when(jiraSite.getCredentialsId()).thenReturn(credId_1);\n            when(jiraSite.getName()).thenReturn(url.toExternalForm());\n            JiraSession jiraSession = mock(JiraSession.class);\n            when(jiraSession.getIssueTypes())\n                    .thenReturn(Collections.singletonList(new IssueType(null, 1L, \"type-1\", true, null, null)));\n            when(jiraSite.getSession(any())).thenReturn(jiraSession);\n\n            JiraGlobalConfiguration.get().setSites(Collections.singletonList(jiraSite));\n\n            FreeStyleProject p = j.jenkins.createProject(\n                    FreeStyleProject.class, \"p\" + j.jenkins.getItems().size());\n            ListBoxModel options = JiraCreateIssueNotifier.DESCRIPTOR.doFillTypeIdItems(p);\n            assertNotNull(options);\n            assertThat(options.size(), Matchers.equalTo(2));\n            assertThat(options.get(1).value, Matchers.equalTo(\"1\"));\n            assertThat(options.get(1).name, Matchers.containsString(\"type-1\"));\n            assertThat(options.get(1).name, Matchers.containsString(\"https://pacific-ale.com.au\"));\n        }\n\n        { // test at folder level\n            Folder folder = j.jenkins.createProject(\n                    Folder.class, \"folder\" + j.jenkins.getItems().size());\n\n            CredentialsStore folderStore = JiraFolderPropertyTest.getFolderStore(folder);\n            folderStore.addCredentials(Domain.global(), cred2);\n\n            JiraFolderProperty foo = new JiraFolderProperty();\n\n            JiraSite jiraSite = mock(JiraSite.class);\n            URL url = new URL(\"https://pale-ale.com.au\");\n            when(jiraSite.getUrl()).thenReturn(url);\n            when(jiraSite.getCredentialsId()).thenReturn(credId_2);\n            when(jiraSite.getName()).thenReturn(url.toExternalForm());\n            JiraSession jiraSession = mock(JiraSession.class);\n            when(jiraSession.getIssueTypes())\n                    .thenReturn(Collections.singletonList(new IssueType(null, 2L, \"type-2\", false, null, null)));\n            when(jiraSite.getSession(any())).thenReturn(jiraSession);\n\n            foo.setSites(Collections.singletonList(jiraSite));\n            folder.getProperties().add(foo);\n\n            ListBoxModel options = JiraCreateIssueNotifier.DESCRIPTOR.doFillTypeIdItems(folder);\n            assertNotNull(options);\n            assertEquals(2, options.size());\n            assertEquals(\"2\", options.get(1).value);\n            assertTrue(options.get(1).name.contains(\"type-2\"));\n            assertTrue(options.get(1).name.contains(\"https://pale-ale.com.au\"));\n        }\n    }\n\n    @Test\n    @WithoutJenkins\n    void testCreateIssueNotifierExceptionLogging() throws Exception {\n        when(previousBuild.getResult()).thenReturn(Result.SUCCESS);\n        when(currentBuild.getResult()).thenReturn(Result.FAILURE);\n        Throwable throwable = mock(Throwable.class);\n        when(buildListener.getLogger()).thenReturn(logger);\n        JiraCreateIssueNotifier notifier =\n                spy(new JiraCreateIssueNotifier(JIRA_PROJECT, DESCRIPTION_PARAM, \"\", \"\", 1L, 1L, 1));\n        doReturn(site).when(notifier).getSiteForProject(Mockito.any());\n        doThrow(new RestClientException(\"[Jira] Jira REST createIssue error. Cause: 401 error\", throwable))\n                .when(session)\n                .createIssue(\n                        Mockito.anyString(),\n                        contains(DESCRIPTION),\n                        Mockito.anyString(),\n                        Mockito.anyIterable(),\n                        Mockito.anyString(),\n                        Mockito.anyLong(),\n                        Mockito.anyLong());\n\n        notifier.perform(currentBuild, launcher, buildListener);\n        verify(logger).println(\"[Jira] Jira REST createIssue error. Cause: 401 error\");\n    }\n\n    private static File newFolder(File root, String... subDirs) throws IOException {\n        String subFolder = String.join(\"/\", subDirs);\n        File result = new File(root, subFolder);\n        if (!result.mkdirs()) {\n            throw new IOException(\"Couldn't create folders \" + root);\n        }\n        return result;\n    }\n}\n"
  },
  {
    "path": "src/test/java/hudson/plugins/jira/JiraCreateReleaseNotesTest.java",
    "content": "package hudson.plugins.jira;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyBoolean;\nimport static org.mockito.Mockito.doReturn;\nimport static org.mockito.Mockito.spy;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport com.atlassian.jira.rest.client.api.domain.Issue;\nimport com.atlassian.jira.rest.client.api.domain.IssueType;\nimport com.atlassian.jira.rest.client.api.domain.Status;\nimport hudson.EnvVars;\nimport hudson.Launcher;\nimport hudson.model.AbstractBuild;\nimport hudson.model.AbstractProject;\nimport hudson.model.BuildListener;\nimport hudson.model.Item;\nimport hudson.model.Result;\nimport hudson.tasks.BuildWrapper;\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.io.PrintWriter;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.concurrent.TimeoutException;\nimport org.hamcrest.Matchers;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.Mock;\nimport org.mockito.Mockito;\nimport org.mockito.junit.jupiter.MockitoExtension;\n\n@ExtendWith(MockitoExtension.class)\nclass JiraCreateReleaseNotesTest {\n\n    private static final String JIRA_RELEASE = Long.toString(System.currentTimeMillis());\n    private static final String JIRA_PRJ = \"TEST_PRJ\";\n    private static final String JIRA_RELEASE_PARAM = \"${JIRA_RELEASE}\";\n    private static final String JIRA_PRJ_PARAM = \"${JIRA_PRJ}\";\n    private static final String JIRA_VARIABLE = \"ReleaseNotes\";\n    private static final String JIRA_OTHER_FILTER = \"status in (Resolved, Done, Closed)\";\n\n    @Mock(strictness = Mock.Strictness.LENIENT)\n    AbstractBuild build;\n\n    @Mock\n    Launcher launcher;\n\n    @Mock(strictness = Mock.Strictness.LENIENT)\n    BuildListener buildListener;\n\n    @Mock(strictness = Mock.Strictness.LENIENT)\n    EnvVars env;\n\n    @Mock\n    AbstractProject project;\n\n    @Mock\n    JiraSite site;\n\n    private final PrintWriter printWriter = new PrintWriter(OutputStream.nullOutputStream());\n\n    @Mock\n    JiraSession session;\n\n    @Mock\n    Item mockItem;\n\n    @BeforeEach\n    void createCommonMocks() throws IOException, InterruptedException {\n        when(build.getEnvironment(buildListener)).thenReturn(env);\n        when(buildListener.fatalError(Mockito.anyString(), Mockito.any(Object[].class)))\n                .thenReturn(printWriter);\n\n        when(env.expand(Mockito.anyString())).thenAnswer(invocationOnMock -> {\n            Object[] args = invocationOnMock.getArguments();\n            String expanded = (String) args[0];\n            if (expanded.equals(JIRA_PRJ_PARAM)) {\n                return JIRA_PRJ;\n            } else if (expanded.equals(JIRA_RELEASE_PARAM)) {\n                return JIRA_RELEASE;\n            } else {\n                return expanded;\n            }\n        });\n\n        when(site.createSession(any(), anyBoolean())).thenReturn(session);\n        when(site.getSession(any(), anyBoolean())).thenCallRealMethod();\n        site.getSession(mockItem, false);\n    }\n\n    @Test\n    void defaults() {\n        JiraCreateReleaseNotes jcrn = new JiraCreateReleaseNotes(JIRA_PRJ, JIRA_RELEASE, \"\");\n        assertEquals(JiraCreateReleaseNotes.DEFAULT_ENVVAR_NAME, jcrn.getJiraEnvironmentVariable());\n        assertEquals(JiraCreateReleaseNotes.DEFAULT_FILTER, jcrn.getJiraFilter());\n    }\n\n    @Test\n    void jiraApiCallDefaultFilter() throws InterruptedException, IOException, TimeoutException {\n        JiraCreateReleaseNotes jcrn = spy(new JiraCreateReleaseNotes(JIRA_PRJ, JIRA_RELEASE, JIRA_VARIABLE));\n        doReturn(site).when(jcrn).getSiteForProject(Mockito.any());\n        jcrn.setUp(build, launcher, buildListener);\n        verify(site).getReleaseNotesForFixVersion(JIRA_PRJ, JIRA_RELEASE, JiraCreateReleaseNotes.DEFAULT_FILTER);\n    }\n\n    @Test\n    void jiraApiCallOtherFilter() throws InterruptedException, IOException, TimeoutException {\n        JiraCreateReleaseNotes jcrn =\n                spy(new JiraCreateReleaseNotes(JIRA_PRJ, JIRA_RELEASE, JIRA_VARIABLE, JIRA_OTHER_FILTER));\n        doReturn(site).when(jcrn).getSiteForProject(Mockito.any());\n        BuildListenerResultMethodMock finishedListener = new BuildListenerResultMethodMock();\n        jcrn.setUp(build, launcher, buildListener);\n        verify(site).getReleaseNotesForFixVersion(JIRA_PRJ, JIRA_RELEASE, JIRA_OTHER_FILTER);\n        // assert that build not fail\n        assertThat(finishedListener.getResult(), Matchers.nullValue());\n    }\n\n    @Test\n    void failBuildOnErrorEmptyProjectKey() throws InterruptedException, IOException {\n        JiraCreateReleaseNotes jcrn =\n                spy(new JiraCreateReleaseNotes(\"\", JIRA_RELEASE, JIRA_VARIABLE, JIRA_OTHER_FILTER));\n        doReturn(site).when(jcrn).getSiteForProject(Mockito.any());\n        BuildListenerResultMethodMock finishedListener = new BuildListenerResultMethodMock();\n        Mockito.doAnswer(finishedListener).when(buildListener).finished(Mockito.any());\n        jcrn.setUp(build, launcher, buildListener);\n        assertThat(finishedListener.getResult(), Matchers.equalTo(Result.FAILURE));\n    }\n\n    @Test\n    void failBuildOnErrorEmptyRelease() throws InterruptedException, IOException {\n        JiraCreateReleaseNotes jcrn = spy(new JiraCreateReleaseNotes(JIRA_PRJ, \"\", JIRA_VARIABLE, JIRA_OTHER_FILTER));\n        doReturn(site).when(jcrn).getSiteForProject(Mockito.any());\n        BuildListenerResultMethodMock finishedListener = new BuildListenerResultMethodMock();\n        Mockito.doAnswer(finishedListener).when(buildListener).finished(Mockito.any());\n        jcrn.setUp(build, launcher, buildListener);\n        assertThat(finishedListener.getResult(), Matchers.equalTo(Result.FAILURE));\n    }\n\n    @Test\n    void releaseNotesContent() throws Exception {\n        JiraCreateReleaseNotes jcrn = spy(new JiraCreateReleaseNotes(JIRA_PRJ, JIRA_RELEASE, JIRA_VARIABLE));\n        doReturn(site).when(jcrn).getSiteForProject(Mockito.any());\n        when(site.getReleaseNotesForFixVersion(JIRA_PRJ, JIRA_RELEASE, JiraCreateReleaseNotes.DEFAULT_FILTER))\n                .thenCallRealMethod();\n\n        Issue issue1 = Mockito.mock(Issue.class);\n        IssueType issueType1 = Mockito.mock(IssueType.class);\n        Status issueStatus = Mockito.mock(Status.class);\n        when(issue1.getIssueType()).thenReturn(issueType1);\n        when(issue1.getStatus()).thenReturn(issueStatus);\n        when(issueType1.getName()).thenReturn(\"Bug\");\n\n        Issue issue2 = Mockito.mock(Issue.class);\n        IssueType issueType2 = Mockito.mock(IssueType.class);\n        when(issue2.getIssueType()).thenReturn(issueType2);\n        when(issue2.getStatus()).thenReturn(issueStatus);\n        when(issueType2.getName()).thenReturn(\"Feature\");\n        when(session.getIssuesWithFixVersion(JIRA_PRJ, JIRA_RELEASE, JiraCreateReleaseNotes.DEFAULT_FILTER))\n                .thenReturn(Arrays.asList(issue1, issue2));\n\n        BuildWrapper.Environment environment = jcrn.setUp(build, launcher, buildListener);\n        Map<String, String> envVars = new HashMap<>();\n        environment.buildEnvVars(envVars);\n        String releaseNotes = envVars.get(jcrn.getJiraEnvironmentVariable());\n        assertNotNull(releaseNotes);\n        assertThat(releaseNotes, Matchers.containsString(issueType1.getName()));\n        assertThat(releaseNotes, Matchers.containsString(issueType2.getName()));\n        assertThat(releaseNotes, Matchers.not(Matchers.containsString(\"UNKNOWN\")));\n    }\n}\n"
  },
  {
    "path": "src/test/java/hudson/plugins/jira/JiraEnvironmentContributingActionTest.java",
    "content": "package hudson.plugins.jira;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.is;\nimport static org.hamcrest.Matchers.nullValue;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\n\nimport hudson.EnvVars;\nimport hudson.model.AbstractBuild;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.ArgumentCaptor;\n\nclass JiraEnvironmentContributingActionTest {\n    private static final String JIRA_URL = \"http://example.com\";\n    private static final String ISSUES_LIST = \"ISS-1,ISS-2\";\n    private static final Integer ISSUES_SIZE = 2;\n\n    @Test\n    public void buildEnvVarsEnvIsNull() {\n        JiraEnvironmentContributingAction action =\n                new JiraEnvironmentContributingAction(ISSUES_LIST, ISSUES_SIZE, JIRA_URL);\n        AbstractBuild<?, ?> build = mock(AbstractBuild.class);\n\n        action.buildEnvVars(build, null);\n        // just expecting no exception\n    }\n\n    @Test\n    public void passedVariablesAreNull() {\n        JiraEnvironmentContributingAction action =\n                new JiraEnvironmentContributingAction(ISSUES_LIST, ISSUES_SIZE, JIRA_URL);\n        AbstractBuild<?, ?> build = mock(AbstractBuild.class);\n\n        action.buildEnvVars(build, null);\n        // just expecting no exception\n    }\n\n    @Test\n    public void buildEnvVarsAddVariables() {\n        JiraEnvironmentContributingAction action =\n                new JiraEnvironmentContributingAction(ISSUES_LIST, ISSUES_SIZE, JIRA_URL);\n        AbstractBuild<?, ?> build = mock(AbstractBuild.class);\n        EnvVars envVars = mock(EnvVars.class);\n\n        action.buildEnvVars(build, envVars);\n\n        ArgumentCaptor<String> keys = ArgumentCaptor.forClass(String.class);\n        ArgumentCaptor<String> values = ArgumentCaptor.forClass(String.class);\n        verify(envVars, times(3)).put(keys.capture(), values.capture());\n\n        assertThat(keys.getAllValues().get(0), is(JiraEnvironmentContributingAction.ISSUES_VARIABLE_NAME));\n        assertThat(values.getAllValues().get(0), is(ISSUES_LIST));\n\n        assertThat(keys.getAllValues().get(1), is(JiraEnvironmentContributingAction.ISSUES_SIZE_VARIABLE_NAME));\n        assertThat(values.getAllValues().get(1), is(ISSUES_SIZE.toString()));\n\n        assertThat(keys.getAllValues().get(2), is(JiraEnvironmentContributingAction.JIRA_URL_VARIABLE_NAME));\n        assertThat(values.getAllValues().get(2), is(JIRA_URL));\n    }\n\n    @Test\n    public void noExceptionWhenNullsPassed() {\n        JiraEnvironmentContributingAction action = new JiraEnvironmentContributingAction(null, null, null);\n        AbstractBuild<?, ?> build = mock(AbstractBuild.class);\n        EnvVars envVars = mock(EnvVars.class);\n\n        action.buildEnvVars(build, envVars);\n\n        ArgumentCaptor<String> keys = ArgumentCaptor.forClass(String.class);\n        ArgumentCaptor<String> values = ArgumentCaptor.forClass(String.class);\n        verify(envVars, times(3)).put(keys.capture(), values.capture());\n\n        assertThat(keys.getAllValues().get(0), is(JiraEnvironmentContributingAction.ISSUES_VARIABLE_NAME));\n        assertThat(values.getAllValues().get(0), nullValue());\n\n        assertThat(keys.getAllValues().get(1), is(JiraEnvironmentContributingAction.ISSUES_SIZE_VARIABLE_NAME));\n        assertThat(values.getAllValues().get(1), is(\"0\"));\n\n        assertThat(keys.getAllValues().get(2), is(JiraEnvironmentContributingAction.JIRA_URL_VARIABLE_NAME));\n        assertThat(values.getAllValues().get(2), nullValue());\n    }\n}\n"
  },
  {
    "path": "src/test/java/hudson/plugins/jira/JiraEnvironmentVariableBuilderTest.java",
    "content": "package hudson.plugins.jira;\n\nimport static org.hamcrest.CoreMatchers.anyOf;\nimport static org.hamcrest.CoreMatchers.instanceOf;\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.mockito.Mockito.*;\n\nimport com.atlassian.jira.rest.client.api.RestClientException;\nimport hudson.EnvVars;\nimport hudson.Launcher;\nimport hudson.model.AbstractBuild;\nimport hudson.model.AbstractProject;\nimport hudson.model.Action;\nimport hudson.model.BuildListener;\nimport hudson.model.Node;\nimport hudson.plugins.jira.selector.AbstractIssueSelector;\nimport hudson.plugins.jira.selector.DefaultIssueSelector;\nimport hudson.plugins.jira.selector.ExplicitIssueSelector;\nimport java.io.IOException;\nimport java.io.PrintStream;\nimport java.util.Arrays;\nimport java.util.LinkedHashSet;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.jvnet.hudson.test.JenkinsRule;\nimport org.jvnet.hudson.test.WithoutJenkins;\nimport org.jvnet.hudson.test.junit.jupiter.WithJenkins;\nimport org.mockito.ArgumentCaptor;\n\n@WithJenkins\nclass JiraEnvironmentVariableBuilderTest {\n\n    private static final String JIRA_URL = \"http://example.com\";\n    private static final String ISSUE_ID_1 = \"ISS-1\";\n    private static final String ISSUE_ID_2 = \"ISS-2\";\n\n    // Ordering of set created from collection intializer seems to depend on which JDK is used\n    // but isn't important for this purpose\n    private static final String EXPECTED_JIRA_ISSUES_1 = ISSUE_ID_1 + \",\" + ISSUE_ID_2;\n    private static final String EXPECTED_JIRA_ISSUES_2 = ISSUE_ID_2 + \",\" + ISSUE_ID_1;\n\n    AbstractBuild build;\n    Launcher launcher;\n    BuildListener listener;\n    EnvVars env;\n    AbstractProject<?, ?> project;\n    JiraSite site;\n    AbstractIssueSelector issueSelector;\n    PrintStream logger;\n    Node node;\n\n    @BeforeEach\n    void createMocks() throws IOException, InterruptedException {\n        build = mock(AbstractBuild.class);\n        launcher = mock(Launcher.class);\n        listener = mock(BuildListener.class);\n        env = mock(EnvVars.class);\n        project = mock(AbstractProject.class);\n        site = mock(JiraSite.class);\n        issueSelector = mock(AbstractIssueSelector.class);\n        logger = mock(PrintStream.class);\n\n        when(site.getName()).thenReturn(JIRA_URL);\n\n        when(listener.getLogger()).thenReturn(logger);\n\n        when(issueSelector.findIssueIds(build, site, listener))\n                .thenReturn(new LinkedHashSet<>(Arrays.asList(ISSUE_ID_1, ISSUE_ID_2)));\n\n        when(build.getProject()).thenReturn(project);\n        when(build.getEnvironment(listener)).thenReturn(env);\n    }\n\n    @Test\n    @WithoutJenkins\n    public void testIssueSelectorDefaultsToDefault() {\n        final JiraEnvironmentVariableBuilder builder = new JiraEnvironmentVariableBuilder(null);\n        assertThat(builder.getIssueSelector(), instanceOf(DefaultIssueSelector.class));\n    }\n\n    @Test\n    @WithoutJenkins\n    public void testSetIssueSelectorPersists() {\n        final JiraEnvironmentVariableBuilder builder = new JiraEnvironmentVariableBuilder(issueSelector);\n        assertThat(builder.getIssueSelector(), is(issueSelector));\n    }\n\n    @Test\n    @WithoutJenkins\n    public void testPerformWithNoSiteFailsBuild() throws InterruptedException, IOException {\n        JiraEnvironmentVariableBuilder builder = spy(new JiraEnvironmentVariableBuilder(issueSelector));\n        doReturn(null).when(builder).getSiteForProject(project);\n        assertThat(builder.perform(build, launcher, listener), is(false));\n        verify(logger, times(1)).println(Messages.JiraEnvironmentVariableBuilder_NoJiraSite());\n    }\n\n    @Test\n    @WithoutJenkins\n    public void testPerformAddsAction() throws InterruptedException, IOException {\n        JiraEnvironmentVariableBuilder builder = spy(new JiraEnvironmentVariableBuilder(issueSelector));\n        doReturn(site).when(builder).getSiteForProject(project);\n        boolean result = builder.perform(build, launcher, listener);\n\n        assertThat(result, is(true));\n\n        ArgumentCaptor<Action> captor = ArgumentCaptor.forClass(Action.class);\n        verify(build).addAction(captor.capture());\n\n        assertThat(captor.getValue(), instanceOf(JiraEnvironmentContributingAction.class));\n\n        JiraEnvironmentContributingAction action = (JiraEnvironmentContributingAction) (captor.getValue());\n\n        assertThat(action.getJiraUrl(), is(JIRA_URL));\n        assertThat(action.getIssuesList(), anyOf(is(EXPECTED_JIRA_ISSUES_1), is(EXPECTED_JIRA_ISSUES_2)));\n        assertThat(action.getNumberOfIssues(), is(2));\n    }\n\n    @Test\n    public void testHasIssueSelectors_HasDefaultSelector(JenkinsRule r) {\n        JiraEnvironmentVariableBuilder builder = new JiraEnvironmentVariableBuilder(null);\n        assertThat(builder.getIssueSelector(), instanceOf(DefaultIssueSelector.class));\n        JiraEnvironmentVariableBuilder.DescriptorImpl descriptor =\n                (JiraEnvironmentVariableBuilder.DescriptorImpl) r.jenkins.getDescriptor(builder.getClass());\n        assertTrue(descriptor.hasIssueSelectors());\n    }\n\n    @Test\n    public void testHasIssueSelectors(JenkinsRule r) {\n        ExplicitIssueSelector explicitIssueSelector = new ExplicitIssueSelector();\n        JiraEnvironmentVariableBuilder builder = new JiraEnvironmentVariableBuilder(explicitIssueSelector);\n        assertEquals(explicitIssueSelector, builder.getIssueSelector());\n        JiraEnvironmentVariableBuilder.DescriptorImpl descriptor =\n                (JiraEnvironmentVariableBuilder.DescriptorImpl) r.jenkins.getDescriptor(builder.getClass());\n        assertTrue(descriptor.hasIssueSelectors());\n    }\n\n    @Test\n    @WithoutJenkins\n    public void testEnvBuilderExceptionLogging(JenkinsRule r) throws IOException, InterruptedException {\n        Throwable throwable = mock(Throwable.class);\n        PrintStream logger = mock(PrintStream.class);\n        when(listener.getLogger()).thenReturn(logger);\n        doThrow(new RestClientException(\"[Jira] Jira REST findIssueIds error. Cause: 401 error\", throwable))\n                .when(issueSelector)\n                .findIssueIds(build, site, listener);\n        JiraEnvironmentVariableBuilder builder = spy(new JiraEnvironmentVariableBuilder(issueSelector));\n        doReturn(site).when(builder).getSiteForProject(project);\n        assertThat(builder.perform(build, launcher, listener), is(false));\n        verify(logger).println(\"[Jira] Jira REST findIssueIds error. Cause: 401 error\");\n    }\n}\n"
  },
  {
    "path": "src/test/java/hudson/plugins/jira/JiraFolderPropertyTest.java",
    "content": "package hudson.plugins.jira;\n\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\n\nimport com.cloudbees.hudson.plugins.folder.Folder;\nimport com.cloudbees.hudson.plugins.folder.properties.FolderCredentialsProvider;\nimport com.cloudbees.plugins.credentials.CredentialsProvider;\nimport com.cloudbees.plugins.credentials.CredentialsStore;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.stream.StreamSupport;\nimport org.junit.jupiter.api.Test;\nimport org.jvnet.hudson.test.JenkinsRule;\nimport org.jvnet.hudson.test.junit.jupiter.WithJenkins;\n\n@WithJenkins\npublic class JiraFolderPropertyTest {\n\n    @Test\n    void configRoundtrip(JenkinsRule r) throws Exception {\n        Folder d = r.jenkins.createProject(Folder.class, \"d\");\n        r.configRoundtrip(d);\n        assertNull(d.getProperties().get(JiraFolderProperty.class));\n        List<JiraSite> list = new ArrayList<>();\n        list.add(new JiraSite(\"https://test.com\"));\n        JiraFolderProperty foo = new JiraFolderProperty();\n        foo.setSites(list);\n        foo.setSites(new JiraSite(\"https://otherTest.com\"));\n        d.getProperties().add(foo);\n        r.configRoundtrip(d);\n        JiraFolderProperty prop = d.getProperties().get(JiraFolderProperty.class);\n        assertNotNull(prop);\n        List<JiraSite> actual = Arrays.asList(prop.getSites());\n        r.assertEqualDataBoundBeans(list, actual);\n    }\n\n    public static CredentialsStore getFolderStore(Folder f) {\n        return StreamSupport.stream(CredentialsProvider.lookupStores(f).spliterator(), false)\n                .filter(s -> s.getProvider() instanceof FolderCredentialsProvider && s.getContext() == f)\n                .findFirst()\n                .orElse(null);\n    }\n}\n"
  },
  {
    "path": "src/test/java/hudson/plugins/jira/JiraGlobalConfigurationSaveTest.java",
    "content": "package hudson.plugins.jira;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.notNullValue;\nimport static org.hamcrest.collection.IsCollectionWithSize.hasSize;\nimport static org.hamcrest.core.Is.is;\n\nimport java.net.URL;\nimport java.util.Collections;\nimport java.util.List;\nimport org.junit.jupiter.api.Test;\nimport org.jvnet.hudson.test.Issue;\nimport org.jvnet.hudson.test.JenkinsRule;\nimport org.jvnet.hudson.test.junit.jupiter.WithJenkins;\n\n@WithJenkins\nclass JiraGlobalConfigurationSaveTest {\n\n    @Test\n    @Issue(\"JENKINS-57899\")\n    void jiraSitesListSaved(JenkinsRule r) throws Throwable {\n        String jiraUrl = \"https://issues.jenkins.io/\";\n        JiraGlobalConfiguration jiraGlobalConfiguration = JiraGlobalConfiguration.get();\n        jiraGlobalConfiguration.setSites(Collections.singletonList(new JiraSite(jiraUrl)));\n        List<JiraSite> sites = jiraGlobalConfiguration.getSites();\n        assertThat(sites, is(hasSize(1)));\n        JiraSite jiraSite = sites.get(0);\n        URL url = jiraSite.getUrl();\n        assertThat(url, is(notNullValue()));\n        assertThat(url.toString(), is(jiraUrl));\n\n        r.restart();\n\n        jiraGlobalConfiguration = JiraGlobalConfiguration.get();\n        sites = jiraGlobalConfiguration.getSites();\n        assertThat(sites, is(hasSize(1)));\n        jiraSite = sites.get(0);\n        url = jiraSite.getUrl();\n        assertThat(url, is(notNullValue()));\n        assertThat(url.toString(), is(jiraUrl));\n    }\n}\n"
  },
  {
    "path": "src/test/java/hudson/plugins/jira/JiraGlobalConfigurationTest.java",
    "content": "package hudson.plugins.jira;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\nimport com.thoughtworks.xstream.XStream;\nimport hudson.plugins.jira.JiraProjectProperty.DescriptorImpl;\nimport hudson.util.XStream2;\nimport java.io.InputStream;\nimport org.junit.jupiter.api.Test;\nimport org.jvnet.hudson.test.JenkinsRule;\nimport org.jvnet.hudson.test.junit.jupiter.WithJenkins;\n\n@WithJenkins\nclass JiraGlobalConfigurationTest {\n\n    @Test\n    void migrateOldConfiguration(JenkinsRule r) throws Exception {\n        String url = \"https://backwardsCompatURL.com/\";\n        XStream xstream = new XStream2();\n        JiraSite expected = new JiraSite(url);\n        InputStream resource = getClass().getResourceAsStream(\"oldJiraProjectProperty.xml\");\n        DescriptorImpl instance = (DescriptorImpl) xstream.fromXML(resource);\n        assertNotNull(instance);\n        assertNull(instance.sites);\n        JiraSite actual = JiraGlobalConfiguration.get().getSites().get(0);\n        assertEquals(url, actual.getName());\n        r.assertEqualDataBoundBeans(expected, actual);\n    }\n}\n"
  },
  {
    "path": "src/test/java/hudson/plugins/jira/JiraIssueMigratorTest.java",
    "content": "package hudson.plugins.jira;\n\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.mockito.Mockito.anyString;\nimport static org.mockito.Mockito.doReturn;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.spy;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport com.atlassian.jira.rest.client.api.RestClientException;\nimport hudson.EnvVars;\nimport hudson.Launcher;\nimport hudson.model.AbstractBuild;\nimport hudson.model.AbstractProject;\nimport hudson.model.BuildListener;\nimport java.io.IOException;\nimport java.util.concurrent.TimeoutException;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\nclass JiraIssueMigratorTest {\n\n    private static final String PROJECT_KEY = \"PROJECT\";\n    private static final String RELEASE = \"release\";\n    private static final String RELEASE_TO_REPLACE = \"releaseToReplace\";\n    private static final String QUERY = \"query\";\n    AbstractBuild build;\n    Launcher launcher;\n    BuildListener listener;\n    EnvVars envVars;\n    JiraSite jiraSite;\n    AbstractProject project;\n    JiraIssueMigrator jiraIssueMigrator;\n\n    @BeforeEach\n    void prepareMocks() throws IOException, TimeoutException, InterruptedException {\n        build = mock(AbstractBuild.class);\n        launcher = mock(Launcher.class);\n        listener = mock(BuildListener.class);\n        envVars = mock(EnvVars.class);\n        jiraSite = mock(JiraSite.class);\n        project = mock(AbstractProject.class);\n\n        when(build.getEnvironment(listener)).thenReturn(envVars);\n        when(envVars.expand(PROJECT_KEY)).thenReturn(PROJECT_KEY);\n        when(envVars.expand(RELEASE)).thenReturn(RELEASE);\n        when(envVars.expand(QUERY)).thenReturn(QUERY);\n        when(envVars.expand(RELEASE_TO_REPLACE)).thenReturn(RELEASE_TO_REPLACE);\n        when(envVars.expand(null)).thenReturn(null);\n        when(build.getProject()).thenReturn(project);\n    }\n\n    @Test\n    void addingVersion() throws IOException, TimeoutException, RestClientException {\n        boolean addRelease = true;\n        jiraIssueMigrator = spy(new JiraIssueMigrator(PROJECT_KEY, RELEASE, QUERY, null, addRelease));\n        doReturn(jiraSite).when(jiraIssueMigrator).getJiraSiteForProject(project);\n        boolean result = jiraIssueMigrator.perform(build, launcher, listener);\n        verify(jiraSite, never()).migrateIssuesToFixVersion(anyString(), anyString(), anyString());\n        verify(jiraSite, never()).replaceFixVersion(anyString(), anyString(), anyString(), anyString());\n        verify(jiraSite, times(1)).addFixVersionToIssue(PROJECT_KEY, RELEASE, QUERY);\n        assertTrue(result);\n    }\n\n    @Test\n    void migratingToVersion() throws IOException, TimeoutException, RestClientException {\n        jiraIssueMigrator = spy(new JiraIssueMigrator(PROJECT_KEY, RELEASE, QUERY, null, false));\n        doReturn(jiraSite).when(jiraIssueMigrator).getJiraSiteForProject(project);\n        boolean result = jiraIssueMigrator.perform(build, launcher, listener);\n        verify(jiraSite, never()).addFixVersionToIssue(anyString(), anyString(), anyString());\n        verify(jiraSite, never()).replaceFixVersion(anyString(), anyString(), anyString(), anyString());\n        verify(jiraSite, times(1)).migrateIssuesToFixVersion(PROJECT_KEY, RELEASE, QUERY);\n        assertTrue(result);\n    }\n\n    @Test\n    void replacingVersion() throws IOException, TimeoutException, RestClientException {\n        jiraIssueMigrator = spy(new JiraIssueMigrator(PROJECT_KEY, RELEASE, QUERY, RELEASE_TO_REPLACE, false));\n        doReturn(jiraSite).when(jiraIssueMigrator).getJiraSiteForProject(project);\n        boolean result = jiraIssueMigrator.perform(build, launcher, listener);\n        verify(jiraSite, never()).addFixVersionToIssue(anyString(), anyString(), anyString());\n        verify(jiraSite, never()).migrateIssuesToFixVersion(anyString(), anyString(), anyString());\n        verify(jiraSite, times(1)).replaceFixVersion(PROJECT_KEY, RELEASE_TO_REPLACE, RELEASE, QUERY);\n        assertTrue(result);\n    }\n}\n"
  },
  {
    "path": "src/test/java/hudson/plugins/jira/JiraIssueParameterDefResultTest.java",
    "content": "package hudson.plugins.jira;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nimport com.atlassian.jira.rest.client.api.domain.Issue;\nimport com.atlassian.jira.rest.client.api.domain.IssueField;\nimport hudson.plugins.jira.listissuesparameter.JiraIssueParameterDefinition;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\nclass JiraIssueParameterDefResultTest {\n\n    private Issue issueMock;\n\n    @BeforeEach\n    void prepareMocks() {\n        issueMock = mock(Issue.class);\n        IssueField fieldMock1 = mock(IssueField.class);\n        IssueField fieldMock2 = mock(IssueField.class);\n        IssueField fieldMock3 = mock(IssueField.class);\n\n        when(issueMock.getFieldByName(\"TestField1\")).thenReturn(fieldMock1);\n        when(issueMock.getFieldByName(\"TestField2\")).thenReturn(fieldMock2);\n        when(issueMock.getFieldByName(\"TestField3\")).thenReturn(fieldMock3);\n        when(issueMock.getFieldByName(\"TestField4\")).thenReturn(null);\n        when(issueMock.getSummary()).thenReturn(\"Summary\");\n        when(fieldMock1.getValue()).thenReturn(\"Field1\");\n        when(fieldMock2.getValue()).thenReturn(\"Field2\");\n        when(fieldMock3.getValue()).thenReturn(\"\");\n    }\n\n    @Test\n    void testSummaryResult() {\n        JiraIssueParameterDefinition.Result result = new JiraIssueParameterDefinition.Result(issueMock, \"\");\n\n        assertThat(\"Summary\", equalTo(result.summary));\n    }\n\n    @Test\n    void testAltSummaryResultCommaSep() {\n        JiraIssueParameterDefinition.Result result =\n                new JiraIssueParameterDefinition.Result(issueMock, \"TestField1,TestField2\");\n\n        assertThat(\"Field1 Field2\", equalTo(result.summary));\n    }\n\n    @Test\n    void testAltSummaryResultCommaSpaceSep() {\n        JiraIssueParameterDefinition.Result result =\n                new JiraIssueParameterDefinition.Result(issueMock, \"TestField1, TestField2\");\n\n        assertThat(\"Field1 Field2\", equalTo(result.summary));\n    }\n\n    @Test\n    void testAltSummaryResultMissingFieldIgnored() {\n        JiraIssueParameterDefinition.Result result =\n                new JiraIssueParameterDefinition.Result(issueMock, \"TestField1, TestField4\");\n\n        assertThat(\"Field1\", equalTo(result.summary));\n    }\n\n    @Test\n    void testAltSummaryResultEmptyFieldIgnored() {\n        JiraIssueParameterDefinition.Result result =\n                new JiraIssueParameterDefinition.Result(issueMock, \"TestField1, TestField3\");\n\n        assertThat(\"Field1\", equalTo(result.summary));\n    }\n}\n"
  },
  {
    "path": "src/test/java/hudson/plugins/jira/JiraIssueUpdateBuilderTest.java",
    "content": "package hudson.plugins.jira;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.is;\nimport static org.mockito.Mockito.*;\n\nimport com.atlassian.jira.rest.client.api.RestClientException;\nimport hudson.EnvVars;\nimport hudson.FilePath;\nimport hudson.Launcher;\nimport hudson.model.AbstractBuild;\nimport hudson.model.AbstractProject;\nimport hudson.model.Result;\nimport hudson.model.TaskListener;\nimport java.io.IOException;\nimport java.io.PrintStream;\nimport java.util.Collections;\nimport org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition;\nimport org.jenkinsci.plugins.workflow.job.WorkflowJob;\nimport org.jenkinsci.plugins.workflow.job.WorkflowRun;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.jvnet.hudson.test.JenkinsRule;\nimport org.jvnet.hudson.test.junit.jupiter.WithJenkins;\n\nclass JiraIssueUpdateBuilderTest {\n\n    private Launcher launcher;\n    private FilePath workspace;\n    private TaskListener listener;\n    private AbstractBuild build;\n    private EnvVars env;\n    private AbstractProject project;\n    private PrintStream logger;\n\n    private Result result;\n    private JiraSite site;\n\n    @BeforeEach\n    void createMocks() throws IOException, InterruptedException {\n        launcher = mock(Launcher.class);\n        listener = mock(TaskListener.class);\n        env = mock(EnvVars.class);\n        project = mock(AbstractProject.class);\n        logger = mock(PrintStream.class);\n        build = mock(AbstractBuild.class);\n        site = mock(JiraSite.class);\n\n        when(build.getEnvironment(listener)).thenReturn(env);\n        when(build.getParent()).thenReturn(project);\n\n        when(listener.getLogger()).thenReturn(logger);\n\n        result = Result.SUCCESS;\n        doAnswer(invocation -> {\n                    Object[] args = invocation.getArguments();\n                    result = (Result) args[0];\n                    return null;\n                })\n                .when(build)\n                .setResult(any());\n    }\n\n    @Test\n    void performNoSite() throws InterruptedException, IOException {\n        JiraIssueUpdateBuilder builder = spy(new JiraIssueUpdateBuilder(null, null, null));\n        doReturn(null).when(builder).getSiteForJob(any());\n        builder.perform(build, workspace, launcher, listener);\n        assertThat(result, is(Result.FAILURE));\n    }\n\n    @Test\n    void validateFailureResult() throws InterruptedException, IOException {\n        JiraIssueUpdateBuilder builder = spy(new JiraIssueUpdateBuilder(null, null, null));\n        Throwable throwable = mock(Throwable.class);\n        doReturn(site).when(builder).getSiteForJob(any());\n        doThrow(new RestClientException(\"Verify failure result\", throwable))\n                .when(site)\n                .progressMatchingIssues(any(), any(), any(), any());\n        builder.perform(build, workspace, launcher, listener);\n        assertThat(result, is(Result.FAILURE));\n    }\n\n    @Test\n    void performProgressFails() throws InterruptedException, IOException {\n        JiraIssueUpdateBuilder builder = spy(new JiraIssueUpdateBuilder(null, null, null));\n        doReturn(site).when(builder).getSiteForJob(any());\n        doReturn(false).when(site).progressMatchingIssues(anyString(), anyString(), anyString(), any());\n        builder.perform(build, workspace, launcher, listener);\n        assertThat(result, is(Result.UNSTABLE));\n    }\n\n    @Test\n    void performProgressOK() throws InterruptedException, IOException {\n        JiraIssueUpdateBuilder builder = spy(new JiraIssueUpdateBuilder(null, null, null));\n        doReturn(site).when(builder).getSiteForJob(any());\n        doReturn(true).when(site).progressMatchingIssues(any(), any(), any(), any());\n        builder.perform(build, workspace, launcher, listener);\n        assertThat(result, is(Result.SUCCESS));\n    }\n\n    @WithJenkins\n    @Test\n    void testPipelineWithJiraSite(JenkinsRule r) throws Exception {\n        JiraGlobalConfiguration jiraGlobalConfiguration = JiraGlobalConfiguration.get();\n        jiraGlobalConfiguration.setSites(Collections.singletonList(site));\n        doReturn(true).when(site).progressMatchingIssues(anyString(), anyString(), anyString(), any());\n        WorkflowJob job = r.createProject(WorkflowJob.class);\n        job.setDefinition(new CpsFlowDefinition(\"\"\"\n                        jiraExecuteWorkflow(jqlSearch: 'search', workflowActionName: 'action', comment: 'comment')\n                \"\"\", true));\n        WorkflowRun b = r.buildAndAssertStatus(Result.SUCCESS, job);\n        r.assertLogContains(\"[Jira] Updating issues using workflow action action.\", b);\n    }\n\n    @Test\n    void testIssueUpdateBuilderRestException() throws InterruptedException, IOException {\n        Throwable throwable = mock(Throwable.class);\n        PrintStream logger = mock(PrintStream.class);\n        when(listener.getLogger()).thenReturn(logger);\n        JiraIssueUpdateBuilder builder = spy(new JiraIssueUpdateBuilder(null, null, null));\n        doReturn(site).when(builder).getSiteForJob(any());\n        doThrow(new RestClientException(\"[Jira] Jira REST progressMatchingIssues error. Cause: 401 error\", throwable))\n                .when(site)\n                .progressMatchingIssues(any(), any(), any(), any());\n        builder.perform(build, workspace, launcher, listener);\n        verify(logger).println(\"[Jira] Jira REST progressMatchingIssues error. Cause: 401 error\");\n    }\n}\n"
  },
  {
    "path": "src/test/java/hudson/plugins/jira/JiraIssueUpdaterTest.java",
    "content": "package hudson.plugins.jira;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.instanceOf;\nimport static org.hamcrest.Matchers.is;\n\nimport hudson.model.Result;\nimport hudson.model.Run;\nimport hudson.model.TaskListener;\nimport hudson.plugins.jira.selector.AbstractIssueSelector;\nimport hudson.plugins.jira.selector.DefaultIssueSelector;\nimport hudson.scm.ChangeLogParser;\nimport hudson.scm.SCM;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Set;\nimport org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition;\nimport org.jenkinsci.plugins.workflow.job.WorkflowJob;\nimport org.jenkinsci.plugins.workflow.job.WorkflowRun;\nimport org.junit.jupiter.api.Test;\nimport org.jvnet.hudson.test.JenkinsRule;\nimport org.jvnet.hudson.test.junit.jupiter.WithJenkins;\n\nclass JiraIssueUpdaterTest {\n\n    @Test\n    void issueSelectorDefaultsToDefault() {\n        final JiraIssueUpdater updater = new JiraIssueUpdater(null, null, null);\n        assertThat(updater.getIssueSelector(), instanceOf(DefaultIssueSelector.class));\n    }\n\n    @Test\n    void setIssueSelectorPersists() {\n        class TestSelector extends AbstractIssueSelector {\n\n            @Override\n            public Set<String> findIssueIds(Run<?, ?> run, JiraSite site, TaskListener listener) {\n                throw new UnsupportedOperationException(\"Not supported yet.\");\n            }\n        }\n\n        final JiraIssueUpdater updater = new JiraIssueUpdater(new TestSelector(), null, null);\n        assertThat(updater.getIssueSelector(), instanceOf(TestSelector.class));\n    }\n\n    @Test\n    void setScmPersists() {\n        class TestSCM extends SCM {\n\n            @Override\n            public ChangeLogParser createChangeLogParser() {\n                throw new UnsupportedOperationException(\"Not supported yet.\");\n            }\n        }\n\n        final JiraIssueUpdater updater = new JiraIssueUpdater(null, new TestSCM(), null);\n        assertThat(updater.getScm(), instanceOf(TestSCM.class));\n    }\n\n    @Test\n    void setLabelsPersists() {\n        List<String> testLabels = Arrays.asList(\"testLabel1\", \"testLabel2\");\n\n        final JiraIssueUpdater updater = new JiraIssueUpdater(null, null, testLabels);\n        assertThat(updater.getLabels(), is(testLabels));\n    }\n\n    @WithJenkins\n    @Test\n    void testPipeline(JenkinsRule r) throws Exception {\n        WorkflowJob job = r.createProject(WorkflowJob.class);\n        job.setDefinition(new CpsFlowDefinition(\"\"\"\n                        jiraCommentIssues(issueSelector: DefaultSelector(), scm: null)\n                \"\"\", true));\n        WorkflowRun b = r.buildAndAssertStatus(Result.FAILURE, job);\n        r.assertLogContains(\" Unsupported run type\", b);\n    }\n}\n"
  },
  {
    "path": "src/test/java/hudson/plugins/jira/JiraJobActionTest.java",
    "content": "package hudson.plugins.jira;\n\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.Mockito.*;\n\nimport com.atlassian.jira.rest.client.api.RestClientException;\nimport hudson.model.TaskListener;\nimport hudson.plugins.jira.model.JiraIssue;\nimport java.io.PrintStream;\nimport org.jenkinsci.plugins.workflow.job.WorkflowJob;\nimport org.jenkinsci.plugins.workflow.job.WorkflowRun;\nimport org.jenkinsci.plugins.workflow.multibranch.WorkflowMultiBranchProject;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.jvnet.hudson.test.JenkinsRule;\nimport org.jvnet.hudson.test.WithoutJenkins;\nimport org.jvnet.hudson.test.junit.jupiter.WithJenkins;\nimport org.mockito.MockedStatic;\nimport org.mockito.Mockito;\n\n@WithJenkins\nclass JiraJobActionTest {\n\n    JiraSite site;\n\n    WorkflowJob job;\n\n    WorkflowMultiBranchProject mbp;\n\n    final JiraIssue issue = new JiraIssue(\"EXAMPLE-123\", \"I like cake\");\n\n    @BeforeEach\n    void setup(JenkinsRule r) throws Exception {\n        mbp = r.jenkins.createProject(WorkflowMultiBranchProject.class, \"mbp\");\n\n        site = spy(new JiraSite(\"https://foo.com\"));\n        doReturn(JiraSite.DEFAULT_ISSUE_PATTERN).when(site).getIssuePattern();\n        doReturn(issue).when(site).getIssue(\"EXAMPLE-123\");\n    }\n\n    @Test\n    void detectBranchNameIssue() throws Exception {\n        job = new WorkflowJob(mbp, \"feature/EXAMPLE-123\");\n        JiraJobAction.setAction(job, site);\n\n        JiraJobAction action = job.getAction(JiraJobAction.class);\n        assertNotNull(action.getIssue());\n        assertEquals(\"EXAMPLE-123\", action.getIssue().getKey());\n        assertEquals(\"I like cake\", action.getIssue().getSummary());\n    }\n\n    @Test\n    void detectBranchNameIssueWithEncodedJobName() throws Exception {\n        job = new WorkflowJob(mbp, \"feature%2FEXAMPLE-123\");\n        JiraJobAction.setAction(job, site);\n\n        JiraJobAction action = job.getAction(JiraJobAction.class);\n        assertNotNull(action.getIssue());\n\n        assertEquals(\"EXAMPLE-123\", action.getIssue().getKey());\n        assertEquals(\"I like cake\", action.getIssue().getSummary());\n    }\n\n    @Test\n    void detectBranchNameIssueJustIssueKey() throws Exception {\n        job = new WorkflowJob(mbp, \"EXAMPLE-123\");\n        JiraJobAction.setAction(job, site);\n\n        JiraJobAction action = job.getAction(JiraJobAction.class);\n        assertNotNull(action.getIssue());\n\n        assertEquals(\"EXAMPLE-123\", action.getIssue().getKey());\n        assertEquals(\"I like cake\", action.getIssue().getSummary());\n    }\n\n    @Test\n    void detectBranchNameIssueNoIssueKey() throws Exception {\n        job = new WorkflowJob(mbp, \"NOTHING INTERESTING\");\n        JiraJobAction.setAction(job, site);\n        JiraJobAction action = job.getAction(JiraJobAction.class);\n        assertNull(action);\n    }\n\n    @Test\n    @WithoutJenkins\n    void testJobActionRestException() {\n        Throwable throwable = mock(Throwable.class);\n        PrintStream logger = mock(PrintStream.class);\n        TaskListener listener = mock(TaskListener.class);\n        WorkflowRun run = mock(WorkflowRun.class);\n        WorkflowJob parent = mock(WorkflowJob.class);\n        when(listener.getLogger()).thenReturn(logger);\n        when(run.getParent()).thenReturn(parent);\n        try (MockedStatic<JiraJobAction> jobActionMockedStatic = Mockito.mockStatic(JiraJobAction.class);\n                MockedStatic<JiraSite> jiraSiteMockedStatic = Mockito.mockStatic(JiraSite.class)) {\n            jiraSiteMockedStatic.when(() -> JiraSite.get(parent)).thenReturn(site);\n            jobActionMockedStatic\n                    .when(() -> JiraJobAction.setAction(parent, site))\n                    .thenThrow(\n                            new RestClientException(\"[Jira] Jira REST setAction error. Cause: 401 error\", throwable));\n            JiraJobAction.RunListenerImpl.fireStarted(run, listener);\n            verify(logger).println(\"[Jira] Jira REST setAction error. Cause: 401 error\");\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/hudson/plugins/jira/JiraProjectPropertyTest.java",
    "content": "package hudson.plugins.jira;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\nimport com.cloudbees.hudson.plugins.folder.Folder;\nimport hudson.model.FreeStyleProject;\nimport io.jenkins.plugins.casc.misc.ConfiguredWithCode;\nimport io.jenkins.plugins.casc.misc.JenkinsConfiguredWithCodeRule;\nimport io.jenkins.plugins.casc.misc.junit.jupiter.WithJenkinsConfiguredWithCode;\nimport java.util.ArrayList;\nimport java.util.List;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.jvnet.hudson.test.JenkinsRule;\n\n@WithJenkinsConfiguredWithCode\nclass JiraProjectPropertyTest {\n\n    private JenkinsRule r;\n\n    private FreeStyleProject freeStyleProject;\n    private Folder folder;\n    private List<JiraSite> firstList;\n\n    @BeforeEach\n    void initialize(JenkinsConfiguredWithCodeRule r) throws Exception {\n        this.r = r;\n        folder = r.jenkins.createProject(Folder.class, \"first\");\n        JiraFolderProperty jiraFolderProperty = new JiraFolderProperty();\n        firstList = new ArrayList<>();\n        firstList.add(new JiraSite(\"https://first.com/\"));\n        jiraFolderProperty.setSites(firstList);\n        folder.getProperties().add(jiraFolderProperty);\n    }\n\n    @Test\n    void getSitesNullWithoutFolder() throws Exception {\n        FreeStyleProject freeStyleProject = r.createFreeStyleProject();\n        JiraProjectProperty prop = new JiraProjectProperty(null);\n        freeStyleProject.addProperty(prop);\n        JiraProjectProperty actual = freeStyleProject.getProperty(JiraProjectProperty.class);\n        assertNotNull(actual);\n        assertNull(actual.getSite());\n    }\n\n    @Test\n    void getSitesNullWithFolder() throws Exception {\n        freeStyleProject = folder.createProject(FreeStyleProject.class, \"something\");\n        JiraProjectProperty prop = new JiraProjectProperty(null);\n        freeStyleProject.addProperty(prop);\n        JiraProjectProperty property = freeStyleProject.getProperty(JiraProjectProperty.class);\n        assertNotNull(property);\n        assertNull(property.getSite());\n    }\n\n    @Test\n    @ConfiguredWithCode(\"single-site.yml\")\n    void getSiteFromProjectProperty() {\n        JiraProjectProperty prop = new JiraProjectProperty(null);\n        JiraSite site = prop.getSite();\n        @SuppressWarnings(\"ConstantConditions\")\n        String actual = site.getUrl().toExternalForm();\n        assertEquals(\"https://jira.com/\", actual);\n    }\n\n    @Test\n    @ConfiguredWithCode(\"single-site.yml\")\n    void getSiteFromSingleEntry() throws Exception {\n        freeStyleProject = r.createFreeStyleProject();\n        JiraSite expected = JiraGlobalConfiguration.get().getSites().get(0);\n        JiraProjectProperty prop = new JiraProjectProperty(null);\n        freeStyleProject.addProperty(prop);\n        JiraProjectProperty property = freeStyleProject.getProperty(JiraProjectProperty.class);\n        assertNotNull(property);\n        assertNotNull(property.getSite());\n        assertEquals(expected.getName(), property.siteName);\n        r.assertEqualDataBoundBeans(expected, property.getSite());\n    }\n\n    @Test\n    @ConfiguredWithCode(\"multiple-sites.yml\")\n    void getSiteFromFirstGlobalMultipleEntryMultipleSites() throws Exception {\n        freeStyleProject = r.createFreeStyleProject();\n        JiraSite expected = JiraGlobalConfiguration.get().getSites().get(0);\n        JiraProjectProperty prop = new JiraProjectProperty(null);\n        freeStyleProject.addProperty(prop);\n        JiraProjectProperty property = freeStyleProject.getProperty(JiraProjectProperty.class);\n        assertNotNull(property);\n        assertNotNull(property.getSite());\n        assertEquals(expected.getName(), property.siteName);\n        r.assertEqualDataBoundBeans(expected, property.getSite());\n    }\n\n    @Test\n    @ConfiguredWithCode(\"multiple-sites.yml\")\n    void getSiteFromSecondGlobalEntryMultipleSites() throws Exception {\n        freeStyleProject = r.createFreeStyleProject();\n        JiraSite expected = new JiraSite(\"https://jira.com/\");\n        JiraProjectProperty prop = new JiraProjectProperty(expected.getName());\n        freeStyleProject.addProperty(prop);\n        JiraProjectProperty property = freeStyleProject.getProperty(JiraProjectProperty.class);\n        assertNotNull(property);\n        assertNotNull(property.getSite());\n        assertEquals(expected.getName(), property.siteName);\n        r.assertEqualDataBoundBeans(expected, property.getSite());\n    }\n\n    @Test\n    @ConfiguredWithCode(\"single-site.yml\")\n    void getSiteFromFirstFolderLayer() throws Exception {\n        freeStyleProject = folder.createProject(FreeStyleProject.class, \"something\");\n        JiraSite expected = firstList.get(0);\n        JiraProjectProperty prop = new JiraProjectProperty(expected.getName());\n        freeStyleProject.addProperty(prop);\n        JiraProjectProperty property = freeStyleProject.getProperty(JiraProjectProperty.class);\n        assertNotNull(property);\n        assertNotNull(property.getSite());\n        assertEquals(expected.getName(), property.siteName);\n        r.assertEqualDataBoundBeans(expected, property.getSite());\n    }\n\n    @Test\n    @ConfiguredWithCode(\"single-site.yml\")\n    void getSiteFromNestedFolderLayer() throws Exception {\n        Folder secondFolder = folder.createProject(Folder.class, \"second\");\n        freeStyleProject = secondFolder.createProject(FreeStyleProject.class, \"something\");\n        // testing we can get value from folder above.\n        JiraSite expected = firstList.get(0);\n        JiraProjectProperty prop = new JiraProjectProperty(expected.getName());\n        freeStyleProject.addProperty(prop);\n        JiraProjectProperty property = freeStyleProject.getProperty(JiraProjectProperty.class);\n        assertNotNull(property);\n        assertNotNull(property.getSite());\n        assertEquals(expected.getName(), property.siteName);\n        r.assertEqualDataBoundBeans(expected, property.getSite());\n    }\n}\n"
  },
  {
    "path": "src/test/java/hudson/plugins/jira/JiraReleaseVersionUpdateBuilderTest.java",
    "content": "package hudson.plugins.jira;\n\nimport static org.mockito.Mockito.*;\n\nimport hudson.model.Result;\nimport java.util.Collections;\nimport org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition;\nimport org.jenkinsci.plugins.workflow.job.WorkflowJob;\nimport org.jenkinsci.plugins.workflow.job.WorkflowRun;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.jvnet.hudson.test.JenkinsRule;\nimport org.jvnet.hudson.test.junit.jupiter.WithJenkins;\n\nclass JiraReleaseVersionUpdateBuilderTest {\n\n    private JiraSite site;\n\n    @BeforeEach\n    void createMocks() {\n        site = mock(JiraSite.class);\n    }\n\n    @WithJenkins\n    @Test\n    void testPipeline(JenkinsRule r) throws Exception {\n        JiraGlobalConfiguration jiraGlobalConfiguration = JiraGlobalConfiguration.get();\n        jiraGlobalConfiguration.setSites(Collections.singletonList(site));\n        WorkflowJob job = r.createProject(WorkflowJob.class);\n        job.setDefinition(new CpsFlowDefinition(\"\"\"\n                        jiraMarkVersionReleased(jiraProjectKey: 'PROJECT', jiraRelease: 'release', jiraDescription: 'description')\n                \"\"\", true));\n        WorkflowRun b = r.buildAndAssertStatus(Result.SUCCESS, job);\n        r.assertLogContains(\"[Jira] Marking version release in project PROJECT as released.\", b);\n    }\n}\n"
  },
  {
    "path": "src/test/java/hudson/plugins/jira/JiraRestServiceProxyTest.java",
    "content": "package hudson.plugins.jira;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\nimport hudson.ProxyConfiguration;\nimport hudson.plugins.jira.extension.ExtendedJiraRestClient;\nimport java.lang.reflect.Field;\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Method;\nimport java.net.URI;\nimport jenkins.model.Jenkins;\nimport org.apache.http.HttpHost;\nimport org.apache.http.client.fluent.Request;\nimport org.eclipse.jetty.server.ConnectionFactory;\nimport org.eclipse.jetty.server.HttpConnectionFactory;\nimport org.eclipse.jetty.server.Server;\nimport org.eclipse.jetty.server.ServerConnector;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.jvnet.hudson.test.JenkinsRule;\nimport org.jvnet.hudson.test.junit.jupiter.WithJenkins;\n\n@WithJenkins\nclass JiraRestServiceProxyTest {\n\n    private final ConnectionFactory connectionFactory = new HttpConnectionFactory();\n    private final URI JIRA_URI = URI.create(\"http://example.com:8080/\");\n    private final String USERNAME = \"user\";\n    private final String PASSWORD = \"password\";\n\n    private Server server;\n    private ServerConnector connector;\n    private ExtendedJiraRestClient client;\n\n    @BeforeEach\n    void prepare() throws Exception {\n        server = new Server();\n        connector = new ServerConnector(server, connectionFactory);\n        server.addConnector(connector);\n        server.start();\n    }\n\n    @AfterEach\n    void dispose() throws Exception {\n        if (server != null) {\n            server.stop();\n        }\n    }\n\n    @Test\n    void withProxy(JenkinsRule r) throws Exception {\n        int localPort = connector.getLocalPort();\n        Jenkins.get().proxy = new ProxyConfiguration(\"localhost\", localPort);\n\n        Object objectProxy = getProxyObjectFromRequest();\n\n        assertNotNull(objectProxy);\n        assertEquals(HttpHost.class, objectProxy.getClass());\n        HttpHost proxyHost = (HttpHost) objectProxy;\n        assertEquals(\"localhost\", proxyHost.getHostName());\n        assertEquals(localPort, proxyHost.getPort());\n    }\n\n    @Test\n    void withProxyAndNoProxyHosts(JenkinsRule r) throws Exception {\n        int localPort = connector.getLocalPort();\n        Jenkins.get().proxy = new ProxyConfiguration(\"localhost\", localPort);\n        Jenkins.get().proxy.setNoProxyHost(\"example.com|google.com\");\n\n        assertNull(getProxyObjectFromRequest());\n    }\n\n    @Test\n    void withoutProxy(JenkinsRule r) throws Exception {\n        assertNull(getProxyObjectFromRequest());\n    }\n\n    private Object getProxyObjectFromRequest()\n            throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, NoSuchFieldException {\n        JiraRestService service = new JiraRestService(JIRA_URI, client, USERNAME, PASSWORD, JiraSite.DEFAULT_TIMEOUT);\n        Method m = service.getClass().getDeclaredMethod(\"buildGetRequest\", URI.class);\n        m.setAccessible(true);\n\n        Request buildGetRequestValue = (Request) m.invoke(service, JIRA_URI);\n\n        assertNotNull(buildGetRequestValue);\n\n        Field proxy = buildGetRequestValue.getClass().getDeclaredField(\"proxy\");\n        proxy.setAccessible(true);\n        return proxy.get(buildGetRequestValue);\n    }\n}\n"
  },
  {
    "path": "src/test/java/hudson/plugins/jira/JiraRestServiceTest.java",
    "content": "package hudson.plugins.jira;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.mockito.Mockito.any;\nimport static org.mockito.Mockito.anyInt;\nimport static org.mockito.Mockito.anyLong;\nimport static org.mockito.Mockito.doReturn;\nimport static org.mockito.Mockito.doThrow;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.spy;\n\nimport com.atlassian.jira.rest.client.api.RestClientException;\nimport com.atlassian.jira.rest.client.api.SearchRestClient;\nimport com.atlassian.jira.rest.client.api.domain.SearchResult;\nimport hudson.plugins.jira.extension.ExtendedJiraRestClient;\nimport io.atlassian.util.concurrent.Promise;\nimport java.net.URI;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.TimeoutException;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.Mockito;\n\nclass JiraRestServiceTest {\n\n    private final URI JIRA_URI = URI.create(\"http://example.com:8080/\");\n    private final String USERNAME = \"user\";\n    private final String PASSWORD = \"password\";\n    private ExtendedJiraRestClient client;\n    private SearchRestClient searchRestClient;\n    private Promise promise;\n    private SearchResult searchResult;\n\n    @BeforeEach\n    void createMocks() throws InterruptedException, ExecutionException, TimeoutException {\n        client = mock(ExtendedJiraRestClient.class);\n        searchRestClient = mock(SearchRestClient.class);\n        promise = mock(Promise.class);\n        searchResult = mock(SearchResult.class);\n\n        doReturn(searchRestClient).when(client).getSearchClient();\n        doReturn(promise).when(searchRestClient).searchJql(any(), any(), anyInt(), any());\n        doReturn(searchResult).when(promise).get(anyLong(), any());\n    }\n\n    @Test\n    void baseApiPath() {\n        JiraRestService service = new JiraRestService(JIRA_URI, client, USERNAME, PASSWORD, JiraSite.DEFAULT_TIMEOUT);\n        assertEquals(\"/\" + JiraRestService.BASE_API_PATH, service.getBaseApiPath());\n\n        URI uri = URI.create(\"https://example.com/path/to/jira\");\n        service = new JiraRestService(uri, client, USERNAME, PASSWORD, JiraSite.DEFAULT_TIMEOUT);\n        assertEquals(\"/path/to/jira/\" + JiraRestService.BASE_API_PATH, service.getBaseApiPath());\n    }\n\n    @Test\n    void getIssuesFromJqlSearchRestException() throws InterruptedException, ExecutionException, TimeoutException {\n        Throwable throwable = mock(Throwable.class);\n        JiraRestService service =\n                spy(new JiraRestService(JIRA_URI, client, USERNAME, PASSWORD, JiraSite.DEFAULT_TIMEOUT));\n        doThrow(new RestClientException(\"Verify Rest client exception\", throwable))\n                .when(promise)\n                .get(Mockito.anyLong(), Mockito.any());\n        assertThrows(RestClientException.class, () -> service.getIssuesFromJqlSearch(\"*\", null));\n    }\n}\n"
  },
  {
    "path": "src/test/java/hudson/plugins/jira/JiraSessionTest.java",
    "content": "package hudson.plugins.jira;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.hamcrest.Matchers.hasItem;\nimport static org.hamcrest.Matchers.hasProperty;\nimport static org.mockito.ArgumentMatchers.anyInt;\nimport static org.mockito.ArgumentMatchers.anyList;\nimport static org.mockito.ArgumentMatchers.anyString;\nimport static org.mockito.Mockito.spy;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport com.atlassian.jira.rest.client.api.RestClientException;\nimport com.atlassian.jira.rest.client.api.domain.Issue;\nimport com.atlassian.jira.rest.client.api.domain.Version;\nimport hudson.plugins.jira.extension.ExtendedVersion;\nimport java.io.IOException;\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.concurrent.ThreadLocalRandom;\nimport java.util.concurrent.TimeoutException;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\n\n@ExtendWith(MockitoExtension.class)\nclass JiraSessionTest {\n\n    private static final String PROJECT_KEY = \"myKey\";\n    private static final String QUERY = \"query\";\n\n    private JiraSession jiraSession = null;\n\n    @Mock\n    private JiraSite site;\n\n    @Mock\n    private final JiraRestService service = null;\n\n    @BeforeEach\n    void prepareMocks() throws IOException, InterruptedException {\n        jiraSession = spy(new JiraSession(site, service));\n    }\n\n    @Test\n    void replaceWithFixVersionByRegex() throws URISyntaxException, TimeoutException, RestClientException {\n        final ExtendedVersion newVersion =\n                new ExtendedVersion(new URI(\"self\"), 3L, \"v3.0\", null, false, false, null, null);\n        List<ExtendedVersion> myVersions = new ArrayList<>();\n        myVersions.add(newVersion);\n        when(jiraSession.getVersions(PROJECT_KEY)).thenReturn(myVersions);\n\n        ArrayList<Issue> issues = new ArrayList<>();\n        issues.add(getIssue(Arrays.asList(\"v1.0\"), 1L));\n        issues.add(getIssue(Arrays.asList(\"v1.0\", \"v2.0\", \"v2.0.0\"), 2L));\n        when(service.getIssuesFromJqlSearch(QUERY, 0)).thenReturn(issues);\n\n        jiraSession.replaceFixVersion(PROJECT_KEY, \"/v1.*/\", newVersion.getName(), QUERY);\n\n        ArgumentCaptor<String> issueKeys = ArgumentCaptor.forClass(String.class);\n        ArgumentCaptor<List> versionList = ArgumentCaptor.forClass(List.class);\n        verify(service, times(issues.size())).updateIssue(issueKeys.capture(), versionList.capture());\n\n        // First Issue, current FixVersion replaced by new one\n        assertThat(issueKeys.getAllValues().get(0), equalTo(issues.get(0).getKey()));\n        List<ExtendedVersion> firstIssueUpdatedFixVersions =\n                versionList.getAllValues().get(0);\n        assertThat(firstIssueUpdatedFixVersions.size(), equalTo(1));\n        assertThat(firstIssueUpdatedFixVersions.get(0).getName(), equalTo(newVersion.getName()));\n\n        // Second Issue, current FixVersion stays, new fixVersion added.\n        assertThat(issueKeys.getAllValues().get(1), equalTo(issues.get(1).getKey()));\n        List<ExtendedVersion> secondIssueUpdatedFixVersions =\n                versionList.getAllValues().get(1);\n        assertThat(secondIssueUpdatedFixVersions.size(), equalTo(3));\n\n        // Check that the collection contains versions with these names in any order\n        assertThat(secondIssueUpdatedFixVersions, hasItem(hasProperty(\"name\", equalTo(newVersion.getName()))));\n        assertThat(secondIssueUpdatedFixVersions, hasItem(hasProperty(\"name\", equalTo(\"v2.0\"))));\n        assertThat(secondIssueUpdatedFixVersions, hasItem(hasProperty(\"name\", equalTo(\"v2.0.0\"))));\n    }\n\n    @Test\n    void replaceFixVersion() throws URISyntaxException, TimeoutException, RestClientException {\n        final ExtendedVersion newVersion =\n                new ExtendedVersion(new URI(\"self\"), 3L, \"v3.0\", null, false, false, null, null);\n        List<ExtendedVersion> myVersions = new ArrayList<>();\n        myVersions.add(newVersion);\n        when(jiraSession.getVersions(PROJECT_KEY)).thenReturn(myVersions);\n\n        ArrayList<Issue> issues = new ArrayList<>();\n        issues.add(getIssue(Arrays.asList(\"v1.0\"), 1L));\n        issues.add(getIssue(Arrays.asList(\"v1.0\", \"v1.0.0\", \"v2.0.0\"), 2L));\n        issues.add(getIssue(null, 3L));\n        when(service.getIssuesFromJqlSearch(QUERY, 0)).thenReturn(issues);\n\n        jiraSession.replaceFixVersion(PROJECT_KEY, \"v1.0\", newVersion.getName(), QUERY);\n\n        ArgumentCaptor<String> issueKeys = ArgumentCaptor.forClass(String.class);\n        ArgumentCaptor<List> versionList = ArgumentCaptor.forClass(List.class);\n        verify(service, times(issues.size())).updateIssue(issueKeys.capture(), versionList.capture());\n\n        // First Issue, current FixVersion replaced by new one\n        assertThat(issueKeys.getAllValues().get(0), equalTo(issues.get(0).getKey()));\n        List<Version> firstIssueUpdatedFixVersions = versionList.getAllValues().get(0);\n        assertThat(firstIssueUpdatedFixVersions.size(), equalTo(1));\n        assertThat(firstIssueUpdatedFixVersions.get(0), equalTo(newVersion));\n\n        // Second Issue, current FixVersion stays, new fixVersion added.\n        assertThat(issueKeys.getAllValues().get(1), equalTo(issues.get(1).getKey()));\n        List<Version> secondIssueUpdatedFixVersions = versionList.getAllValues().get(1);\n        assertThat(secondIssueUpdatedFixVersions.size(), equalTo(3));\n        assertThat(secondIssueUpdatedFixVersions, hasItem(hasProperty(\"name\", equalTo(newVersion.getName()))));\n        assertThat(secondIssueUpdatedFixVersions, hasItem(hasProperty(\"name\", equalTo(\"v1.0.0\"))));\n        assertThat(secondIssueUpdatedFixVersions, hasItem(hasProperty(\"name\", equalTo(\"v2.0.0\"))));\n\n        // Third Issue, no FixVersion, new fixVersion added.\n        List<Version> thirdIssueVersions = versionList.getAllValues().get(2);\n        assertThat(thirdIssueVersions.size(), equalTo(1));\n        assertThat(thirdIssueVersions.get(0), equalTo(newVersion));\n    }\n\n    private Issue getIssue(List<String> versions, long id) throws URISyntaxException {\n        List<Version> fixVersions = null;\n        if (versions != null) {\n            fixVersions = new ArrayList<>();\n            for (String fixVersion : versions) {\n                fixVersions.add(new Version(\n                        new URI(\"self\"), ThreadLocalRandom.current().nextLong(), fixVersion, null, false, false, null));\n            }\n        }\n\n        return new Issue(\n                \"\",\n                new URI(\"\"),\n                PROJECT_KEY + id,\n                ThreadLocalRandom.current().nextLong(),\n                null,\n                null,\n                null,\n                null,\n                null,\n                null,\n                null,\n                null,\n                null,\n                null,\n                null,\n                null,\n                null,\n                fixVersions,\n                null,\n                null,\n                null,\n                null,\n                null,\n                null,\n                null,\n                null,\n                null,\n                null,\n                null,\n                null,\n                null,\n                null,\n                null);\n    }\n\n    @Test\n    void shouldAddVersionToAllIssues() throws Exception {\n        final ExtendedVersion newVersion =\n                new ExtendedVersion(new URI(\"self\"), 10L, \"v4.0\", null, false, false, null, null);\n        List<ExtendedVersion> myVersions = List.of(newVersion);\n        when(jiraSession.getVersions(PROJECT_KEY)).thenReturn(myVersions);\n\n        List<Issue> issues = new ArrayList<>();\n        issues.add(getIssue(Arrays.asList(\"v1.0\"), 1L));\n        issues.add(getIssue(Arrays.asList(\"v2.0\", \"v3.0\"), 2L));\n        when(service.getIssuesFromJqlSearch(QUERY, 0)).thenReturn(issues);\n\n        jiraSession.addFixVersion(PROJECT_KEY, newVersion.getName(), QUERY);\n\n        ArgumentCaptor<String> issueKeys = ArgumentCaptor.forClass(String.class);\n        ArgumentCaptor<List> versionList = ArgumentCaptor.forClass(List.class);\n        verify(service, times(issues.size())).updateIssue(issueKeys.capture(), versionList.capture());\n\n        // First issue: should have original + new version\n        List<Version> firstIssueVersions = versionList.getAllValues().get(0);\n        assertThat(firstIssueVersions, hasItem(hasProperty(\"name\", equalTo(\"v1.0\"))));\n        assertThat(firstIssueVersions, hasItem(hasProperty(\"name\", equalTo(\"v4.0\"))));\n        assertThat(firstIssueVersions.size(), equalTo(2));\n\n        // Second issue: should have both original + new version\n        List<Version> secondIssueVersions = versionList.getAllValues().get(1);\n        assertThat(secondIssueVersions, hasItem(hasProperty(\"name\", equalTo(\"v2.0\"))));\n        assertThat(secondIssueVersions, hasItem(hasProperty(\"name\", equalTo(\"v3.0\"))));\n        assertThat(secondIssueVersions, hasItem(hasProperty(\"name\", equalTo(\"v4.0\"))));\n        assertThat(secondIssueVersions.size(), equalTo(3));\n    }\n\n    @Test\n    void shouldAddVersionWhenNoFixVersions() throws Exception {\n        final ExtendedVersion newVersion =\n                new ExtendedVersion(new URI(\"self\"), 11L, \"v5.0\", null, false, false, null, null);\n        List<ExtendedVersion> myVersions = List.of(newVersion);\n        when(jiraSession.getVersions(PROJECT_KEY)).thenReturn(myVersions);\n\n        List<Issue> issues = List.of(getIssue(null, 3L));\n        when(service.getIssuesFromJqlSearch(QUERY, 0)).thenReturn(issues);\n\n        jiraSession.addFixVersion(PROJECT_KEY, newVersion.getName(), QUERY);\n\n        ArgumentCaptor<String> issueKeys = ArgumentCaptor.forClass(String.class);\n        ArgumentCaptor<List> versionList = ArgumentCaptor.forClass(List.class);\n        verify(service).updateIssue(issueKeys.capture(), versionList.capture());\n\n        List<Version> updatedVersions = versionList.getValue();\n        assertThat(updatedVersions.size(), equalTo(1));\n        assertThat(updatedVersions.get(0).getName(), equalTo(\"v5.0\"));\n    }\n\n    @Test\n    void shouldNotCallUpdateIfNoIssues() throws Exception {\n        final ExtendedVersion newVersion =\n                new ExtendedVersion(new URI(\"self\"), 12L, \"v6.0\", null, false, false, null, null);\n        List<ExtendedVersion> myVersions = List.of(newVersion);\n        when(jiraSession.getVersions(PROJECT_KEY)).thenReturn(myVersions);\n\n        when(service.getIssuesFromJqlSearch(QUERY, 0)).thenReturn(new ArrayList<>());\n\n        jiraSession.addFixVersion(PROJECT_KEY, newVersion.getName(), QUERY);\n\n        verify(service, times(0)).updateIssue(anyString(), anyList());\n    }\n\n    @Test\n    void shouldReturnIfVersionNotFound() throws Exception {\n        when(jiraSession.getVersions(PROJECT_KEY)).thenReturn(List.of());\n\n        jiraSession.addFixVersion(PROJECT_KEY, \"nonexistent\", QUERY);\n\n        verify(service, times(0)).getIssuesFromJqlSearch(anyString(), anyInt());\n        verify(service, times(0)).updateIssue(anyString(), anyList());\n    }\n}\n"
  },
  {
    "path": "src/test/java/hudson/plugins/jira/JiraSiteSecurity1029Test.java",
    "content": "package hudson.plugins.jira;\n\nimport static org.hamcrest.CoreMatchers.containsString;\nimport static org.hamcrest.CoreMatchers.equalTo;\nimport static org.hamcrest.CoreMatchers.nullValue;\nimport static org.hamcrest.MatcherAssert.assertThat;\n\nimport com.cloudbees.hudson.plugins.folder.Folder;\nimport com.cloudbees.plugins.credentials.CredentialsScope;\nimport com.cloudbees.plugins.credentials.CredentialsStore;\nimport com.cloudbees.plugins.credentials.SystemCredentialsProvider;\nimport com.cloudbees.plugins.credentials.domains.Domain;\nimport com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl;\nimport com.sun.net.httpserver.HttpExchange;\nimport com.sun.net.httpserver.HttpHandler;\nimport com.sun.net.httpserver.HttpServer;\nimport hudson.model.Item;\nimport hudson.model.User;\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.net.HttpURLConnection;\nimport java.net.InetAddress;\nimport java.net.InetSocketAddress;\nimport java.net.URI;\nimport java.net.URL;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Arrays;\nimport java.util.Base64;\nimport java.util.HashMap;\nimport jenkins.model.Jenkins;\nimport jenkins.security.ApiTokenProperty;\nimport net.sf.json.JSONObject;\nimport org.htmlunit.HttpMethod;\nimport org.htmlunit.Page;\nimport org.htmlunit.WebRequest;\nimport org.htmlunit.WebResponse;\nimport org.htmlunit.util.NameValuePair;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Test;\nimport org.jvnet.hudson.test.Issue;\nimport org.jvnet.hudson.test.JenkinsRule;\nimport org.jvnet.hudson.test.MockAuthorizationStrategy;\nimport org.jvnet.hudson.test.junit.jupiter.WithJenkins;\n\n@WithJenkins\npublic class JiraSiteSecurity1029Test {\n\n    private HttpServer server;\n    private URI serverUri;\n    private FakeJiraServlet servlet;\n\n    @Test\n    @Issue(\"SECURITY-1029\")\n    void cannotLeakCredentials(JenkinsRule j) throws Exception {\n        setupServer(j);\n\n        final String ADMIN = \"admin\";\n        final String USER = \"user\";\n        final String USER_FOLDER_CONFIGURE = \"folder_configure\";\n\n        j.jenkins.setCrumbIssuer(null);\n        j.jenkins.setSecurityRealm(j.createDummySecurityRealm());\n        j.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy()\n                .grant(Jenkins.ADMINISTER)\n                .everywhere()\n                .to(ADMIN)\n                .grant(Jenkins.READ, Item.READ)\n                .everywhere()\n                .to(USER)\n                .grant(Jenkins.READ, Item.READ, Item.CONFIGURE)\n                .everywhere()\n                .to(USER_FOLDER_CONFIGURE));\n\n        String credId_1 = \"cred-1-id\";\n        String credId_2 = \"cred-2-id\";\n        String credId_3 = \"cred-3-id\";\n\n        String pwd1 = \"pwd1\";\n        String pwd2 = \"pwd2\";\n        String pwd3 = \"pwd3\";\n\n        UsernamePasswordCredentialsImpl cred1 =\n                new UsernamePasswordCredentialsImpl(CredentialsScope.GLOBAL, credId_1, null, \"user1\", pwd1);\n        UsernamePasswordCredentialsImpl cred2 =\n                new UsernamePasswordCredentialsImpl(CredentialsScope.GLOBAL, credId_2, null, \"user2\", pwd2);\n        UsernamePasswordCredentialsImpl cred3 =\n                new UsernamePasswordCredentialsImpl(CredentialsScope.GLOBAL, credId_3, null, \"user3\", pwd3);\n\n        SystemCredentialsProvider systemProvider = SystemCredentialsProvider.getInstance();\n        systemProvider.getCredentials().add(cred1);\n        systemProvider.getCredentials().add(cred2);\n        systemProvider.save();\n\n        User admin = User.getById(ADMIN, true);\n        admin.addProperty(new ApiTokenProperty());\n        admin.getProperty(ApiTokenProperty.class).changeApiToken();\n        User user = User.getById(USER, true);\n        user.addProperty(new ApiTokenProperty());\n        user.getProperty(ApiTokenProperty.class).changeApiToken();\n\n        User userFolderConfigure = User.getById(USER_FOLDER_CONFIGURE, true);\n        userFolderConfigure.addProperty(new ApiTokenProperty());\n        userFolderConfigure.getProperty(ApiTokenProperty.class).changeApiToken();\n\n        { // as an admin I should be able to validate my url / credentials\n            JenkinsRule.WebClient wc = j.createWebClient();\n            wc.getOptions().setThrowExceptionOnFailingStatusCode(false);\n            wc = wc.withBasicApiToken(admin);\n\n            String jiraSiteValidateUrl = j.getURL() + \"descriptorByName/\" + JiraSite.class.getName() + \"/validate\";\n            WebRequest request = new WebRequest(new URL(jiraSiteValidateUrl), HttpMethod.POST);\n            request.setRequestParameters(Arrays.asList(\n                    new NameValuePair(\"threadExecutorNumber\", \"1\"),\n                    new NameValuePair(\"url\", serverUri.toString()),\n                    new NameValuePair(\"credentialsId\", credId_1),\n                    new NameValuePair(\"useHTTPAuth\", \"true\")));\n\n            Page page = wc.getPage(request);\n            assertThat(page.getWebResponse().getStatusCode(), equalTo(200));\n            assertThat(servlet.getPasswordAndReset(), equalTo(pwd1));\n        }\n        { // as an user with just read access, I may not be able to leak any credentials\n            JenkinsRule.WebClient wc = j.createWebClient();\n            wc.getOptions().setThrowExceptionOnFailingStatusCode(false);\n            wc.withBasicApiToken(user);\n\n            String jiraSiteValidateUrl = j.getURL() + \"descriptorByName/\" + JiraSite.class.getName() + \"/validate\";\n            WebRequest request = new WebRequest(new URL(jiraSiteValidateUrl), HttpMethod.POST);\n            request.setRequestParameters(Arrays.asList(\n                    new NameValuePair(\"threadExecutorNumber\", \"1\"),\n                    new NameValuePair(\"url\", serverUri.toString()),\n                    new NameValuePair(\"credentialsId\", credId_2),\n                    new NameValuePair(\"useHTTPAuth\", \"true\")));\n\n            Page page = wc.getPage(request);\n            // to avoid trouble, we always validate when the user has not the good permission\n            assertThat(page.getWebResponse().getStatusCode(), equalTo(403));\n            assertThat(servlet.getPasswordAndReset(), nullValue());\n        }\n\n        { // as an user with just read access, I may not be able to leak any credentials in folder\n            Folder folder = j.jenkins.createProject(\n                    Folder.class, \"folder\" + j.jenkins.getItems().size());\n\n            JenkinsRule.WebClient wc = j.createWebClient();\n            wc.getOptions().setThrowExceptionOnFailingStatusCode(false);\n            wc.withBasicApiToken(user);\n\n            String jiraSiteValidateUrl = j.jenkins.getRootUrl() + folder.getUrl() + \"descriptorByName/\"\n                    + JiraSite.class.getName() + \"/validate\";\n\n            WebRequest request = new WebRequest(new URL(jiraSiteValidateUrl), HttpMethod.POST);\n            request.setRequestParameters(Arrays.asList(\n                    new NameValuePair(\"threadExecutorNumber\", \"1\"),\n                    new NameValuePair(\"url\", serverUri.toString()),\n                    new NameValuePair(\"credentialsId\", credId_2),\n                    new NameValuePair(\"useHTTPAuth\", \"true\")));\n\n            Page page = wc.getPage(request);\n            // to avoid trouble, we always validate when the user has not the good permission\n            assertThat(page.getWebResponse().getStatusCode(), equalTo(403));\n            assertThat(servlet.getPasswordAndReset(), nullValue());\n        }\n\n        { // as an user with just read access, I may not be able to leak any credentials with fake id in a folder\n            Folder folder = j.jenkins.createProject(\n                    Folder.class, \"folder\" + j.jenkins.getItems().size());\n\n            JenkinsRule.WebClient wc = j.createWebClient();\n            wc.getOptions().setThrowExceptionOnFailingStatusCode(false);\n            wc.withBasicApiToken(userFolderConfigure);\n\n            String jiraSiteValidateUrl = j.jenkins.getRootUrl() + folder.getUrl() + \"descriptorByName/\"\n                    + JiraSite.class.getName() + \"/validate\";\n            WebRequest request = new WebRequest(new URL(jiraSiteValidateUrl), HttpMethod.POST);\n            request.setRequestParameters(Arrays.asList(\n                    new NameValuePair(\"threadExecutorNumber\", \"1\"),\n                    new NameValuePair(\"url\", serverUri.toString()),\n                    new NameValuePair(\"credentialsId\", \"aussie-beer-is-the-best\"), // use a non existing id on purpose\n                    new NameValuePair(\"useHTTPAuth\", \"true\")));\n\n            Page page = wc.getPage(request);\n            WebResponse webResponse = page.getWebResponse();\n            assertThat(webResponse.getStatusCode(), equalTo(200));\n            assertThat(webResponse.getContentAsString(), containsString(\"Cannot validate configuration\"));\n            assertThat(servlet.getPasswordAndReset(), nullValue());\n        }\n\n        { // as an user with configure access, I can access\n            Folder folder = j.jenkins.createProject(\n                    Folder.class, \"folder\" + j.jenkins.getItems().size());\n\n            JenkinsRule.WebClient wc = j.createWebClient();\n            wc.getOptions().setThrowExceptionOnFailingStatusCode(false);\n            wc.withBasicApiToken(userFolderConfigure);\n\n            String jiraSiteValidateUrl = j.jenkins.getRootUrl() + folder.getUrl() + \"descriptorByName/\"\n                    + JiraSite.class.getName() + \"/validate\";\n\n            WebRequest request = new WebRequest(new URL(jiraSiteValidateUrl), HttpMethod.POST);\n            request.setRequestParameters(Arrays.asList(\n                    new NameValuePair(\"threadExecutorNumber\", \"1\"),\n                    new NameValuePair(\"url\", serverUri.toString()),\n                    new NameValuePair(\"credentialsId\", credId_2),\n                    new NameValuePair(\"useHTTPAuth\", \"true\")));\n\n            Page page = wc.getPage(request);\n            // to avoid trouble, we always validate when the user has the good permission\n            assertThat(page.getWebResponse().getStatusCode(), equalTo(200));\n            assertThat(servlet.getPasswordAndReset(), equalTo(pwd2));\n        }\n\n        { // as an user with folder access, I can access\n            Folder folder = j.jenkins.createProject(\n                    Folder.class, \"folder\" + j.jenkins.getItems().size());\n\n            CredentialsStore folderStore = JiraFolderPropertyTest.getFolderStore(folder);\n            folderStore.addCredentials(Domain.global(), cred3);\n\n            JenkinsRule.WebClient wc = j.createWebClient();\n            wc.getOptions().setThrowExceptionOnFailingStatusCode(false);\n            wc.withBasicApiToken(userFolderConfigure);\n\n            String jiraSiteValidateUrl = j.jenkins.getRootUrl() + folder.getUrl() + \"descriptorByName/\"\n                    + JiraSite.class.getName() + \"/validate\";\n\n            WebRequest request = new WebRequest(new URL(jiraSiteValidateUrl), HttpMethod.POST);\n            request.setRequestParameters(Arrays.asList(\n                    new NameValuePair(\"threadExecutorNumber\", \"1\"),\n                    new NameValuePair(\"url\", serverUri.toString()),\n                    new NameValuePair(\"credentialsId\", credId_3),\n                    new NameValuePair(\"useHTTPAuth\", \"true\")));\n\n            Page page = wc.getPage(request);\n            // to avoid trouble, we always validate when the user has the good permission\n            assertThat(page.getWebResponse().getStatusCode(), equalTo(200));\n            assertThat(servlet.getPasswordAndReset(), equalTo(pwd3));\n        }\n    }\n\n    public void setupServer(JenkinsRule j) throws Exception {\n        // auto-bind to available port\n        server = HttpServer.create(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 0);\n\n        servlet = new FakeJiraServlet(j);\n\n        server.createContext(\"/\", servlet);\n\n        server.start();\n\n        InetSocketAddress address = server.getAddress();\n        serverUri = new URI(String.format(\"http://%s:%d/\", address.getHostString(), address.getPort()));\n        servlet.setServerUrl(serverUri);\n    }\n\n    @AfterEach\n    void stopEmbeddedHttpServer() {\n        server.stop(1);\n    }\n\n    private static class FakeJiraServlet implements HttpHandler {\n\n        private JenkinsRule jenkinsRule;\n        private URI serverUri;\n\n        private String pwdCollected;\n\n        FakeJiraServlet(JenkinsRule jenkinsRule) {\n            this.jenkinsRule = jenkinsRule;\n        }\n\n        public void setServerUrl(URI serverUri) {\n            this.serverUri = serverUri;\n        }\n\n        public String getPasswordAndReset() {\n            String result = pwdCollected;\n            this.pwdCollected = null;\n            return result;\n        }\n\n        @Override\n        public void handle(HttpExchange he) throws IOException {\n            String path = he.getRequestURI().getPath();\n\n            String authBasicBase64 = he.getRequestHeaders().getFirst(\"Authorization\");\n            String authBase64 = authBasicBase64.substring(\"Basic \".length());\n            String auth = new String(Base64.getDecoder().decode(authBase64), StandardCharsets.UTF_8);\n            String[] authArray = auth.split(\":\");\n            String user = authArray[0];\n            String pwd = authArray[1];\n\n            this.pwdCollected = pwd;\n\n            try {\n                if (\"GET\".equals(he.getRequestMethod()) && \"/rest/api/latest/mypermissions\".equals(path)) {\n                    myPermissions(he);\n                } else {\n                    he.sendResponseHeaders(HttpURLConnection.HTTP_NOT_FOUND, -1);\n                }\n            } finally {\n                he.close();\n            }\n        }\n\n        private void myPermissions(HttpExchange he) throws IOException {\n            Object body = new HashMap<String, Object>() {\n                {\n                    put(\"permissions\", new HashMap<String, Object>() {\n                        {\n                            put(\"perm_1\", new HashMap<String, Object>() {\n                                {\n                                    put(\"id\", 1);\n                                    put(\"key\", \"perm_key\");\n                                    put(\"name\", \"perm_name\");\n                                    put(\"description\", null);\n                                    put(\"havePermission\", \"true\");\n                                }\n                            });\n                        }\n                    });\n                }\n            };\n\n            String response = JSONObject.fromObject(body).toString();\n            byte[] bytes = response.getBytes(StandardCharsets.UTF_8);\n            he.sendResponseHeaders(HttpURLConnection.HTTP_OK, bytes.length);\n            try (OutputStream os = he.getResponseBody()) {\n                os.write(bytes);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/hudson/plugins/jira/JiraSiteTest.java",
    "content": "package hudson.plugins.jira;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.containsString;\nimport static org.hamcrest.Matchers.empty;\nimport static org.hamcrest.Matchers.not;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.Mockito.doReturn;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.spy;\nimport static org.mockito.Mockito.when;\n\nimport com.cloudbees.hudson.plugins.folder.AbstractFolderProperty;\nimport com.cloudbees.hudson.plugins.folder.AbstractFolderPropertyDescriptor;\nimport com.cloudbees.hudson.plugins.folder.Folder;\nimport com.cloudbees.plugins.credentials.CredentialsProvider;\nimport com.cloudbees.plugins.credentials.CredentialsScope;\nimport com.cloudbees.plugins.credentials.SystemCredentialsProvider;\nimport com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials;\nimport com.cloudbees.plugins.credentials.domains.Domain;\nimport com.cloudbees.plugins.credentials.domains.DomainSpecification;\nimport com.cloudbees.plugins.credentials.domains.HostnameSpecification;\nimport com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl;\nimport hudson.model.Descriptor.FormException;\nimport hudson.model.FreeStyleProject;\nimport hudson.model.Job;\nimport hudson.plugins.jira.model.JiraIssue;\nimport hudson.util.DescribableList;\nimport hudson.util.Secret;\nimport hudson.util.XStream2;\nimport java.io.IOException;\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport jenkins.model.Jenkins;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.jvnet.hudson.test.Issue;\nimport org.jvnet.hudson.test.JenkinsRule;\nimport org.jvnet.hudson.test.WithoutJenkins;\nimport org.jvnet.hudson.test.junit.jupiter.WithJenkins;\n\n@WithJenkins\nclass JiraSiteTest {\n\n    private static final String ANY_USER = \"Kohsuke\";\n    private static final String ANY_PASSWORD = \"Kawaguchi\";\n\n    private URL validPrimaryUrl;\n\n    private URL exampleOrg;\n\n    @BeforeEach\n    void init() throws MalformedURLException {\n        validPrimaryUrl = new URL(\"https://nonexistent.url\");\n        exampleOrg = new URL(\"https://example.org/\");\n    }\n\n    @Test\n    void createSessionWithProvidedCredentials(JenkinsRule r) throws FormException {\n        JiraSite site = new JiraSite(\n                validPrimaryUrl,\n                null,\n                new UsernamePasswordCredentialsImpl(CredentialsScope.SYSTEM, null, null, ANY_USER, ANY_PASSWORD),\n                false,\n                false,\n                null,\n                false,\n                null,\n                null,\n                true);\n        site.setTimeout(1);\n        JiraSession session = site.getSession(null);\n        assertNotNull(session);\n        assertEquals(session, site.getSession(null));\n    }\n\n    @Test\n    @Issue(\"JENKINS-64083\")\n    void createSessionWithGlobalCredentials(JenkinsRule r) throws FormException {\n        JiraSite site = new JiraSite(\n                validPrimaryUrl,\n                null,\n                new UsernamePasswordCredentialsImpl(CredentialsScope.SYSTEM, null, null, ANY_USER, ANY_PASSWORD),\n                false,\n                false,\n                null,\n                false,\n                null,\n                null,\n                true);\n        site.setTimeout(1);\n        JiraSession session = site.getSession(mock(Job.class));\n        assertNotNull(session);\n        assertEquals(session, site.getSession(null));\n    }\n\n    @Test\n    void createSessionReturnsNullIfCredentialsIsNull(JenkinsRule r) throws FormException {\n        JiraSite site = new JiraSite(\n                validPrimaryUrl,\n                null,\n                (StandardUsernamePasswordCredentials) null,\n                false,\n                false,\n                null,\n                false,\n                null,\n                null,\n                true);\n        site.setTimeout(1);\n        JiraSession session = site.getSession(null);\n        assertEquals(session, site.getSession(null));\n        assertNull(session);\n    }\n\n    @Test\n    void deserializeMigrateCredentials(JenkinsRule j) throws MalformedURLException, FormException {\n        JiraSiteOld old = new JiraSiteOld(\n                validPrimaryUrl, null, ANY_USER, ANY_PASSWORD, false, false, null, false, null, null, true);\n\n        XStream2 xStream2 = new XStream2();\n        String xml = xStream2.toXML(old);\n        // trick to get old version config of JiraSite\n        xml = xml.replace(\n                this.getClass().getName() + \"_-\" + JiraSiteOld.class.getSimpleName(), JiraSite.class.getName());\n\n        assertThat(xml, containsString(validPrimaryUrl.toExternalForm()));\n        assertThat(xml, containsString(\"userName\"));\n        assertThat(xml, containsString(\"password\"));\n        assertThat(xml, not(containsString(\"credentialsId\")));\n        assertThat(\n                CredentialsProvider.lookupStores(j.jenkins).iterator().next().getCredentials(Domain.global()), empty());\n\n        JiraSite site = (JiraSite) xStream2.fromXML(xml);\n\n        assertNotNull(site);\n        assertNotNull(site.credentialsId);\n        assertEquals(\n                ANY_USER,\n                CredentialsHelper.lookupSystemCredentials(site.credentialsId, null)\n                        .getUsername());\n        assertEquals(\n                ANY_PASSWORD,\n                CredentialsHelper.lookupSystemCredentials(site.credentialsId, null)\n                        .getPassword()\n                        .getPlainText());\n    }\n\n    @Test\n    void deserializeNormal(JenkinsRule j) throws IOException, FormException {\n        Domain domain = new Domain(\n                \"example\",\n                \"test domain\",\n                Arrays.<DomainSpecification>asList(new HostnameSpecification(\"example.org\", null)));\n        StandardUsernamePasswordCredentials c =\n                new UsernamePasswordCredentialsImpl(CredentialsScope.SYSTEM, null, null, ANY_USER, ANY_PASSWORD);\n        CredentialsProvider.lookupStores(j.jenkins).iterator().next().addDomain(domain, c);\n\n        JiraSite site = new JiraSite(exampleOrg, null, c.getId(), false, false, null, false, null, null, true);\n        site.setUseBearerAuth(true);\n\n        XStream2 xStream2 = new XStream2();\n        String xml = xStream2.toXML(site);\n\n        assertThat(xml, not(containsString(\"userName\")));\n        assertThat(xml, not(containsString(\"password\")));\n        assertThat(xml, containsString(\"credentialsId\"));\n\n        JiraSite site1 = (JiraSite) xStream2.fromXML(xml);\n        assertNotNull(site1.credentialsId);\n        assertTrue(site1.useBearerAuth);\n    }\n\n    @WithoutJenkins\n    @Test\n    void deserializeWithoutCredentials() {\n        JiraSite site = new JiraSite(exampleOrg, null, (String) null, false, false, null, false, null, null, true);\n\n        XStream2 xStream2 = new XStream2();\n        String xml = xStream2.toXML(site);\n\n        assertThat(xml, not(containsString(\"credentialsId\")));\n\n        JiraSite site1 = (JiraSite) xStream2.fromXML(xml);\n\n        assertNotNull(site1.url);\n        assertEquals(exampleOrg, site1.url);\n        assertNull(site1.credentialsId);\n    }\n\n    private static class JiraSiteOld extends JiraSite {\n        public String userName;\n        public Secret password;\n\n        JiraSiteOld(\n                URL url,\n                URL alternativeUrl,\n                String userName,\n                String password,\n                boolean supportsWikiStyleComment,\n                boolean recordScmChanges,\n                String userPattern,\n                boolean updateJiraIssueForAllStatus,\n                String groupVisibility,\n                String roleVisibility,\n                boolean useHTTPAuth)\n                throws FormException {\n            super(\n                    url,\n                    alternativeUrl,\n                    (StandardUsernamePasswordCredentials) null,\n                    supportsWikiStyleComment,\n                    recordScmChanges,\n                    userPattern,\n                    updateJiraIssueForAllStatus,\n                    groupVisibility,\n                    roleVisibility,\n                    useHTTPAuth);\n            this.userName = userName;\n            this.password = Secret.fromString(password);\n        }\n    }\n\n    @Test\n    @WithoutJenkins\n    void alternativeURLNotNull() throws FormException {\n        JiraSite site = new JiraSite(\n                validPrimaryUrl,\n                exampleOrg,\n                (StandardUsernamePasswordCredentials) null,\n                false,\n                false,\n                null,\n                false,\n                null,\n                null,\n                true);\n        assertNotNull(site.getAlternativeUrl());\n        assertEquals(exampleOrg, site.getAlternativeUrl());\n    }\n\n    @Test\n    @WithoutJenkins\n    void ensureUrlEndsWithSlash() {\n        JiraSite jiraSite = new JiraSite(validPrimaryUrl.toExternalForm());\n        jiraSite.setAlternativeUrl(exampleOrg.toExternalForm());\n        assertTrue(jiraSite.getUrl().toExternalForm().endsWith(\"/\"));\n        assertTrue(jiraSite.getAlternativeUrl().toExternalForm().endsWith(\"/\"));\n        URL url1 = JiraSite.toURL(validPrimaryUrl.toExternalForm());\n        URL url2 = JiraSite.toURL(exampleOrg.toExternalForm());\n        assertTrue(url1.toExternalForm().endsWith(\"/\"));\n        assertTrue(url2.toExternalForm().endsWith(\"/\"));\n    }\n\n    @Test\n    @WithoutJenkins\n    void urlNulls() {\n        JiraSite jiraSite = new JiraSite(validPrimaryUrl.toExternalForm());\n        jiraSite.setAlternativeUrl(\" \");\n        assertNotNull(jiraSite.getUrl());\n        assertNull(jiraSite.getAlternativeUrl());\n    }\n\n    @Test\n    @WithoutJenkins\n    void toUrlConvertsEmptyStringToNull() {\n        URL emptyString = JiraSite.toURL(\"\");\n        assertNull(emptyString);\n    }\n\n    @Test\n    @WithoutJenkins\n    void toUrlConvertsOnlyWhitespaceToNull() {\n        URL whitespace = JiraSite.toURL(\" \");\n        assertNull(whitespace);\n    }\n\n    @WithoutJenkins\n    @Test\n    void ensureMainUrlIsMandatory() {\n        assertThrows(AssertionError.class, () -> {\n            new JiraSite(\"\");\n        });\n    }\n\n    @Test\n    @WithoutJenkins\n    void ensureAlternativeUrlIsNotMandatory() {\n        JiraSite jiraSite = new JiraSite(validPrimaryUrl.toExternalForm());\n        jiraSite.setAlternativeUrl(\"\");\n        assertNull(jiraSite.getAlternativeUrl());\n    }\n\n    @WithoutJenkins\n    @Test\n    void malformedUrl() {\n        assertThrows(AssertionError.class, () -> {\n            new JiraSite(\"malformed.url\");\n        });\n    }\n\n    @WithoutJenkins\n    @Test\n    void malformedAlternativeUrl() {\n        JiraSite jiraSite = new JiraSite(validPrimaryUrl.toExternalForm());\n        assertThrows(AssertionError.class, () -> jiraSite.setAlternativeUrl(\"malformed.url\"));\n    }\n\n    @Test\n    @WithoutJenkins\n    void credentialsAreNullByDefault() {\n        JiraSite jiraSite = new JiraSite(exampleOrg.toExternalForm());\n        jiraSite.setCredentialsId(\"\");\n        assertNull(jiraSite.getCredentialsId());\n    }\n\n    @Test\n    void credentials(JenkinsRule r) throws Exception {\n        JiraSite jiraSite = new JiraSite(exampleOrg.toExternalForm());\n        String cred = \"cred-1\";\n        String user = \"user1\";\n        String pwd = \"pwd1\";\n\n        UsernamePasswordCredentialsImpl credentials =\n                new UsernamePasswordCredentialsImpl(CredentialsScope.GLOBAL, cred, null, user, pwd);\n\n        SystemCredentialsProvider systemProvider = SystemCredentialsProvider.getInstance();\n        systemProvider.getCredentials().add(credentials);\n        systemProvider.save();\n        jiraSite.setCredentialsId(cred);\n        assertNotNull(jiraSite.getCredentialsId());\n        assertEquals(\n                credentials.getUsername(),\n                CredentialsHelper.lookupSystemCredentials(cred, null).getUsername());\n        assertEquals(\n                credentials.getPassword(),\n                CredentialsHelper.lookupSystemCredentials(cred, null).getPassword());\n    }\n\n    @Test\n    @WithoutJenkins\n    void siteAsProjectProperty() throws Exception {\n        JiraSite jiraSite = new JiraSite(new URL(\"https://foo.org/\").toExternalForm());\n        Job<?, ?> job = mock(Job.class);\n        JiraProjectProperty jpp = mock(JiraProjectProperty.class);\n        when(job.getProperty(JiraProjectProperty.class)).thenReturn(jpp);\n        when(jpp.getSite()).thenReturn(jiraSite);\n\n        assertEquals(jiraSite.getUrl(), JiraSite.get(job).getUrl());\n    }\n\n    @Test\n    void projectPropertySiteAndParentBothNull(JenkinsRule j) {\n        JiraGlobalConfiguration jiraGlobalConfiguration = mock(JiraGlobalConfiguration.class);\n        Job<?, ?> job = mock(Job.class);\n        JiraProjectProperty jpp = mock(JiraProjectProperty.class);\n\n        when(job.getProperty(JiraProjectProperty.class)).thenReturn(jpp);\n        when(jpp.getSite()).thenReturn(null);\n        when(job.getParent()).thenReturn(null);\n        when(jiraGlobalConfiguration.getSites()).thenReturn(Collections.emptyList());\n\n        assertNull(JiraSite.get(job));\n    }\n\n    @Test\n    void noProjectProperty(JenkinsRule j) throws Exception {\n        JiraGlobalConfiguration.get().setSites(null);\n        Job<?, ?> job = j.jenkins.createProject(FreeStyleProject.class, \"foo\");\n        assertNull(JiraSite.get(job));\n    }\n\n    @Test\n    void noProjectPropertyUpFoldersWithNoProperty(JenkinsRule j) throws Exception {\n        JiraGlobalConfiguration jiraGlobalConfiguration = mock(JiraGlobalConfiguration.class);\n\n        Folder folder2 = spy(createFolder(j, null));\n        Folder folder1 = spy(createFolder(j, folder2));\n        Job<?, ?> job = folder1.createProject(FreeStyleProject.class, \"foo\");\n\n        DescribableList<AbstractFolderProperty<?>, AbstractFolderPropertyDescriptor> folder1Properties =\n                spy(new DescribableList(Jenkins.get()));\n        DescribableList<AbstractFolderProperty<?>, AbstractFolderPropertyDescriptor> folder2Properties =\n                spy(new DescribableList(Jenkins.get()));\n\n        doReturn(folder1Properties).when(folder1).getProperties();\n        doReturn(folder2Properties).when(folder2).getProperties();\n        doReturn(Collections.emptyList()).when(jiraGlobalConfiguration).getSites();\n\n        assertNull(JiraSite.get(job));\n    }\n\n    @Test\n    void noProjectPropertyFindFolderPropertyWithNullZeroLengthAndValidSites(JenkinsRule j) throws Exception {\n        JiraSite jiraSite1 = new JiraSite(new URL(\"https://example1.org/\").toExternalForm());\n        JiraSite jiraSite2 = new JiraSite(new URL(\"https://example2.org/\").toExternalForm());\n\n        Folder folder1 = spy(createFolder(j, null));\n        Job job = folder1.createProject(FreeStyleProject.class, \"foo\");\n        DescribableList<AbstractFolderProperty<?>, AbstractFolderPropertyDescriptor> folder1Properties =\n                new DescribableList(Jenkins.get());\n\n        Folder folder2 = spy(createFolder(j, folder1));\n        DescribableList<AbstractFolderProperty<?>, AbstractFolderPropertyDescriptor> folder2Properties =\n                new DescribableList(Jenkins.get());\n        JiraFolderProperty jfp = new JiraFolderProperty();\n        jfp.setSites(Arrays.asList(jiraSite2, jiraSite1));\n        folder1Properties.add(jfp);\n        folder1.addProperty(folder1Properties.get(JiraFolderProperty.class));\n        folder2Properties.add(jfp);\n        folder2.addProperty(folder2Properties.get(JiraFolderProperty.class));\n\n        assertEquals(jiraSite2.getUrl(), JiraSite.get(job).getUrl());\n    }\n\n    @Test\n    void siteConfiguredGlobally(JenkinsRule j) throws Exception {\n        JiraSite jiraSite = new JiraSite(new URL(\"https://foo.org/\").toExternalForm());\n        JiraGlobalConfiguration.get().setSites(Collections.singletonList(jiraSite));\n        Job<?, ?> job = mock(Job.class);\n        doReturn(null).when(job).getProperty(JiraProjectProperty.class);\n\n        assertEquals(jiraSite.getUrl(), JiraSite.get(job).getUrl());\n    }\n\n    @Test\n    @WithoutJenkins\n    void getIssueWithoutSession() throws Exception {\n        JiraSite jiraSite = new JiraSite(new URL(\"https://foo.org/\").toExternalForm());\n        // Verify that no session will be created\n        assertNull(jiraSite.getSession(null));\n        JiraIssue issue = jiraSite.getIssue(\"JIRA-1235\");\n        assertNull(issue);\n    }\n\n    @Test\n    @WithoutJenkins\n    void ensureMaxIssuesFromJqlEndsUpMaxAllowed() throws MalformedURLException {\n        JiraSite jiraSite = new JiraSite(new URL(\"https://example1.org/\").toExternalForm());\n        jiraSite.setMaxIssuesFromJqlSearch(6000);\n        assertEquals(5000, jiraSite.getMaxIssuesFromJqlSearch());\n    }\n\n    private Folder createFolder(JenkinsRule j, Folder folder) throws IOException {\n        return folder == null\n                ? j.jenkins.createProject(\n                        Folder.class, \"folder\" + j.jenkins.getItems().size())\n                : folder.createProject(\n                        Folder.class, \"folder\" + j.jenkins.getItems().size());\n    }\n}\n"
  },
  {
    "path": "src/test/java/hudson/plugins/jira/JiraVersionCreatorBuilderTest.java",
    "content": "package hudson.plugins.jira;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.is;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.doReturn;\nimport static org.mockito.Mockito.mock;\n\nimport hudson.model.Result;\nimport hudson.util.XStream2;\nimport java.util.Collections;\nimport org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition;\nimport org.jenkinsci.plugins.workflow.job.WorkflowJob;\nimport org.jenkinsci.plugins.workflow.job.WorkflowRun;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.jvnet.hudson.test.JenkinsRule;\nimport org.jvnet.hudson.test.WithoutJenkins;\nimport org.jvnet.hudson.test.junit.jupiter.WithJenkins;\n\nclass JiraVersionCreatorBuilderTest {\n\n    private JiraSite site;\n\n    private JiraSession session;\n\n    private final XStream2 xStream2 = new XStream2();\n\n    @BeforeEach\n    void createMocks() {\n        site = mock(JiraSite.class);\n        session = mock(JiraSession.class);\n    }\n\n    @WithJenkins\n    @Test\n    void testPipelineWithJiraSite(JenkinsRule r) throws Exception {\n        JiraGlobalConfiguration jiraGlobalConfiguration = JiraGlobalConfiguration.get();\n        jiraGlobalConfiguration.setSites(Collections.singletonList(site));\n        doReturn(session).when(site).getSession(any());\n        WorkflowJob job = r.createProject(WorkflowJob.class);\n        job.setDefinition(new CpsFlowDefinition(\"\"\"\n                        jiraCreateVersion(jiraVersion: 'Version', jiraProjectKey: 'project-key')\n                \"\"\", true));\n        WorkflowRun b = r.buildAndAssertStatus(Result.SUCCESS, job);\n        r.assertLogContains(\"[Jira] Creating version Version in project project-key.\", b);\n    }\n\n    @Test\n    @WithoutJenkins\n    void readResolveSetsFailIfAlreadyExistsWhenMissingInConfig() {\n        String xml = \"\"\"\n    <hudson.plugins.jira.JiraVersionCreatorBuilder>\n      <jiraVersion>1.0</jiraVersion>\n      <jiraProjectKey>PROJ</jiraProjectKey>\n    </hudson.plugins.jira.JiraVersionCreatorBuilder>\n    \"\"\";\n        JiraVersionCreatorBuilder builder = (JiraVersionCreatorBuilder) xStream2.fromXML(xml);\n\n        assertTrue(builder.isFailIfAlreadyExists());\n\n        xml = \"\"\"\n    <hudson.plugins.jira.JiraVersionCreator>\n      <jiraVersion>1.2</jiraVersion>\n      <jiraProjectKey>PROJ</jiraProjectKey>\n    </hudson.plugins.jira.JiraVersionCreator>\n    \"\"\";\n        JiraVersionCreator notifier = (JiraVersionCreator) xStream2.fromXML(xml);\n\n        assertThat(notifier.getJiraProjectKey(), is(\"PROJ\"));\n        assertThat(notifier.getJiraVersion(), is(\"1.2\"));\n        assertTrue(notifier.isFailIfAlreadyExists());\n    }\n\n    @Test\n    @WithoutJenkins\n    void readResolvePresentInConfig() {\n        String xml = \"\"\"\n    <hudson.plugins.jira.JiraVersionCreatorBuilder>\n      <jiraVersion>1.0</jiraVersion>\n      <jiraProjectKey>PROJ</jiraProjectKey>\n      <failIfAlreadyExists>false</failIfAlreadyExists>\n    </hudson.plugins.jira.JiraVersionCreatorBuilder>\n    \"\"\";\n        JiraVersionCreatorBuilder builder = (JiraVersionCreatorBuilder) xStream2.fromXML(xml);\n\n        assertFalse(builder.isFailIfAlreadyExists());\n\n        xml = \"\"\"\n    <hudson.plugins.jira.JiraVersionCreator>\n      <jiraVersion>1.0</jiraVersion>\n      <jiraProjectKey>PROJ</jiraProjectKey>\n      <failIfAlreadyExists>false</failIfAlreadyExists>\n    </hudson.plugins.jira.JiraVersionCreator>\n    \"\"\";\n        JiraVersionCreator notifier = (JiraVersionCreator) xStream2.fromXML(xml);\n\n        assertFalse(notifier.isFailIfAlreadyExists());\n    }\n}\n"
  },
  {
    "path": "src/test/java/hudson/plugins/jira/MailResolverDisabledTest.java",
    "content": "/*\n * The MIT License\n *\n * Copyright (c) 2015 schristou88\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\npackage hudson.plugins.jira;\n\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.core.IsNull.nullValue;\n\nimport hudson.model.User;\nimport java.util.Collections;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.jvnet.hudson.test.JenkinsRule;\nimport org.jvnet.hudson.test.junit.jupiter.WithJenkins;\n\n/**\n * @author schristou88\n */\n@WithJenkins\nclass MailResolverDisabledTest {\n\n    @BeforeAll\n    static void setUp() throws Exception {\n        System.setProperty(\"hudson.plugins.jira.JiraMailAddressResolver.disabled\", \"true\");\n    }\n\n    @Test\n    void disableEmailResolver(JenkinsRule r) {\n        User user = User.get(\"bob\", true, Collections.emptyMap());\n        assertThat(JiraMailAddressResolver.resolve(user), is(nullValue()));\n    }\n}\n"
  },
  {
    "path": "src/test/java/hudson/plugins/jira/MailResolverWithExtensionTest.java",
    "content": "/*\n * The MIT License\n *\n * Copyright (c) 2015 schristou88\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\npackage hudson.plugins.jira;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.is;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.doReturn;\n\nimport edu.umd.cs.findbugs.annotations.NonNull;\nimport hudson.model.User;\nimport hudson.security.HudsonPrivateSecurityRealm;\nimport java.net.URI;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\nimport jenkins.model.Jenkins;\nimport jenkins.security.SecurityListener;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.jvnet.hudson.test.JenkinsRule;\nimport org.jvnet.hudson.test.TestExtension;\nimport org.jvnet.hudson.test.junit.jupiter.WithJenkins;\nimport org.mockito.Mock;\nimport org.mockito.Mockito;\nimport org.mockito.junit.jupiter.MockitoExtension;\nimport org.springframework.security.core.userdetails.UserDetails;\n\n@WithJenkins\n@ExtendWith(MockitoExtension.class)\nclass MailResolverWithExtensionTest {\n\n    @Mock(strictness = Mock.Strictness.LENIENT)\n    JiraSite site;\n\n    JiraSession session;\n\n    @Mock\n    JiraRestService service;\n\n    @BeforeEach\n    void createMocks() throws Exception {\n        session = new JiraSession(site, service);\n        Mockito.when(site.getSession(any())).thenReturn(session);\n\n        Map<String, URI> avatars = new HashMap<>();\n        // pre check condition in Jira User constructor and do not ask me why!!\n        avatars.put(\"48x48\", new URI(\"https://foo.com\"));\n        com.atlassian.jira.rest.client.api.domain.User jiraUser = new com.atlassian.jira.rest.client.api.domain.User(\n                null, \"foo\", \"bar\", \"foo@beer.com\", true, null, avatars, null);\n\n        doReturn(session).when(site).getSession(any());\n        doReturn(jiraUser).when(service).getUser(\"foo\");\n    }\n\n    @Test\n    void emailResolverWithSecurityExtension(JenkinsRule r) throws Exception {\n        HudsonPrivateSecurityRealm realm = new HudsonPrivateSecurityRealm(true);\n        realm.createAccount(\"foo\", \"pacific_ale\");\n\n        r.jenkins.setSecurityRealm(realm);\n\n        JiraGlobalConfiguration.get().setSites(Collections.singletonList(site));\n\n        r.createWebClient().login(\"foo\", \"pacific_ale\");\n    }\n\n    @TestExtension\n    public static class DummySecurityListener extends SecurityListener {\n        @Override\n        protected void authenticated2(@NonNull UserDetails details) {\n            check(details.getUsername());\n        }\n\n        @Override\n        protected void authenticated(@NonNull org.acegisecurity.userdetails.UserDetails details) {\n            check(details.getUsername());\n        }\n\n        private void check(String userId) {\n\n            User user = User.getById(userId, false);\n\n            JiraMailAddressResolver jiraMailAddressResolver = Jenkins.get()\n                    .getExtensionList(JiraMailAddressResolver.class)\n                    .get(0);\n            String email = jiraMailAddressResolver.findMailAddressFor(user);\n            assertThat(email, is(\"foo@beer.com\"));\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/hudson/plugins/jira/MockAffectedFile.java",
    "content": "package hudson.plugins.jira;\n\nimport hudson.scm.ChangeLogSet.AffectedFile;\n\n/*\n * required to mocking subclass of AffectedFile\n */\npublic interface MockAffectedFile extends AffectedFile {}\n"
  },
  {
    "path": "src/test/java/hudson/plugins/jira/UnmaskMailTest.java",
    "content": "package hudson.plugins.jira;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport org.junit.jupiter.api.Test;\n\n/**\n * Test unmasking email. Eg: <code>john dot doe at example dot com</code> to <code>john.doe@example.com</code>\n *\n * @author Honza Brázdil (jbrazdil@redhat.com)\n */\nclass UnmaskMailTest {\n\n    @Test\n    void unmaskMailTest() {\n        test(\"user@example.com\", \"user at example dot com\");\n        test(\"user@example.com\", \"user AT example DOT com\");\n        test(\"user@example.com\", \"user aT example dOt com\");\n        test(\"user@example.com\", \"user At example d0t com\");\n        test(\"user@example.com\", \"user at example D0t com\");\n\n        test(\"user@example.com\", \"user[at]example[dot]com\");\n        test(\"user@example.com\", \"user{AT}example{DOT}com\");\n        test(\"user@example.com\", \"user<aT>example<dOt>com\");\n        test(\"user@example.com\", \"user\\\"At\\\"example\\\"d0t\\\"com\");\n        test(\"user@example.com\", \"user_at_example_D0t_com\");\n        test(\"user@example.com\", \"user(at)example(dot)com\");\n\n        test(\"user@example.com\", \"user [at] example [dot] com\");\n        test(\"user@example.com\", \"user {AT} example {DOT} com\");\n        test(\"user@example.com\", \"user <aT> example <dOt> com\");\n        test(\"user@example.com\", \"user \\\"At\\\" example \\\"d0t\\\" com\");\n        test(\"user@example.com\", \"user _at_ example _D0t_ com\");\n        test(\"user@example.com\", \"user (at) example (dot) com\");\n\n        test(\"john.doe.junior@my.site.eu\", \"john dot doe DOT junior at my dOt site D0T eu\");\n        test(\"john.doe.junior@my.site.eu\", \"john(dot)doe[DOT]junior{at}my<dOt>site_D0T_eu\");\n        test(\"john.doe.junior@my.site.eu\", \"john (dot) doe [DOT] junior {at} my <dOt> site _D0T_ eu\");\n\n        test(\"atdot@dotat.com\", \"atdot at dotat dot com\");\n        test(\"atdot@dotat.com\", \"atdot AT dotat DOT com\");\n        test(\"atdot@dotat.com\", \"atdot aT dotat dOt com\");\n        test(\"atdot@dotat.com\", \"atdot At dotat d0t com\");\n        test(\"atdot@dotat.com\", \"atdot at dotat D0t com\");\n    }\n\n    private void test(String expected, String masked) {\n        assertEquals(expected, JiraMailAddressResolver.unmaskEmail(masked));\n    }\n}\n"
  },
  {
    "path": "src/test/java/hudson/plugins/jira/UpdaterTest.java",
    "content": "package hudson.plugins.jira;\n\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.containsString;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.mockito.ArgumentMatchers.anyString;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.doAnswer;\nimport static org.mockito.Mockito.doReturn;\nimport static org.mockito.Mockito.doThrow;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nimport com.atlassian.jira.rest.client.api.RestClientException;\nimport com.atlassian.jira.rest.client.api.domain.Comment;\nimport com.atlassian.jira.rest.client.api.domain.Issue;\nimport hudson.model.FreeStyleBuild;\nimport hudson.model.FreeStyleProject;\nimport hudson.model.Job;\nimport hudson.model.Result;\nimport hudson.model.Run;\nimport hudson.model.User;\nimport hudson.plugins.jira.model.JiraIssue;\nimport hudson.scm.ChangeLogSet;\nimport hudson.scm.ChangeLogSet.Entry;\nimport hudson.scm.EditType;\nimport hudson.scm.SCM;\nimport java.io.File;\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Calendar;\nimport java.util.Collection;\nimport java.util.HashSet;\nimport java.util.LinkedHashSet;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.Set;\nimport org.hamcrest.Matchers;\nimport org.jenkinsci.plugins.workflow.job.WorkflowJob;\nimport org.jenkinsci.plugins.workflow.job.WorkflowRun;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.jvnet.hudson.test.JenkinsRule;\nimport org.jvnet.hudson.test.WithoutJenkins;\nimport org.jvnet.hudson.test.junit.jupiter.WithJenkins;\nimport org.mockito.Mockito;\nimport org.mockito.stubbing.Answer;\n\n/**\n * Test case for the Jira {@link Updater}.\n *\n * @author kutzi\n */\n@SuppressWarnings(\"unchecked\")\n@WithJenkins\npublic class UpdaterTest {\n\n    private Updater updater;\n\n    private static class MockEntry extends Entry {\n\n        private final String msg;\n\n        MockEntry(String msg) {\n            this.msg = msg;\n        }\n\n        @Override\n        public Collection<String> getAffectedPaths() {\n            return null;\n        }\n\n        @Override\n        public User getAuthor() {\n            return null;\n        }\n\n        @Override\n        public String getMsg() {\n            return this.msg;\n        }\n    }\n\n    @BeforeEach\n    void prepare() {\n        SCM scm = mock(SCM.class);\n        this.updater = new Updater(scm);\n    }\n\n    @Test\n    void getScmCommentsFromPreviousBuilds(JenkinsRule r) {\n        final FreeStyleProject project = mock(FreeStyleProject.class);\n        final FreeStyleBuild build1 = mock(FreeStyleBuild.class);\n        final MockEntry entry1 = new MockEntry(\"FOOBAR-1: The first build\");\n        {\n            ChangeLogSet changeLogSet = mock(ChangeLogSet.class);\n            when(build1.getChangeSet()).thenReturn(changeLogSet);\n            List<ChangeLogSet<? extends ChangeLogSet.Entry>> changeSets = new ArrayList<>();\n            changeSets.add(changeLogSet);\n            when(build1.getChangeSets()).thenReturn(changeSets);\n            when(build1.getResult()).thenReturn(Result.FAILURE);\n            doReturn(project).when(build1).getProject();\n\n            doReturn(new JiraCarryOverAction(new HashSet(Arrays.asList(new JiraIssue(\"FOOBAR-1\", null)))))\n                    .when(build1)\n                    .getAction(JiraCarryOverAction.class);\n\n            final Set<? extends Entry> entries = new HashSet(Arrays.asList(entry1));\n            when(changeLogSet.iterator()).thenAnswer(invocation -> entries.iterator());\n        }\n\n        final FreeStyleBuild build2 = mock(FreeStyleBuild.class);\n        final MockEntry entry2 = new MockEntry(\"FOOBAR-2: The next build\");\n        {\n            ChangeLogSet changeLogSet = mock(ChangeLogSet.class);\n            when(build2.getChangeSet()).thenReturn(changeLogSet);\n            List<ChangeLogSet<? extends ChangeLogSet.Entry>> changeSets = new ArrayList<>();\n            changeSets.add(changeLogSet);\n            when(build2.getChangeSets()).thenReturn(changeSets);\n            when(build2.getPreviousCompletedBuild()).thenReturn(build1);\n            when(build2.getResult()).thenReturn(Result.SUCCESS);\n            doReturn(project).when(build2).getProject();\n\n            final Set<? extends Entry> entries = new HashSet(Arrays.asList(entry2));\n            when(changeLogSet.iterator()).thenAnswer(invocation -> entries.iterator());\n        }\n\n        final List<Comment> comments = new ArrayList();\n        final JiraSession session = mock(JiraSession.class);\n        doAnswer((Answer<Object>) invocation -> {\n                    Comment rc =\n                            Comment.createWithGroupLevel((String) invocation.getArguments()[1], (String)\n                                    invocation.getArguments()[2]);\n                    comments.add(rc);\n                    return null;\n                })\n                .when(session)\n                .addComment(anyString(), anyString(), anyString(), anyString());\n\n        this.updater = new Updater(build2.getProject().getScm());\n\n        final Set<JiraIssue> ids =\n                new HashSet(Arrays.asList(new JiraIssue(\"FOOBAR-1\", null), new JiraIssue(\"FOOBAR-2\", null)));\n        updater.submitComments(build2, System.out, \"http://jenkins\", ids, session, false, false, \"\", \"\");\n\n        assertEquals(2, comments.size());\n        assertThat(comments.get(0).getBody(), Matchers.containsString(entry1.getMsg()));\n        assertThat(comments.get(1).getBody(), Matchers.containsString(entry2.getMsg()));\n    }\n\n    /**\n     * Tests that the generated comment matches the expectations -\n     * especially that the Jira id is not stripped from the comment.\n     */\n    @Test\n    @org.jvnet.hudson.test.Issue(\"4572\")\n    void comment(JenkinsRule r) {\n        // mock Jira session:\n        JiraSession session = mock(JiraSession.class);\n        final Issue mockIssue = Mockito.mock(Issue.class);\n        when(session.getIssue(Mockito.anyString())).thenReturn(mockIssue);\n\n        final List<String> comments = new ArrayList<>();\n\n        Answer answer = (Answer<Object>) invocation -> {\n            comments.add((String) invocation.getArguments()[1]);\n            return null;\n        };\n        doAnswer(answer)\n                .when(session)\n                .addComment(Mockito.anyString(), Mockito.anyString(), Mockito.anyString(), Mockito.anyString());\n\n        // mock build:\n        FreeStyleBuild build = mock(FreeStyleBuild.class);\n        FreeStyleProject project = mock(FreeStyleProject.class);\n        when(build.getParent()).thenReturn(project);\n        when(build.getProject()).thenReturn(project);\n        ChangeLogSet changeLogSet = mock(ChangeLogSet.class);\n        when(build.getChangeSet()).thenReturn(changeLogSet);\n        when(build.getResult()).thenReturn(Result.SUCCESS);\n\n        Set<? extends Entry> entries = new HashSet(Arrays.asList(new MockEntry(\"Fixed FOOBAR-4711\")));\n        when(changeLogSet.iterator()).thenReturn(entries.iterator());\n\n        List<ChangeLogSet<? extends ChangeLogSet.Entry>> changeSets = new ArrayList<>();\n        changeSets.add(changeLogSet);\n        when(build.getChangeSets()).thenReturn(changeSets);\n\n        // test:\n        Set<JiraIssue> ids = new HashSet(Arrays.asList(new JiraIssue(\"FOOBAR-4711\", \"Title\")));\n        Updater updaterCurrent = new Updater(build.getParent().getScm());\n        updaterCurrent.submitComments(build, System.out, \"http://jenkins\", ids, session, false, false, \"\", \"\");\n\n        assertEquals(1, comments.size());\n        String comment = comments.get(0);\n\n        assertTrue(comment.contains(\"FOOBAR-4711\"));\n\n        // must also work case-insensitively (JENKINS-4132)\n        comments.clear();\n        entries = new HashSet(Arrays.asList(new MockEntry(\"Fixed Foobar-4711\")));\n        when(changeLogSet.iterator()).thenReturn(entries.iterator());\n        ids = new HashSet(Arrays.asList(new JiraIssue(\"FOOBAR-4711\", \"Title\")));\n\n        updaterCurrent.submitComments(build, System.out, \"http://jenkins\", ids, session, false, false, \"\", \"\");\n\n        assertEquals(1, comments.size());\n        comment = comments.get(0);\n\n        assertTrue(comment.contains(\"Foobar-4711\"));\n    }\n\n    /**\n     * /**\n     * Checks if issues are correctly removed from the carry over list.\n     */\n    @Test\n    @org.jvnet.hudson.test.Issue(\"17156\")\n    @WithoutJenkins\n    void issueIsRemovedFromCarryOverListAfterSubmission() {\n        // mock build:\n        FreeStyleBuild build = mock(FreeStyleBuild.class);\n        FreeStyleProject project = mock(FreeStyleProject.class);\n        when(build.getParent()).thenReturn(project);\n        when(build.getProject()).thenReturn(project);\n        ChangeLogSet changeLogSet = ChangeLogSet.createEmpty(build);\n        when(build.getChangeSet()).thenReturn(changeLogSet);\n        when(build.getResult()).thenReturn(Result.SUCCESS);\n\n        final JiraIssue firstIssue = new JiraIssue(\"FOOBAR-1\", \"Title\");\n        final JiraIssue secondIssue = new JiraIssue(\"ALIBA-1\", \"Title\");\n        final JiraIssue thirdIssue = new JiraIssue(\"MOONA-1\", \"Title\");\n        final JiraIssue deletedIssue = new JiraIssue(\"FOOBAR-2\", \"Title\");\n        final JiraIssue forbiddenIssue = new JiraIssue(\"LASSO-17\", \"Title\");\n\n        // assume that there is a following list of jira issues from scm commit messages out of\n        // hudson.plugins.jira.JiraCarryOverAction\n        Set<JiraIssue> issues = new HashSet(Arrays.asList(firstIssue, secondIssue, forbiddenIssue, thirdIssue));\n\n        // mock Jira session:\n        JiraSession session = mock(JiraSession.class);\n\n        final List<Comment> comments = new ArrayList<>();\n\n        Answer answer = (Answer<Object>) invocation -> {\n            Comment c = Comment.createWithGroupLevel(\n                    (String) invocation.getArguments()[0], (String) invocation.getArguments()[1]);\n            comments.add(c);\n            return null;\n        };\n\n        doAnswer(answer)\n                .when(session)\n                .addComment(eq(firstIssue.getKey()), Mockito.anyString(), Mockito.anyString(), Mockito.anyString());\n        doAnswer(answer)\n                .when(session)\n                .addComment(eq(secondIssue.getKey()), Mockito.anyString(), Mockito.anyString(), Mockito.anyString());\n        doAnswer(answer)\n                .when(session)\n                .addComment(eq(thirdIssue.getKey()), Mockito.anyString(), Mockito.anyString(), Mockito.anyString());\n\n        // issue for the caught exception\n        doThrow(new RestClientException(new Throwable(), 404))\n                .when(session)\n                .addComment(eq(deletedIssue.getKey()), Mockito.anyString(), Mockito.anyString(), Mockito.anyString());\n        doThrow(new RestClientException(new Throwable(), 403))\n                .when(session)\n                .addComment(eq(forbiddenIssue.getKey()), Mockito.anyString(), Mockito.anyString(), Mockito.anyString());\n\n        final String groupVisibility = \"\";\n        final String roleVisibility = \"\";\n\n        Updater updaterCurrent = new Updater(build.getParent().getScm());\n\n        updaterCurrent.submitComments(\n                build, System.out, \"http://jenkins\", issues, session, false, false, groupVisibility, roleVisibility);\n\n        // expected issue list\n        final Set<JiraIssue> expectedIssuesToCarryOver = new LinkedHashSet();\n        expectedIssuesToCarryOver.add(forbiddenIssue);\n        assertThat(issues, is(expectedIssuesToCarryOver));\n    }\n\n    @TempDir\n    public File folder;\n\n    /**\n     * Test that workflow job - run instance of type WorkflowJob - can\n     * return changeSets using java reflection api\n     *\n     */\n    @Test\n    void getChangesUsingReflectionForWorkflowJob(JenkinsRule r) throws IOException {\n        WorkflowJob workflowJob = new WorkflowJob(r.getInstance(), \"job\");\n        WorkflowRun workflowRun = new WorkflowRun(workflowJob);\n\n        ChangeLogSet.createEmpty(workflowRun);\n\n        List<ChangeLogSet<? extends Entry>> changesUsingReflection =\n                RunScmChangeExtractor.getChangesUsingReflection(workflowRun);\n        assertNotNull(changesUsingReflection);\n        assertTrue(changesUsingReflection.isEmpty());\n    }\n\n    @WithoutJenkins\n    @Test\n    void getChangesUsingReflectionForunknownJob() {\n        Run run = mock(Run.class);\n        assertThrows(IllegalArgumentException.class, () -> RunScmChangeExtractor.getChangesUsingReflection(run));\n    }\n\n    /**\n     * Test formatting of scm entry change time.\n     *\n     */\n    @Test\n    @WithoutJenkins\n    void appendChangeTimestampToDescription() {\n        Updater updater = new Updater(null);\n        StringBuilder description = new StringBuilder();\n        Calendar calendar = Calendar.getInstance();\n        calendar.set(2016, 0, 1, 0, 0, 0);\n        JiraSite site = mock(JiraSite.class);\n        when(site.getDateTimePattern()).thenReturn(\"yyyy-MM-dd HH:mm:ss\");\n        updater.appendChangeTimestampToDescription(description, site, calendar.getTimeInMillis());\n        System.out.println(description.toString());\n        assertThat(description.toString(), equalTo(\"2016-01-01 00:00:00\"));\n    }\n\n    /**\n     * Test formatting of scm entry change description.\n     *\n     */\n    @Test\n    void dateTimeInChangeDescription(JenkinsRule rule) {\n        rule.getInstance();\n        Calendar calendar = Calendar.getInstance();\n        calendar.set(2016, 0, 1, 0, 0, 0);\n        JiraSite site = mock(JiraSite.class);\n        when(site.isAppendChangeTimestamp()).thenReturn(true);\n        when(site.getDateTimePattern()).thenReturn(\"yyyy-MM-dd HH:mm:ss\");\n\n        Run r = mock(Run.class);\n        Job j = mock(Job.class);\n        when(r.getParent()).thenReturn(j);\n        JiraProjectProperty jiraProjectProperty = mock(JiraProjectProperty.class);\n        when(j.getProperty(JiraProjectProperty.class)).thenReturn(jiraProjectProperty);\n        when(jiraProjectProperty.getSite()).thenReturn(site);\n\n        ChangeLogSet.Entry entry = mock(ChangeLogSet.Entry.class);\n        when(entry.getTimestamp()).thenReturn(calendar.getTimeInMillis());\n        when(entry.getCommitId()).thenReturn(\"dsgsvds2re3dsv\");\n        User mockAuthor = mock(User.class);\n        when(mockAuthor.getId()).thenReturn(\"jenkins-user\");\n        when(entry.getAuthor()).thenReturn(mockAuthor);\n\n        Updater updater = new Updater(null);\n        String description = updater.createScmChangeEntryDescription(r, entry, false, false);\n        System.out.println(description);\n        assertThat(description, containsString(\"2016-01-01 00:00:00\"));\n        assertThat(description, containsString(\"jenkins-user\"));\n        assertThat(description, containsString(\"dsgsvds2re3dsv\"));\n    }\n\n    /**\n     * Test formatting of scm entry change description\n     * when no format is provided (e.g. when null).\n     *\n     */\n    @Test\n    @WithoutJenkins\n    void appendChangeTimestampToDescriptionNullFormat() {\n        // set default locale -> predictable test without explicit format\n        Locale.setDefault(Locale.ENGLISH);\n\n        Updater updater = new Updater(null);\n        JiraSite site = mock(JiraSite.class);\n        when(site.isAppendChangeTimestamp()).thenReturn(true);\n        when(site.getDateTimePattern()).thenReturn(null);\n        // when(site.getDateTimePattern()).thenReturn(\"d/M/yy hh:mm a\");\n\n        Calendar calendar = Calendar.getInstance();\n        calendar.set(2016, 0, 1, 0, 0, 0);\n\n        StringBuilder builder = new StringBuilder();\n        updater.appendChangeTimestampToDescription(builder, site, calendar.getTimeInMillis());\n        assertThat(builder.toString(), equalTo(\"1/1/16 12:00 AM\"));\n    }\n\n    /**\n     * Test formatting of scm entry change description\n     * when no format is provided (e.g. when empty string).\n     *\n     */\n    @Test\n    @WithoutJenkins\n    void appendChangeTimestampToDescriptionNoFormat() {\n        // set default locale -> predictable test without explicit format\n        Locale.setDefault(Locale.ENGLISH);\n\n        Updater updater = new Updater(null);\n        JiraSite site = mock(JiraSite.class);\n        when(site.isAppendChangeTimestamp()).thenReturn(true);\n        when(site.getDateTimePattern()).thenReturn(null);\n        // when(site.getDateTimePattern()).thenReturn(\"d/M/yy hh:mm a\");\n\n        Calendar calendar = Calendar.getInstance();\n        calendar.set(2016, 0, 1, 0, 0, 0);\n\n        StringBuilder builder = new StringBuilder();\n        updater.appendChangeTimestampToDescription(builder, site, calendar.getTimeInMillis());\n        assertThat(builder.toString(), equalTo(\"1/1/16 12:00 AM\"));\n    }\n\n    /**\n     * Test formatting of scm entry change description coverage primary wiki\n     * style appendRevisionToDescription and appendAffectedFilesToDescription\n     *\n     */\n    @Test\n    void tesDescriptionWithAffectedFiles(JenkinsRule rule) {\n        rule.getInstance();\n        Calendar calendar = Calendar.getInstance();\n        calendar.set(2016, 0, 1, 0, 0, 0);\n        JiraSite site = mock(JiraSite.class);\n        when(site.isAppendChangeTimestamp()).thenReturn(false);\n\n        Run r = mock(Run.class);\n        Job j = mock(Job.class);\n        when(r.getParent()).thenReturn(j);\n        JiraProjectProperty jiraProjectProperty = mock(JiraProjectProperty.class);\n        when(j.getProperty(JiraProjectProperty.class)).thenReturn(jiraProjectProperty);\n        when(jiraProjectProperty.getSite()).thenReturn(site);\n\n        ChangeLogSet.Entry entry = mock(ChangeLogSet.Entry.class);\n        when(entry.getTimestamp()).thenReturn(calendar.getTimeInMillis());\n        when(entry.getCommitId()).thenReturn(\"dsgsvds2re3dsv\");\n        User mockAuthor = mock(User.class);\n        when(mockAuthor.getId()).thenReturn(\"jenkins-user\");\n        when(entry.getAuthor()).thenReturn(mockAuthor);\n\n        Collection<MockAffectedFile> affectedFiles = new ArrayList();\n        MockAffectedFile affectedFile1 = mock(MockAffectedFile.class);\n        when(affectedFile1.getEditType()).thenReturn(EditType.ADD);\n        when(affectedFile1.getPath()).thenReturn(\"hudson/plugins/jira/File1\");\n        affectedFiles.add(affectedFile1);\n        MockAffectedFile corruptedFile = mock(MockAffectedFile.class);\n        when(corruptedFile.getEditType()).thenReturn(null);\n        when(corruptedFile.getPath()).thenReturn(null);\n        affectedFiles.add(corruptedFile);\n        MockAffectedFile affectedFile2 = mock(MockAffectedFile.class);\n        when(affectedFile2.getEditType()).thenReturn(EditType.DELETE);\n        when(affectedFile2.getPath()).thenReturn(\"hudson/plugins/jira/File2\");\n        affectedFiles.add(affectedFile2);\n        MockAffectedFile affectedFile3 = mock(MockAffectedFile.class);\n        when(affectedFile3.getEditType()).thenReturn(EditType.EDIT);\n        when(affectedFile3.getPath()).thenReturn(\"hudson/plugins/jira/File3\");\n        affectedFiles.add(affectedFile3);\n        doReturn(affectedFiles).when(entry).getAffectedFiles();\n\n        Updater updater = new Updater(null);\n        String description = updater.createScmChangeEntryDescription(r, entry, true, true);\n        System.out.println(description);\n        assertThat(\n                description,\n                equalTo(\" (jenkins-user: rev dsgsvds2re3dsv)\\n\" + \"* (add) hudson/plugins/jira/File1\\n\" + \"* \\n\"\n                        + \"* (delete) hudson/plugins/jira/File2\\n\" + \"* (edit) hudson/plugins/jira/File3\\n\"));\n    }\n}\n"
  },
  {
    "path": "src/test/java/hudson/plugins/jira/VersionCreatorTest.java",
    "content": "package hudson.plugins.jira;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.is;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.doReturn;\nimport static org.mockito.Mockito.reset;\nimport static org.mockito.Mockito.spy;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport com.atlassian.jira.rest.client.api.RestClientException;\nimport hudson.EnvVars;\nimport hudson.model.AbstractBuild;\nimport hudson.model.AbstractProject;\nimport hudson.model.BuildListener;\nimport hudson.model.Result;\nimport hudson.plugins.jira.extension.ExtendedVersion;\nimport java.io.IOException;\nimport java.io.PrintStream;\nimport java.util.Arrays;\nimport org.joda.time.DateTime;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.Captor;\nimport org.mockito.Mock;\nimport org.mockito.Mockito;\nimport org.mockito.junit.jupiter.MockitoExtension;\nimport org.mockito.stubbing.Answer;\n\n@ExtendWith(MockitoExtension.class)\nclass VersionCreatorTest {\n    private static final String JIRA_VER = Long.toString(System.currentTimeMillis());\n    private static final String JIRA_PRJ = \"TEST_PRJ\";\n    private static final String JIRA_VER_PARAM = \"${JIRA_VER}\";\n    private static final String JIRA_PRJ_PARAM = \"${JIRA_PRJ}\";\n    private static final Long ANY_ID = System.currentTimeMillis();\n    private static final DateTime ANY_DATE = new DateTime();\n\n    @Mock\n    AbstractBuild build;\n\n    @Mock\n    BuildListener listener;\n\n    @Mock\n    PrintStream logger;\n\n    @Mock\n    EnvVars env;\n\n    @Mock\n    AbstractProject project;\n\n    @Mock\n    JiraSite site;\n\n    @Mock\n    JiraSession session;\n\n    @Captor\n    ArgumentCaptor<String> versionCaptor;\n\n    @Captor\n    ArgumentCaptor<String> projectCaptor;\n\n    private VersionCreator versionCreator = spy(VersionCreator.class);\n    private ExtendedVersion existingVersion =\n            new ExtendedVersion(null, ANY_ID, JIRA_VER, null, false, false, ANY_DATE, ANY_DATE);\n\n    @BeforeEach\n    void createMocks() {\n        when(site.getSession(any())).thenReturn(session);\n        when(env.expand(Mockito.anyString())).thenAnswer((Answer<String>) invocationOnMock -> {\n            Object[] args = invocationOnMock.getArguments();\n            String expanded = (String) args[0];\n            if (expanded.equals(JIRA_PRJ_PARAM)) {\n                return JIRA_PRJ;\n            } else if (expanded.equals(JIRA_VER_PARAM)) {\n                return JIRA_VER;\n            } else {\n                return expanded;\n            }\n        });\n        when(listener.getLogger()).thenReturn(logger);\n        doReturn(site).when(versionCreator).getSiteForProject(any());\n    }\n\n    @Test\n    void callsJiraWithSpecifiedParameters() throws InterruptedException, IOException, RestClientException {\n        when(build.getEnvironment(listener)).thenReturn(env);\n        when(site.getSession(any())).thenReturn(session);\n\n        // for new version, verify the addVersion method is called\n        when(session.getVersions(JIRA_PRJ)).thenReturn(null);\n        versionCreator.setJiraProjectKey(JIRA_PRJ);\n        versionCreator.setJiraVersion(JIRA_VER);\n        boolean result = versionCreator.perform(project, build, listener);\n        verify(session, times(1)).addVersion(versionCaptor.capture(), projectCaptor.capture());\n        assertThat(projectCaptor.getValue(), is(JIRA_PRJ));\n        assertThat(versionCaptor.getValue(), is(JIRA_VER));\n        verify(logger, times(1)).println(Messages.JiraVersionCreator_CreatingVersion(JIRA_VER, JIRA_PRJ));\n        assertThat(result, is(true));\n\n        // for existing version, verify the addVersion method is not called\n        reset(session);\n        when(session.getVersions(JIRA_PRJ)).thenReturn(Arrays.asList(existingVersion));\n        versionCreator.setJiraProjectKey(JIRA_PRJ);\n        versionCreator.setJiraVersion(JIRA_VER);\n        result = versionCreator.perform(project, build, listener);\n        verify(session, times(0)).addVersion(versionCaptor.capture(), projectCaptor.capture());\n        verify(logger, times(1)).println(Messages.JiraVersionCreator_VersionExists(JIRA_VER, JIRA_PRJ));\n        verify(listener).finished(Result.FAILURE);\n        assertThat(result, is(false));\n    }\n\n    @Test\n    void expandsEnvParameters() throws InterruptedException, IOException, RestClientException {\n        when(build.getEnvironment(listener)).thenReturn(env);\n        when(site.getSession(any())).thenReturn(session);\n\n        // for new version, verify the addVersion method is called\n        when(session.getVersions(JIRA_PRJ)).thenReturn(null);\n        versionCreator.setJiraProjectKey(JIRA_PRJ_PARAM);\n        versionCreator.setJiraVersion(JIRA_VER_PARAM);\n        boolean result = versionCreator.perform(project, build, listener);\n        verify(session, times(1)).addVersion(versionCaptor.capture(), projectCaptor.capture());\n        assertThat(projectCaptor.getValue(), is(JIRA_PRJ));\n        assertThat(versionCaptor.getValue(), is(JIRA_VER));\n        assertThat(result, is(true));\n\n        // for existing version, verify the addVersion method is called\n        reset(session);\n        when(session.getVersions(JIRA_PRJ)).thenReturn(Arrays.asList(existingVersion));\n        versionCreator.setJiraProjectKey(JIRA_PRJ_PARAM);\n        versionCreator.setJiraVersion(JIRA_VER_PARAM);\n        result = versionCreator.perform(project, build, listener);\n        verify(session, times(0)).addVersion(versionCaptor.capture(), projectCaptor.capture());\n        verify(logger, times(1)).println(Messages.JiraVersionCreator_VersionExists(JIRA_VER, JIRA_PRJ));\n        verify(listener).finished(Result.FAILURE);\n        assertThat(result, is(false));\n    }\n\n    @Test\n    void buildDidNotFailWhenVersionExists() throws IOException, InterruptedException, RestClientException {\n        when(build.getEnvironment(listener)).thenReturn(env);\n        ExtendedVersion releasedVersion =\n                new ExtendedVersion(null, ANY_ID, JIRA_VER, null, false, true, ANY_DATE, ANY_DATE);\n        when(site.getSession(any())).thenReturn(session);\n        when(session.getVersions(JIRA_PRJ)).thenReturn(Arrays.asList(releasedVersion));\n        versionCreator.setJiraProjectKey(JIRA_PRJ_PARAM);\n        versionCreator.setJiraVersion(JIRA_VER_PARAM);\n        versionCreator.perform(project, build, listener);\n        verify(session, times(0)).addVersion(any(), any());\n    }\n\n    @Test\n    void buildDoesNotFailWhenVersionExistsAndFailIfAlreadyExistsIsFalse()\n            throws IOException, InterruptedException, RestClientException {\n        when(build.getEnvironment(listener)).thenReturn(env);\n        when(site.getSession(any())).thenReturn(session);\n        when(session.getVersions(JIRA_PRJ)).thenReturn(Arrays.asList(existingVersion));\n\n        // Set failIfAlreadyExists to false\n        versionCreator.setFailIfAlreadyExists(false);\n        versionCreator.setJiraProjectKey(JIRA_PRJ);\n        versionCreator.setJiraVersion(JIRA_VER);\n        boolean result = versionCreator.perform(project, build, listener);\n\n        // Verify that addVersion is not called (because version already exists)\n        verify(session, times(0)).addVersion(any(), any());\n        // Verify the message is logged\n        verify(logger, times(1)).println(Messages.JiraVersionCreator_VersionExists(JIRA_VER, JIRA_PRJ));\n        // Verify build is NOT failed\n        verify(listener, times(0)).finished(Result.FAILURE);\n        // Verify the perform method returns true\n        assertThat(result, is(true));\n    }\n}\n"
  },
  {
    "path": "src/test/java/hudson/plugins/jira/VersionReleaserTest.java",
    "content": "package hudson.plugins.jira;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.is;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.doReturn;\nimport static org.mockito.Mockito.spy;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport hudson.EnvVars;\nimport hudson.model.AbstractBuild;\nimport hudson.model.AbstractProject;\nimport hudson.model.BuildListener;\nimport hudson.plugins.jira.extension.ExtendedVersion;\nimport java.io.PrintStream;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport org.joda.time.DateTime;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.Captor;\nimport org.mockito.Mock;\nimport org.mockito.Mockito;\nimport org.mockito.junit.jupiter.MockitoExtension;\nimport org.mockito.stubbing.Answer;\n\n@ExtendWith(MockitoExtension.class)\nclass VersionReleaserTest {\n    private static final String JIRA_VER = Long.toString(System.currentTimeMillis());\n    private static final String JIRA_PRJ = \"TEST_PRJ\";\n    private static final String JIRA_DES = \"TEST_DES\";\n    private static final String JIRA_VER_PARAM = \"${JIRA_VER}\";\n    private static final String JIRA_PRJ_PARAM = \"${JIRA_PRJ}\";\n    private static final String JIRA_DES_PARAM = \"${JIRA_DES}\";\n    private static final Long ANY_ID = System.currentTimeMillis();\n    private static final DateTime ANY_DATE = new DateTime();\n\n    @Mock\n    AbstractBuild build;\n\n    @Mock\n    BuildListener listener;\n\n    @Mock\n    PrintStream logger;\n\n    @Mock\n    EnvVars env;\n\n    @Mock\n    AbstractProject project;\n\n    @Mock(strictness = Mock.Strictness.LENIENT)\n    JiraSite site;\n\n    @Mock\n    JiraSession session;\n\n    @Captor\n    ArgumentCaptor<ExtendedVersion> versionCaptor;\n\n    @Captor\n    ArgumentCaptor<String> projectCaptor;\n\n    private VersionReleaser versionReleaser = spy(VersionReleaser.class);\n    private ExtendedVersion existingVersion =\n            new ExtendedVersion(null, ANY_ID, JIRA_VER, JIRA_DES, false, false, ANY_DATE, ANY_DATE);\n\n    @BeforeEach\n    void createMocks() throws Exception {\n        when(site.getSession(any())).thenReturn(session);\n\n        when(build.getEnvironment(listener)).thenReturn(env);\n        when(env.expand(Mockito.anyString())).thenAnswer((Answer<String>) invocationOnMock -> {\n            Object[] args = invocationOnMock.getArguments();\n            String expanded = (String) args[0];\n            if (expanded.equals(JIRA_PRJ_PARAM)) {\n                return JIRA_PRJ;\n            } else if (expanded.equals(JIRA_VER_PARAM)) {\n                return JIRA_VER;\n            } else if (expanded.equals(JIRA_DES_PARAM)) {\n                return JIRA_DES;\n            } else {\n                return expanded;\n            }\n        });\n        when(listener.getLogger()).thenReturn(logger);\n        doReturn(site).when(versionReleaser).getSiteForProject(any());\n    }\n\n    @Test\n    void callsJiraWithSpecifiedParameters() {\n        when(session.getVersions(JIRA_PRJ)).thenReturn(Collections.singletonList(existingVersion));\n        when(site.getVersions(JIRA_PRJ)).thenReturn(new HashSet<>(Arrays.asList(existingVersion)));\n        when(site.getSession(any())).thenReturn(session);\n\n        versionReleaser.perform(project, JIRA_PRJ, JIRA_VER, JIRA_DES, build, listener);\n\n        verify(session).releaseVersion(projectCaptor.capture(), versionCaptor.capture());\n        assertThat(projectCaptor.getValue(), is(JIRA_PRJ));\n        assertThat(versionCaptor.getValue().getName(), is(JIRA_VER));\n        assertThat(versionCaptor.getValue().getDescription(), is(JIRA_DES));\n    }\n\n    @Test\n    void expandsEnvParameters() {\n        when(session.getVersions(JIRA_PRJ)).thenReturn(Collections.singletonList(existingVersion));\n        when(site.getVersions(JIRA_PRJ)).thenReturn(new HashSet<>(Arrays.asList(existingVersion)));\n        when(site.getSession(any())).thenReturn(session);\n\n        versionReleaser.perform(project, JIRA_PRJ_PARAM, JIRA_VER_PARAM, JIRA_DES_PARAM, build, listener);\n\n        verify(session).releaseVersion(projectCaptor.capture(), versionCaptor.capture());\n        assertThat(projectCaptor.getValue(), is(JIRA_PRJ));\n        assertThat(versionCaptor.getValue().getName(), is(JIRA_VER));\n        assertThat(versionCaptor.getValue().getDescription(), is(JIRA_DES));\n    }\n\n    @Test\n    void buildDidNotFailWhenVersionExists() {\n        ExtendedVersion releasedVersion =\n                new ExtendedVersion(null, ANY_ID, JIRA_VER, JIRA_DES, false, true, ANY_DATE, ANY_DATE);\n        when(site.getVersions(JIRA_PRJ)).thenReturn(new HashSet<>(Arrays.asList(releasedVersion)));\n\n        versionReleaser.perform(project, JIRA_PRJ_PARAM, JIRA_VER_PARAM, JIRA_DES_PARAM, build, listener);\n        verify(session, times(0)).releaseVersion(projectCaptor.capture(), versionCaptor.capture());\n    }\n}\n"
  },
  {
    "path": "src/test/java/hudson/plugins/jira/auth/BearerHttpAuthenticationHandlerTest.java",
    "content": "package hudson.plugins.jira.auth;\n\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\n\nimport com.atlassian.httpclient.api.Request;\nimport org.junit.jupiter.api.Test;\n\nclass BearerHttpAuthenticationHandlerTest {\n\n    @Test\n    void testConfigure() {\n        String token = \"token\";\n        BearerHttpAuthenticationHandler handler = new BearerHttpAuthenticationHandler(token);\n        Request.Builder builder = mock(Request.Builder.class);\n\n        handler.configure(builder);\n\n        verify(builder).setHeader(\"Authorization\", \"Bearer \" + token);\n    }\n}\n"
  },
  {
    "path": "src/test/java/hudson/plugins/jira/auth/JiraRestServiceBearerAuthTest.java",
    "content": "package hudson.plugins.jira.auth;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.mockito.Mockito.any;\nimport static org.mockito.Mockito.anyInt;\nimport static org.mockito.Mockito.anyLong;\nimport static org.mockito.Mockito.doReturn;\nimport static org.mockito.Mockito.doThrow;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.spy;\n\nimport com.atlassian.jira.rest.client.api.RestClientException;\nimport com.atlassian.jira.rest.client.api.SearchRestClient;\nimport com.atlassian.jira.rest.client.api.domain.SearchResult;\nimport hudson.plugins.jira.JiraRestService;\nimport hudson.plugins.jira.JiraSite;\nimport hudson.plugins.jira.extension.ExtendedJiraRestClient;\nimport io.atlassian.util.concurrent.Promise;\nimport java.net.URI;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.TimeoutException;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.Mockito;\n\nclass JiraRestServiceBearerAuthTest {\n\n    private final URI JIRA_URI = URI.create(\"http://example.com:8080/\");\n    private final String TOKEN = \"token\";\n    private ExtendedJiraRestClient client;\n    private SearchRestClient searchRestClient;\n    private Promise promise;\n    private SearchResult searchResult;\n\n    @BeforeEach\n    void createMocks() throws InterruptedException, ExecutionException, TimeoutException {\n        client = mock(ExtendedJiraRestClient.class);\n        searchRestClient = mock(SearchRestClient.class);\n        promise = mock(Promise.class);\n        searchResult = mock(SearchResult.class);\n\n        doReturn(searchRestClient).when(client).getSearchClient();\n        doReturn(promise).when(searchRestClient).searchJql(any(), any(), anyInt(), any());\n        doReturn(searchResult).when(promise).get(anyLong(), any());\n    }\n\n    @Test\n    void baseApiPath() {\n        JiraRestService service = new JiraRestService(JIRA_URI, client, TOKEN, JiraSite.DEFAULT_TIMEOUT);\n        assertEquals(\"/\" + JiraRestService.BASE_API_PATH, service.getBaseApiPath());\n\n        URI uri = URI.create(\"https://example.com/path/to/jira\");\n        service = new JiraRestService(uri, client, TOKEN, JiraSite.DEFAULT_TIMEOUT);\n        assertEquals(\"/path/to/jira/\" + JiraRestService.BASE_API_PATH, service.getBaseApiPath());\n    }\n\n    @Test\n    void getIssuesFromJqlSearchTimeout() throws ExecutionException, InterruptedException, TimeoutException {\n        Throwable throwable = mock(Throwable.class);\n        JiraRestService service = spy(new JiraRestService(JIRA_URI, client, TOKEN, JiraSite.DEFAULT_TIMEOUT));\n        doThrow(new RestClientException(\"Verify rest client exception\", throwable))\n                .when(promise)\n                .get(Mockito.anyLong(), Mockito.any());\n        assertThrows(RestClientException.class, () -> service.getIssuesFromJqlSearch(\"*\", null));\n    }\n}\n"
  },
  {
    "path": "src/test/java/hudson/plugins/jira/listissuesparameter/JiraIssueParameterDefinitionTest.java",
    "content": "package hudson.plugins.jira.listissuesparameter;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.hasSize;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport com.atlassian.jira.rest.client.api.domain.Issue;\nimport hudson.model.Item;\nimport hudson.model.Job;\nimport hudson.model.ParameterValue;\nimport hudson.model.ParametersDefinitionProperty;\nimport hudson.plugins.jira.Messages;\nimport hudson.util.ListBoxModel;\nimport java.util.List;\nimport java.util.stream.Stream;\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.junit.jupiter.params.provider.NullSource;\nimport org.kohsuke.stapler.StaplerRequest2;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\n\n@ExtendWith(MockitoExtension.class)\nclass JiraIssueParameterDefinitionTest {\n\n    private JiraIssueParameterDefinition definition =\n            new JiraIssueParameterDefinition(\"PARAM_NAME\", \"desc\", \"jqlQuery\");\n\n    static Stream<Arguments> createValueInvalidParameters() {\n        return Stream.of(\n                Arguments.of((Object) new String[] {}),\n                Arguments.of((Object) new String[] {\"a\", \"b\"}),\n                Arguments.of((Object) new String[] {\"\"}));\n    }\n\n    @ParameterizedTest\n    @NullSource\n    @MethodSource(\"createValueInvalidParameters\")\n    void shouldCreateNullParameterForInvalidValues(String[] values, @Mock StaplerRequest2 req) {\n        when(req.getParameterValues(any())).thenReturn(values);\n\n        ParameterValue result = definition.createValue(req);\n\n        assertNull(result);\n    }\n\n    @Test\n    void shouldCreateValue(@Mock StaplerRequest2 req) {\n        when(req.getParameterValues(any())).thenReturn(new String[] {\"value\"});\n\n        ParameterValue result = definition.createValue(req);\n\n        assertNotNull(result);\n        assertEquals(\"PARAM_NAME\", result.getName());\n        assertEquals(\"value\", result.getValue());\n    }\n\n    @Nested\n    class DescriptorImplTest {\n\n        private JiraIssueParameterDefinition.DescriptorImpl uut = new JiraIssueParameterDefinition.DescriptorImpl();\n\n        @Test\n        void shouldFillValueItems(\n                @Mock Job<?, ?> job,\n                @Mock ParametersDefinitionProperty propertyDef,\n                @Mock JiraIssueParameterDefinition paramDef,\n                @Mock Issue issue) {\n            when(job.hasPermission(Item.BUILD)).thenReturn(true);\n            when(job.getProperty(ParametersDefinitionProperty.class)).thenReturn(propertyDef);\n            when(propertyDef.getParameterDefinition(\"PARAM_NAME\")).thenReturn(paramDef);\n            when(issue.getKey()).thenReturn(\"JIRA-1234\");\n            when(issue.getSummary()).thenReturn(\"Summary\");\n            JiraIssueParameterDefinition.Result item = new JiraIssueParameterDefinition.Result(issue, null);\n            when(paramDef.getIssues(any())).thenReturn(List.of(item));\n\n            ListBoxModel result = uut.doFillValueItems(job, \"PARAM_NAME\");\n\n            assertThat(result, hasSize(1));\n            ListBoxModel.Option option = result.get(0);\n            assertEquals(\"JIRA-1234\", option.value);\n            assertEquals(\"JIRA-1234: Summary\", option.name);\n            verify(job).hasPermission(Item.BUILD);\n        }\n\n        @Test\n        void shouldNotFillValueItemsIfPermissionMissing(@Mock Job<?, ?> job) {\n            ListBoxModel result = uut.doFillValueItems(job, \"PARAM_NAME\");\n\n            assertThat(result, hasSize(1));\n            ListBoxModel.Option option = result.get(0);\n            assertEquals(\"\", option.value);\n            assertEquals(Messages.JiraIssueParameterDefinition_NoIssueMatchedSearch(), option.name);\n            verify(job).hasPermission(Item.BUILD);\n        }\n\n        @Test\n        void shouldHaveNoSearchMatchesItemIfSearchMatchesNoItem(\n                @Mock Job<?, ?> job,\n                @Mock ParametersDefinitionProperty propertyDef,\n                @Mock JiraIssueParameterDefinition paramDef,\n                @Mock Issue issue) {\n            when(job.hasPermission(Item.BUILD)).thenReturn(true);\n            when(job.getProperty(ParametersDefinitionProperty.class)).thenReturn(propertyDef);\n            when(propertyDef.getParameterDefinition(\"PARAM_NAME\")).thenReturn(paramDef);\n\n            ListBoxModel result = uut.doFillValueItems(job, \"PARAM_NAME\");\n\n            assertThat(result, hasSize(1));\n            ListBoxModel.Option option = result.get(0);\n            assertEquals(\"\", option.value);\n            assertEquals(Messages.JiraIssueParameterDefinition_NoIssueMatchedSearch(), option.name);\n            verify(job).hasPermission(Item.BUILD);\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/hudson/plugins/jira/listissuesparameter/JiraIssueParameterTest.java",
    "content": "package hudson.plugins.jira.listissuesparameter;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.allOf;\nimport static org.hamcrest.Matchers.hasItem;\nimport static org.hamcrest.Matchers.hasProperty;\nimport static org.hamcrest.Matchers.instanceOf;\nimport static org.hamcrest.Matchers.is;\nimport static org.hamcrest.Matchers.notNullValue;\nimport static org.hamcrest.Matchers.nullValue;\nimport static org.junit.jupiter.api.Assertions.assertDoesNotThrow;\n\nimport hudson.EnvVars;\nimport hudson.model.ParametersDefinitionProperty;\nimport hudson.model.Run;\nimport org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition;\nimport org.jenkinsci.plugins.workflow.job.WorkflowJob;\nimport org.junit.jupiter.api.Test;\nimport org.jvnet.hudson.test.JenkinsRule;\nimport org.jvnet.hudson.test.junit.jupiter.WithJenkins;\n\n@WithJenkins\nclass JiraIssueParameterTest {\n\n    @Test\n    void scriptedPipeline(JenkinsRule r) throws Exception {\n        WorkflowJob p = r.createProject(WorkflowJob.class);\n        p.setDefinition(new CpsFlowDefinition(\"\"\"\n                properties([\n                  parameters([\n                    jiraIssue(name: 'JIRA_ISSUE', description: 'Jira Test Description', jiraIssueFilter: 'project=PRJ')\n                  ])\n                ])\"\"\", true));\n        r.buildAndAssertSuccess(p);\n\n        ParametersDefinitionProperty parameters = p.getProperty(ParametersDefinitionProperty.class);\n        assertThat(parameters, is(notNullValue()));\n        assertThat(\n                parameters.getParameterDefinitions(),\n                hasItem(allOf(\n                        instanceOf(JiraIssueParameterDefinition.class),\n                        hasProperty(\"name\", is(\"JIRA_ISSUE\")),\n                        hasProperty(\"description\", is(\"Jira Test Description\")),\n                        hasProperty(\"jiraIssueFilter\", is(\"project=PRJ\")))));\n    }\n\n    @Test\n    void nullValueDoesNotThrowException(JenkinsRule r) throws Exception {\n        // Test that JiraIssueParameterValue with null value doesn't throw NPE\n        JiraIssueParameterValue paramValue = new JiraIssueParameterValue(\"JIRA_ISSUE\", null);\n\n        // Test buildEnvironment method doesn't throw NPE with null value\n        EnvVars envVars = new EnvVars();\n        WorkflowJob job = r.createProject(WorkflowJob.class);\n        job.setDefinition(new CpsFlowDefinition(\"echo 'test'\", true));\n        Run<?, ?> run = r.buildAndAssertSuccess(job);\n\n        assertDoesNotThrow(() -> paramValue.buildEnvironment(run, envVars));\n        assertThat(envVars.get(\"JIRA_ISSUE\"), is(\"\"));\n\n        // Test that toString works with null value\n        assertDoesNotThrow(() -> paramValue.toString());\n    }\n\n    @Test\n    void nonNullValueInBuildEnvironment(JenkinsRule r) throws Exception {\n        // Test that JiraIssueParameterValue with non-null value works correctly\n        JiraIssueParameterValue paramValue = new JiraIssueParameterValue(\"JIRA_ISSUE\", \"TEST-123\");\n\n        // Test buildEnvironment method with non-null value\n        EnvVars envVars = new EnvVars();\n        WorkflowJob job = r.createProject(WorkflowJob.class);\n        job.setDefinition(new CpsFlowDefinition(\"echo 'test'\", true));\n        Run<?, ?> run = r.buildAndAssertSuccess(job);\n\n        assertDoesNotThrow(() -> paramValue.buildEnvironment(run, envVars));\n        assertThat(envVars.get(\"JIRA_ISSUE\"), is(\"TEST-123\"));\n    }\n\n    @Test\n    void variableResolverHandlesNullAndNonNullValues() throws Exception {\n        // Test with null value\n        JiraIssueParameterValue nullParamValue = new JiraIssueParameterValue(\"JIRA_ISSUE\", null);\n        hudson.util.VariableResolver<String> nullResolver = nullParamValue.createVariableResolver(null);\n\n        // Should return empty string for matching parameter name with null value\n        assertThat(nullResolver.resolve(\"JIRA_ISSUE\"), is(\"\"));\n        // Should return null for non-matching parameter name\n        assertThat(nullResolver.resolve(\"OTHER_PARAM\"), is(nullValue()));\n\n        // Test with non-null value\n        JiraIssueParameterValue nonNullParamValue = new JiraIssueParameterValue(\"JIRA_ISSUE\", \"TEST-123\");\n        hudson.util.VariableResolver<String> nonNullResolver = nonNullParamValue.createVariableResolver(null);\n\n        // Should return the actual value for matching parameter name\n        assertThat(nonNullResolver.resolve(\"JIRA_ISSUE\"), is(\"TEST-123\"));\n        // Should return null for non-matching parameter name\n        assertThat(nonNullResolver.resolve(\"OTHER_PARAM\"), is(nullValue()));\n    }\n\n    @Test\n    void originalNullPointerExceptionScenarioFixed(JenkinsRule r) throws Exception {\n        // This test replicates the original issue scenario where a null JIRA_ISSUE parameter\n        // would cause a NullPointerException during pipeline execution with ${params.JIRA_ISSUE}\n\n        JiraIssueParameterValue paramValue = new JiraIssueParameterValue(\"JIRA_ISSUE\", null);\n\n        // Test the buildEnvironment path (used when setting environment variables)\n        EnvVars envVars = new EnvVars();\n        WorkflowJob job = r.createProject(WorkflowJob.class);\n        job.setDefinition(new CpsFlowDefinition(\"echo \\\"Ticket: ${params.JIRA_ISSUE}\\\"\", true));\n        Run<?, ?> run = r.buildAndAssertSuccess(job);\n\n        // This should not throw NPE and should set empty string\n        assertDoesNotThrow(() -> paramValue.buildEnvironment(run, envVars));\n        assertThat(envVars.get(\"JIRA_ISSUE\"), is(\"\"));\n\n        // Test the variable resolver path (used during pipeline script evaluation)\n        hudson.util.VariableResolver<String> resolver = paramValue.createVariableResolver(null);\n\n        // This should not throw NPE and should return empty string\n        String resolvedValue = assertDoesNotThrow(() -> resolver.resolve(\"JIRA_ISSUE\"));\n        assertThat(resolvedValue, is(\"\"));\n    }\n}\n"
  },
  {
    "path": "src/test/java/hudson/plugins/jira/pipeline/CommentStepTest.java",
    "content": "package hudson.plugins.jira.pipeline;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.collection.IsCollectionWithSize.hasSize;\nimport static org.hamcrest.core.IsEqual.equalTo;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.mockito.Mockito.*;\n\nimport com.atlassian.jira.rest.client.api.RestClientException;\nimport com.google.inject.Inject;\nimport hudson.model.*;\nimport hudson.plugins.jira.JiraProjectProperty;\nimport hudson.plugins.jira.JiraSession;\nimport hudson.plugins.jira.JiraSite;\nimport hudson.plugins.jira.pipeline.CommentStep.CommentStepExecution;\nimport java.io.PrintStream;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport org.jenkinsci.plugins.workflow.steps.StepConfigTester;\nimport org.jenkinsci.plugins.workflow.steps.StepContext;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.jvnet.hudson.test.JenkinsRule;\nimport org.jvnet.hudson.test.junit.jupiter.WithJenkins;\nimport org.mockito.Mockito;\n\n@WithJenkins\nclass CommentStepTest {\n\n    private JenkinsRule jenkinsRule;\n\n    @Inject\n    CommentStep.DescriptorImpl descriptor;\n\n    @BeforeEach\n    void setUp(JenkinsRule jenkinsRule) {\n        this.jenkinsRule = jenkinsRule;\n        jenkinsRule.getInstance().getInjector().injectMembers(this);\n    }\n\n    @Test\n    void configRoundTrip() throws Exception {\n        configRoundTrip(\"EXAMPLE-1\", \"comment\");\n    }\n\n    private void configRoundTrip(String issueKey, String body) throws Exception {\n        CommentStep configRoundTrip =\n                new StepConfigTester(jenkinsRule).configRoundTrip(new CommentStep(issueKey, body));\n\n        assertEquals(issueKey, configRoundTrip.getIssueKey());\n        assertEquals(body, configRoundTrip.getBody());\n    }\n\n    @Test\n    void callSessionAddComment() throws Exception {\n        JiraSession session = mock(JiraSession.class);\n        final String issueKey = \"KEY\";\n        final String body = \"dsgsags\";\n\n        AbstractProject mockProject = mock(FreeStyleProject.class);\n        Run mockRun = mock(Run.class);\n        JiraProjectProperty jiraProjectProperty = mock(JiraProjectProperty.class);\n        JiraSite site = mock(JiraSite.class);\n\n        when(jiraProjectProperty.getSite()).thenReturn(site);\n        when(site.getSession(mockProject)).thenReturn(session);\n        when(mockRun.getParent()).thenReturn(mockProject);\n        when(mockRun.getParent().getProperty(JiraProjectProperty.class)).thenReturn(jiraProjectProperty);\n\n        final List<Object> assertCalledParams = new ArrayList<>();\n\n        Mockito.doAnswer(invocation -> {\n                    String issueId = invocation.getArgument(0, String.class);\n                    String comment = invocation.getArgument(1, String.class);\n                    System.out.println(\"issueId: \" + issueId);\n                    System.out.println(\"comment: \" + comment);\n                    assertThat(issueId, equalTo(issueKey));\n                    assertThat(comment, equalTo(body));\n                    assertCalledParams.addAll(Arrays.asList(invocation.getArguments()));\n                    return null;\n                })\n                .when(session)\n                .addComment(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any());\n\n        Map<String, Object> r = new HashMap<>();\n        r.put(\"issueKey\", issueKey);\n        r.put(\"body\", body);\n        CommentStep step = (CommentStep) descriptor.newInstance(r);\n\n        StepContext ctx = mock(StepContext.class);\n        when(ctx.get(Node.class)).thenReturn(jenkinsRule.getInstance());\n        when(ctx.get(Run.class)).thenReturn(mockRun);\n\n        assertThat(assertCalledParams, hasSize(0));\n\n        CommentStepExecution start = (CommentStepExecution) step.start(ctx);\n        start.run();\n\n        assertThat(assertCalledParams, hasSize(4));\n    }\n\n    @Test\n    void addCommentRestException() throws Exception {\n        JiraSession session = mock(JiraSession.class);\n        final String issueKey = \"KEY\";\n        final String body = \"dsgsags\";\n\n        AbstractProject mockProject = mock(FreeStyleProject.class);\n        Run mockRun = mock(Run.class);\n        JiraProjectProperty jiraProjectProperty = mock(JiraProjectProperty.class);\n        JiraSite site = mock(JiraSite.class);\n\n        when(jiraProjectProperty.getSite()).thenReturn(site);\n        when(site.getSession(mockProject)).thenReturn(session);\n        when(mockRun.getParent()).thenReturn(mockProject);\n        when(mockRun.getParent().getProperty(JiraProjectProperty.class)).thenReturn(jiraProjectProperty);\n\n        final List<Object> assertCalledParams = new ArrayList<>();\n\n        Throwable throwable = mock(Throwable.class);\n        PrintStream logger = mock(PrintStream.class);\n        TaskListener listener = mock(TaskListener.class);\n        Mockito.doThrow(new RestClientException(\"[Jira] Jira REST addComment error. Cause: 401 error\", throwable))\n                .when(session)\n                .addComment(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any());\n\n        Map<String, Object> r = new HashMap<>();\n        r.put(\"issueKey\", issueKey);\n        r.put(\"body\", body);\n        CommentStep step = (CommentStep) descriptor.newInstance(r);\n\n        StepContext ctx = mock(StepContext.class);\n        when(ctx.get(Node.class)).thenReturn(jenkinsRule.getInstance());\n        when(ctx.get(Run.class)).thenReturn(mockRun);\n        when(ctx.get(TaskListener.class)).thenReturn(listener);\n        when(listener.getLogger()).thenReturn(logger);\n        assertThat(assertCalledParams, hasSize(0));\n\n        CommentStepExecution start = (CommentStepExecution) step.start(ctx);\n        start.run();\n        verify(logger).println(\"[Jira] Jira REST addComment error. Cause: 401 error\");\n    }\n}\n"
  },
  {
    "path": "src/test/java/hudson/plugins/jira/pipeline/IssueFieldUpdateStepTest.java",
    "content": "package hudson.plugins.jira.pipeline;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.containsString;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyList;\nimport static org.mockito.ArgumentMatchers.anyString;\nimport static org.mockito.Mockito.*;\nimport static org.mockito.Mockito.doThrow;\n\nimport com.atlassian.jira.rest.client.api.RestClientException;\nimport hudson.EnvVars;\nimport hudson.Launcher;\nimport hudson.model.*;\nimport hudson.plugins.jira.JiraGlobalConfiguration;\nimport hudson.plugins.jira.JiraSession;\nimport hudson.plugins.jira.JiraSite;\nimport hudson.plugins.jira.model.JiraIssueField;\nimport hudson.plugins.jira.selector.ExplicitIssueSelector;\nimport io.jenkins.plugins.casc.misc.JenkinsConfiguredWithCodeRule;\nimport io.jenkins.plugins.casc.misc.junit.jupiter.WithJenkinsConfiguredWithCode;\nimport java.io.IOException;\nimport java.io.PrintStream;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Random;\nimport org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition;\nimport org.jenkinsci.plugins.workflow.job.WorkflowJob;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.jvnet.hudson.test.JenkinsRule;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\n\n/** @author Dmitry Frolov tekillaz.dev@gmail.com */\n@WithJenkinsConfiguredWithCode\n@ExtendWith(MockitoExtension.class)\nclass IssueFieldUpdateStepTest {\n\n    private static final String BUILD_NUMBER_VAR = \"${BUILD_NUMBER}\";\n\n    @Mock(strictness = Mock.Strictness.LENIENT)\n    JiraSite site;\n\n    @Mock(strictness = Mock.Strictness.LENIENT)\n    JiraSession session;\n\n    @Mock\n    PrintStream logger;\n\n    @Mock(strictness = Mock.Strictness.LENIENT)\n    AbstractBuild build;\n\n    @Mock(strictness = Mock.Strictness.LENIENT)\n    Launcher launcher;\n\n    @Mock(strictness = Mock.Strictness.LENIENT)\n    BuildListener listener;\n\n    @Mock\n    AbstractProject project;\n\n    @Mock(strictness = Mock.Strictness.LENIENT)\n    Job job;\n\n    private JenkinsRule r;\n\n    @BeforeEach\n    void before(JenkinsConfiguredWithCodeRule r) {\n        this.r = r;\n        when(build.getParent()).thenReturn(project);\n        when(build.getProject()).thenReturn(project);\n        when(listener.getLogger()).thenReturn(logger);\n        doReturn(session).when(site).getSession(any());\n        JiraGlobalConfiguration.get().setSites(Collections.singletonList(site));\n        //    when(jiraGlobalConfiguration.getSites()).thenReturn(sites);\n        //    .getSession(build.getParent())).thenReturn(session);\n        when(job.getBuild(any())).thenReturn(build);\n    }\n\n    @Test\n    void checkPrepareFieldId() {\n\n        List<String> field_test = Arrays.asList(\"10100\", \"customfield_10100\", \"field_10100\");\n\n        List<String> field_after = Arrays.asList(\"customfield_10100\", \"customfield_10100\", \"customfield_field_10100\");\n\n        IssueFieldUpdateStep jifu = new IssueFieldUpdateStep(null, null, \"\");\n        for (int i = 0; i < field_test.size(); i++) {\n            assertEquals(jifu.prepareFieldId(field_test.get(i)), field_after.get(i), \"Check field id conversion #\" + i);\n        }\n    }\n\n    @Test\n    void shouldFailIfSelectorIsNull() throws IOException, InterruptedException {\n        IssueFieldUpdateStep jifu = spy(new IssueFieldUpdateStep(null, \"\", \"\"));\n        assertThrows(IOException.class, () -> jifu.perform(build, null, launcher, listener));\n    }\n\n    //  @ConfiguredWithCode(\"single-site.yml\")\n    @Test\n    void checkSubmit() throws InterruptedException, IOException {\n        Random random = new Random();\n        Integer randomBuildNumber = random.nextInt(85) + 15; // random number 15 < r < 99\n        String issueId = \"ISSUE-\" + random.nextInt(1000) + 999;\n        String beforeFieldid = \"field\" + random.nextInt(100) + 99;\n        String beforeFieldValue = \"Some comment, build #${BUILD_NUMBER}\";\n\n        EnvVars env = new EnvVars();\n        env.put(\"BUILD_NUMBER\", randomBuildNumber.toString());\n        when(build.getEnvironment(listener)).thenReturn(env);\n\n        final ExplicitIssueSelector issueSelector = new ExplicitIssueSelector(issueId);\n        final List<String> issuesAfter = new ArrayList<String>();\n        final List<JiraIssueField> fieldsAfter = new ArrayList<JiraIssueField>();\n\n        doAnswer(invocation -> {\n                    String id = (String) invocation.getArguments()[0];\n                    List<JiraIssueField> jif = (List<JiraIssueField>) invocation.getArguments()[1];\n                    issuesAfter.add(id);\n                    fieldsAfter.addAll(jif);\n                    return null;\n                })\n                .when(session)\n                .addFields(anyString(), anyList());\n\n        IssueFieldUpdateStep jifu = spy(new IssueFieldUpdateStep(issueSelector, beforeFieldid, beforeFieldValue));\n        jifu.perform(build, null, launcher, listener);\n\n        assertEquals(issuesAfter.get(0), issueId, \"Check issue value\");\n        assertEquals(\"customfield_\" + beforeFieldid, fieldsAfter.get(0).getId());\n        assertThat(fieldsAfter.get(0).getValue().toString(), containsString(\"build #\" + randomBuildNumber.toString()));\n\n        jifu.perform(build, null, env, launcher, listener);\n        assertThat(fieldsAfter.get(1).getValue().toString(), containsString(\"build #\" + randomBuildNumber.toString()));\n    }\n\n    @Test\n    void testPipeline() throws Exception {\n        WorkflowJob job = r.createProject(WorkflowJob.class);\n        job.setDefinition(new CpsFlowDefinition(\"\"\"\n                        jiraUpdateIssueField(\n                                issueSelector: ExplicitSelector(\"JIRA-123\"),\n                                fieldId: \"field\",\n                                fieldValue: \"value\"\n                            )\n                \"\"\", true));\n        r.buildAndAssertStatus(Result.SUCCESS, job);\n    }\n\n    @Test\n    void issueFieldFindIssueIdsRestException() throws IOException, InterruptedException {\n        Random random = new Random();\n        Integer randomBuildNumber = random.nextInt(85) + 15; // random number 15 < r < 99\n        String issueId = \"ISSUE-\" + random.nextInt(1000) + 999;\n        String beforeFieldid = \"field\" + random.nextInt(100) + 99;\n        String beforeFieldValue = \"Some comment, build #${BUILD_NUMBER}\";\n\n        EnvVars env = new EnvVars();\n        env.put(\"BUILD_NUMBER\", randomBuildNumber.toString());\n        when(build.getEnvironment(listener)).thenReturn(env);\n\n        final ExplicitIssueSelector issueSelector = spy(new ExplicitIssueSelector(issueId));\n        Throwable throwable = mock(Throwable.class);\n        PrintStream logger = mock(PrintStream.class);\n        when(listener.getLogger()).thenReturn(logger);\n        final List<String> issuesAfter = new ArrayList<String>();\n        final List<JiraIssueField> fieldsAfter = new ArrayList<JiraIssueField>();\n        doAnswer(invocation -> {\n                    String id = (String) invocation.getArguments()[0];\n                    List<JiraIssueField> jif = (List<JiraIssueField>) invocation.getArguments()[1];\n                    issuesAfter.add(id);\n                    fieldsAfter.addAll(jif);\n                    return null;\n                })\n                .when(session)\n                .addFields(anyString(), anyList());\n\n        IssueFieldUpdateStep jifu = spy(new IssueFieldUpdateStep(issueSelector, beforeFieldid, beforeFieldValue));\n        doThrow(new RestClientException(\"[Jira] Jira REST findIssueIds error. Cause: 401 error\", throwable))\n                .when(issueSelector)\n                .findIssueIds(build, site, listener);\n        jifu.perform(build, null, launcher, listener);\n        verify(logger).println(\"[Jira] Jira REST findIssueIds error. Cause: 401 error\");\n    }\n\n    @Test\n    void issueFieldSubmitFieldsRestException() throws IOException, InterruptedException {\n        Random random = new Random();\n        Integer randomBuildNumber = random.nextInt(85) + 15; // random number 15 < r < 99\n        String issueId = \"ISSUE-\" + random.nextInt(1000) + 999;\n        String beforeFieldid = \"field\" + random.nextInt(100) + 99;\n        String beforeFieldValue = \"Some comment, build #${BUILD_NUMBER}\";\n\n        EnvVars env = new EnvVars();\n        env.put(\"BUILD_NUMBER\", randomBuildNumber.toString());\n        when(build.getEnvironment(listener)).thenReturn(env);\n\n        final ExplicitIssueSelector issueSelector = new ExplicitIssueSelector(issueId);\n        Throwable throwable = mock(Throwable.class);\n        PrintStream logger = mock(PrintStream.class);\n        when(listener.getLogger()).thenReturn(logger);\n        final List<String> issuesAfter = new ArrayList<String>();\n        final List<JiraIssueField> fieldsAfter = new ArrayList<JiraIssueField>();\n        doAnswer(invocation -> {\n                    String id = (String) invocation.getArguments()[0];\n                    List<JiraIssueField> jif = (List<JiraIssueField>) invocation.getArguments()[1];\n                    issuesAfter.add(id);\n                    fieldsAfter.addAll(jif);\n                    return null;\n                })\n                .when(session)\n                .addFields(anyString(), anyList());\n\n        IssueFieldUpdateStep jifu = spy(new IssueFieldUpdateStep(issueSelector, beforeFieldid, beforeFieldValue));\n        doThrow(new RestClientException(\"[Jira] Jira REST submitFields error. Cause: 401 error\", throwable))\n                .when(jifu)\n                .submitFields(any(JiraSession.class), anyString(), anyList(), any(PrintStream.class));\n        jifu.perform(build, null, launcher, listener);\n        verify(logger).println(\"[Jira] Jira REST submitFields error. Cause: 401 error\");\n    }\n}\n"
  },
  {
    "path": "src/test/java/hudson/plugins/jira/pipeline/IssueSelectorStepTest.java",
    "content": "package hudson.plugins.jira.pipeline;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.collection.IsCollectionWithSize.hasSize;\nimport static org.mockito.Mockito.*;\n\nimport com.atlassian.jira.rest.client.api.RestClientException;\nimport com.google.inject.Inject;\nimport hudson.model.Node;\nimport hudson.model.Result;\nimport hudson.model.Run;\nimport hudson.model.TaskListener;\nimport hudson.plugins.jira.JiraGlobalConfiguration;\nimport hudson.plugins.jira.JiraSite;\nimport hudson.plugins.jira.selector.AbstractIssueSelector;\nimport java.io.PrintStream;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Set;\nimport org.jenkinsci.plugins.workflow.steps.StepContext;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.jvnet.hudson.test.JenkinsRule;\nimport org.jvnet.hudson.test.junit.jupiter.WithJenkins;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\n\n@WithJenkins\n@ExtendWith(MockitoExtension.class)\nclass IssueSelectorStepTest {\n\n    @Inject\n    private IssueSelectorStep.DescriptorImpl descriptor;\n\n    @Mock\n    private AbstractIssueSelector issueSelector;\n\n    @Mock(strictness = Mock.Strictness.LENIENT)\n    private TaskListener listener;\n\n    @Mock\n    private PrintStream logger;\n\n    @Mock\n    private Run run;\n\n    @Mock(strictness = Mock.Strictness.LENIENT)\n    private StepContext stepContext;\n\n    private IssueSelectorStep.IssueSelectorStepExecution stepExecution;\n    private IssueSelectorStep subject;\n\n    @BeforeEach\n    void setUp(JenkinsRule jenkinsRule) throws Exception {\n        jenkinsRule.getInstance().getInjector().injectMembers(this);\n\n        when(listener.getLogger()).thenReturn(logger);\n        when(stepContext.get(Node.class)).thenReturn(jenkinsRule.getInstance());\n        when(stepContext.get(Run.class)).thenReturn(run);\n        when(stepContext.get(TaskListener.class)).thenReturn(listener);\n\n        subject = (IssueSelectorStep) descriptor.newInstance(new HashMap<>());\n        subject.setIssueSelector(issueSelector);\n    }\n\n    @Test\n    void runWithNullSite() throws Exception {\n        stepExecution = spy((IssueSelectorStep.IssueSelectorStepExecution) subject.start(stepContext));\n        // doReturn(Optional.empty()).when(stepExecution).getOptionalJiraSite();\n\n        doCallRealMethod().when(run).getParent();\n        Set<String> ids = stepExecution.run();\n\n        verify(run, times(1)).setResult(Result.FAILURE);\n        assertThat(ids, hasSize(0));\n    }\n\n    @Test\n    void run() throws Exception {\n        JiraSite site = mock(JiraSite.class);\n        JiraGlobalConfiguration jiraGlobalConfiguration = JiraGlobalConfiguration.get();\n        jiraGlobalConfiguration.setSites(Collections.singletonList(site));\n        stepExecution = spy((IssueSelectorStep.IssueSelectorStepExecution) subject.start(stepContext));\n        doCallRealMethod().when(run).getParent();\n\n        stepExecution.run();\n\n        verify(run, times(0)).setResult(Result.FAILURE);\n        verify(issueSelector).findIssueIds(run, site, listener);\n    }\n\n    @Test\n    void runWithRestException() throws Exception {\n        JiraSite site = mock(JiraSite.class);\n        JiraGlobalConfiguration jiraGlobalConfiguration = JiraGlobalConfiguration.get();\n        jiraGlobalConfiguration.setSites(Collections.singletonList(site));\n        stepExecution = spy((IssueSelectorStep.IssueSelectorStepExecution) subject.start(stepContext));\n        doCallRealMethod().when(run).getParent();\n        Throwable throwable = mock(Throwable.class);\n        doThrow(new RestClientException(\"[Jira] Jira REST findIssueIds error. Cause: 401 error\", throwable))\n                .when(issueSelector)\n                .findIssueIds(run, site, listener);\n        stepExecution.run();\n        verify(run, times(1)).setResult(Result.FAILURE);\n        verify(logger).println(\"[Jira] Jira REST findIssueIds error. Cause: 401 error\");\n    }\n}\n"
  },
  {
    "path": "src/test/java/hudson/plugins/jira/pipeline/SearchIssuesStepTest.java",
    "content": "package hudson.plugins.jira.pipeline;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.collection.IsCollectionWithSize.hasSize;\nimport static org.hamcrest.core.IsEqual.equalTo;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.mockito.Mockito.*;\n\nimport com.atlassian.jira.rest.client.api.RestClientException;\nimport com.atlassian.jira.rest.client.api.domain.Issue;\nimport com.google.inject.Inject;\nimport hudson.model.*;\nimport hudson.plugins.jira.JiraProjectProperty;\nimport hudson.plugins.jira.JiraSession;\nimport hudson.plugins.jira.JiraSite;\nimport hudson.plugins.jira.pipeline.SearchIssuesStep.SearchStepExecution;\nimport java.io.PrintStream;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport org.jenkinsci.plugins.workflow.steps.StepConfigTester;\nimport org.jenkinsci.plugins.workflow.steps.StepContext;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.jvnet.hudson.test.JenkinsRule;\nimport org.jvnet.hudson.test.junit.jupiter.WithJenkins;\n\n@WithJenkins\nclass SearchIssuesStepTest {\n\n    private JenkinsRule jenkinsRule;\n\n    @Inject\n    SearchIssuesStep.DescriptorImpl descriptor;\n\n    @BeforeEach\n    void setUp(JenkinsRule jenkinsRule) throws Exception {\n        this.jenkinsRule = jenkinsRule;\n        jenkinsRule.getInstance().getInjector().injectMembers(this);\n    }\n\n    @Test\n    void configRoundTrip() throws Exception {\n        configRoundTrip(\"\");\n        configRoundTrip(\"key='EXAMPLE-1'\");\n    }\n\n    private void configRoundTrip(String jql) throws Exception {\n        SearchIssuesStep step = new StepConfigTester(jenkinsRule).configRoundTrip(new SearchIssuesStep(jql));\n        assertEquals(jql, step.getJql());\n    }\n\n    @Test\n    void callGetIssuesFromJqlSearch() throws Exception {\n        JiraSession session = mock(JiraSession.class);\n        String jql = \"key='EXAMPLE-1'\";\n        Issue issue = mock(Issue.class);\n        when(issue.getKey()).thenReturn(\"EXAMPLE-1\");\n\n        final List<Issue> assertCalledList = new ArrayList<>();\n        when(session.getIssuesFromJqlSearch(jql)).then(invocation -> {\n            Issue issue2 = mock(Issue.class);\n            when(issue2.getKey()).thenReturn(\"EXAMPLE-1\");\n            assertCalledList.add(issue2);\n            return assertCalledList;\n        });\n\n        JiraSite site = mock(JiraSite.class);\n\n        AbstractProject mockProject = mock(FreeStyleProject.class);\n        Run mockRun = mock(Run.class);\n        Job mockJob = mock(Job.class);\n        JiraProjectProperty jiraProjectProperty = mock(JiraProjectProperty.class);\n\n        when(jiraProjectProperty.getSite()).thenReturn(site);\n        when(site.getSession(mockProject)).thenReturn(session);\n        when(mockRun.getParent()).thenReturn(mockProject);\n        when(mockRun.getParent().getProperty(JiraProjectProperty.class)).thenReturn(jiraProjectProperty);\n\n        Map<String, Object> r = new HashMap<>();\n        r.put(\"jql\", jql);\n        SearchIssuesStep step = (SearchIssuesStep) descriptor.newInstance(r);\n\n        StepContext ctx = mock(StepContext.class);\n        when(ctx.get(Node.class)).thenReturn(jenkinsRule.getInstance());\n        when(ctx.get(Run.class)).thenReturn(mockRun);\n\n        SearchStepExecution start = (SearchStepExecution) step.start(ctx);\n        List<String> returnedList = start.run();\n        assertThat(assertCalledList, hasSize(1));\n        assertThat(returnedList, hasSize(1));\n        assertThat(assertCalledList.iterator().next().getKey(), equalTo(\"EXAMPLE-1\"));\n        assertThat(returnedList.iterator().next(), equalTo(\"EXAMPLE-1\"));\n    }\n\n    @Test\n    void getIssuesFromJqlSearchRestException() throws Exception {\n        JiraSession session = mock(JiraSession.class);\n        String jql = \"key='EXAMPLE-1'\";\n        Issue issue = mock(Issue.class);\n        when(issue.getKey()).thenReturn(\"EXAMPLE-1\");\n\n        Throwable throwable = mock(Throwable.class);\n        doThrow(new RestClientException(\"[Jira] Jira REST getIssuesFromJqlSearch error. Cause: 401 error\", throwable))\n                .when(session)\n                .getIssuesFromJqlSearch(jql);\n\n        JiraSite site = mock(JiraSite.class);\n\n        AbstractProject mockProject = mock(FreeStyleProject.class);\n        Run mockRun = mock(Run.class);\n        JiraProjectProperty jiraProjectProperty = mock(JiraProjectProperty.class);\n\n        when(jiraProjectProperty.getSite()).thenReturn(site);\n        when(site.getSession(mockProject)).thenReturn(session);\n        when(mockRun.getParent()).thenReturn(mockProject);\n        when(mockRun.getParent().getProperty(JiraProjectProperty.class)).thenReturn(jiraProjectProperty);\n\n        Map<String, Object> r = new HashMap<>();\n        r.put(\"jql\", jql);\n        SearchIssuesStep step = (SearchIssuesStep) descriptor.newInstance(r);\n\n        StepContext ctx = mock(StepContext.class);\n        when(ctx.get(Node.class)).thenReturn(jenkinsRule.getInstance());\n        when(ctx.get(Run.class)).thenReturn(mockRun);\n        TaskListener listener = mock(TaskListener.class);\n        PrintStream logger = mock(PrintStream.class);\n        when(ctx.get(TaskListener.class)).thenReturn(listener);\n        when(listener.getLogger()).thenReturn(logger);\n        SearchStepExecution start = (SearchStepExecution) step.start(ctx);\n        start.run();\n        verify(logger).println(\"[Jira] Jira REST getIssuesFromJqlSearch error. Cause: 401 error\");\n    }\n}\n"
  },
  {
    "path": "src/test/java/hudson/plugins/jira/selector/DefaultIssueSelectorTest.java",
    "content": "package hudson.plugins.jira.selector;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nimport hudson.model.BuildListener;\nimport hudson.model.FreeStyleBuild;\nimport hudson.model.ParameterValue;\nimport hudson.model.ParametersAction;\nimport hudson.model.User;\nimport hudson.plugins.jira.JiraSite;\nimport hudson.plugins.jira.listissuesparameter.JiraIssueParameterValue;\nimport hudson.scm.ChangeLogSet;\nimport hudson.scm.ChangeLogSet.Entry;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.LinkedHashSet;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.TreeSet;\nimport java.util.regex.Pattern;\nimport org.junit.jupiter.api.Test;\nimport org.jvnet.hudson.test.Issue;\n\nclass DefaultIssueSelectorTest {\n\n    private static class MockEntry extends Entry {\n\n        private final String msg;\n\n        MockEntry(String msg) {\n            this.msg = msg;\n        }\n\n        @Override\n        public Collection<String> getAffectedPaths() {\n            return null;\n        }\n\n        @Override\n        public User getAuthor() {\n            return null;\n        }\n\n        @Override\n        public String getMsg() {\n            return this.msg;\n        }\n    }\n\n    @Test\n    @Issue(\"4132\")\n    void projectNamesAllowed() {\n        FreeStyleBuild build = mock(FreeStyleBuild.class);\n        ChangeLogSet changeLogSet = mock(ChangeLogSet.class);\n        BuildListener listener = mock(BuildListener.class);\n        JiraSite site = mock(JiraSite.class);\n\n        when(site.getIssuePattern()).thenReturn(JiraSite.DEFAULT_ISSUE_PATTERN);\n\n        Set<? extends Entry> entries = new HashSet(Arrays.asList(\n                new MockEntry(\"Fixed JI123-4711\"),\n                new MockEntry(\"Fixed foo_bar-4710\"),\n                new MockEntry(\"Fixed FoO_bAr-4711\"),\n                new MockEntry(\"Fixed something.\\nJFoO_bAr_MULTI-4718\"),\n                new MockEntry(\"TR-123: foo\"),\n                new MockEntry(\"[ABC-42] hallo\"),\n                new MockEntry(\"#123: this one must not match\"),\n                new MockEntry(\"ABC-: this one must also not match\"),\n                new MockEntry(\"ABC-: \\n\\nABC-127:\\nthis one should match\"),\n                new MockEntry(\"ABC-: \\n\\nABC-128:\\nthis one should match\"),\n                new MockEntry(\"ABC-: \\n\\nXYZ-10:\\nXYZ-20 this one too\"),\n                new MockEntry(\"Fixed DOT-4.\"),\n                new MockEntry(\"Fixed DOT-5. Did it right this time\")));\n\n        when(build.getChangeSet()).thenReturn(changeLogSet);\n        when(changeLogSet.iterator()).thenReturn(entries.iterator());\n\n        List<ChangeLogSet<? extends ChangeLogSet.Entry>> changeSets = new ArrayList<>();\n        changeSets.add(changeLogSet);\n        when(build.getChangeSets()).thenReturn(changeSets);\n\n        Set<String> expected = new HashSet(Arrays.asList(\n                \"JI123-4711\",\n                \"FOO_BAR-4710\",\n                \"FOO_BAR-4711\",\n                \"JFOO_BAR_MULTI-4718\",\n                \"TR-123\",\n                \"ABC-42\",\n                \"ABC-127\",\n                \"ABC-128\",\n                \"XYZ-10\",\n                \"XYZ-20\",\n                \"DOT-4\",\n                \"DOT-5\"));\n\n        Set<String> result = new DefaultIssueSelector().findIssueIds(build, site, listener);\n\n        assertEquals(expected.size(), result.size());\n        assertEquals(expected, result);\n    }\n\n    /**\n     * Tests that the JiraIssueParameters are identified as updateable Jira\n     * issues.\n     */\n    @Test\n    @Issue(\"12312\")\n    void findIssuesWithJiraParameters() {\n        FreeStyleBuild build = mock(FreeStyleBuild.class);\n        ChangeLogSet changeLogSet = mock(ChangeLogSet.class);\n        BuildListener listener = mock(BuildListener.class);\n        JiraSite site = mock(JiraSite.class);\n\n        when(site.getIssuePattern()).thenReturn(JiraSite.DEFAULT_ISSUE_PATTERN);\n\n        JiraIssueParameterValue parameter = mock(JiraIssueParameterValue.class);\n        JiraIssueParameterValue parameterTwo = mock(JiraIssueParameterValue.class);\n        ParametersAction action = mock(ParametersAction.class);\n        List<ParameterValue> parameters = new ArrayList<>();\n\n        when(changeLogSet.iterator()).thenReturn(Collections.EMPTY_LIST.iterator());\n        when(build.getChangeSet()).thenReturn(changeLogSet);\n        when(build.getAction(ParametersAction.class)).thenReturn(action);\n        when(action.getParameters()).thenReturn(parameters);\n        when(parameter.getValue()).thenReturn(\"JIRA-123\");\n        when(parameterTwo.getValue()).thenReturn(\"JIRA-321\");\n\n        // Initial state contains zero parameters\n        Set<String> ids = new DefaultIssueSelector().findIssueIds(build, site, listener);\n        assertTrue(ids.isEmpty());\n\n        parameters.add(parameter);\n        ids = new DefaultIssueSelector().findIssueIds(build, site, listener);\n        assertEquals(1, ids.size());\n        assertEquals(\"JIRA-123\", ids.iterator().next());\n\n        parameters.add(parameterTwo);\n        ids = new DefaultIssueSelector().findIssueIds(build, site, listener);\n        assertEquals(2, ids.size());\n        Set<String> expected = new TreeSet(Arrays.asList(\"JIRA-123\", \"JIRA-321\"));\n        assertEquals(expected, ids);\n    }\n\n    @Test\n    @Issue(\"6043\")\n    void userPatternNotMatch() {\n        FreeStyleBuild build = mock(FreeStyleBuild.class);\n        ChangeLogSet changeLogSet = mock(ChangeLogSet.class);\n        when(build.getChangeSet()).thenReturn(changeLogSet);\n\n        Set<? extends Entry> entries = new HashSet(Arrays.asList(new MockEntry(\"Fixed FOO_BAR-4711\")));\n        when(changeLogSet.iterator()).thenReturn(entries.iterator());\n\n        Set<String> ids = new LinkedHashSet<>();\n        DefaultIssueSelector.findIssues(build, ids, Pattern.compile(\"[(w)]\"), mock(BuildListener.class));\n\n        assertEquals(0, ids.size());\n    }\n\n    @Test\n    @Issue(\"6043\")\n    void userPatternMatchTwoIssuesInOneComment() {\n        FreeStyleBuild build = mock(FreeStyleBuild.class);\n        ChangeLogSet changeLogSet = mock(ChangeLogSet.class);\n        when(build.getChangeSet()).thenReturn(changeLogSet);\n\n        Set<? extends Entry> entries = new HashSet(Arrays.asList(\n                new MockEntry(\"Fixed toto [FOOBAR-4711]  [FOOBAR-21] \"),\n                new MockEntry(\"[TEST-9] with [dede]\"),\n                new MockEntry(\"toto [maven-release-plugin] prepare release foo-2.2.3\")));\n        when(changeLogSet.iterator()).thenReturn(entries.iterator());\n\n        List<ChangeLogSet<? extends ChangeLogSet.Entry>> changeSets = new ArrayList<ChangeLogSet<? extends Entry>>();\n        changeSets.add(changeLogSet);\n        when(build.getChangeSets()).thenReturn(changeSets);\n\n        Set<String> ids = new LinkedHashSet<>();\n        Pattern pat = Pattern.compile(\"\\\\[(\\\\w+-\\\\d+)\\\\]\");\n        DefaultIssueSelector.findIssues(build, ids, pat, mock(BuildListener.class));\n        assertEquals(3, ids.size());\n        assertTrue(ids.contains(\"TEST-9\"));\n        assertTrue(ids.contains(\"FOOBAR-4711\"));\n        assertTrue(ids.contains(\"FOOBAR-21\"));\n    }\n\n    @Test\n    @Issue(\"6043\")\n    void userPatternMatch() {\n        FreeStyleBuild build = mock(FreeStyleBuild.class);\n        ChangeLogSet changeLogSet = mock(ChangeLogSet.class);\n        when(build.getChangeSet()).thenReturn(changeLogSet);\n\n        Set<? extends Entry> entries = new HashSet(Arrays.asList(\n                new MockEntry(\"Fixed toto [FOOBAR-4711]\"),\n                new MockEntry(\"[TEST-9] with [dede]\"),\n                new MockEntry(\"toto [maven-release-plugin] prepare release foo-2.2.3\")));\n        when(changeLogSet.iterator()).thenReturn(entries.iterator());\n\n        List<ChangeLogSet<? extends ChangeLogSet.Entry>> changeSets = new ArrayList<>();\n        changeSets.add(changeLogSet);\n        when(build.getChangeSets()).thenReturn(changeSets);\n\n        Set<String> ids = new LinkedHashSet<>();\n        Pattern pat = Pattern.compile(\"\\\\[(\\\\w+-\\\\d+)\\\\]\");\n        DefaultIssueSelector.findIssues(build, ids, pat, mock(BuildListener.class));\n        assertEquals(2, ids.size());\n        assertTrue(ids.contains(\"TEST-9\"));\n        assertTrue(ids.contains(\"FOOBAR-4711\"));\n    }\n\n    /**\n     * Tests that the default pattern doesn't match strings like 'project-1.1'.\n     * These patterns are used e.g. by the maven release plugin.\n     */\n    @Test\n    void defaultPatternNotToMatchMavenRelease() {\n        FreeStyleBuild build = mock(FreeStyleBuild.class);\n        ChangeLogSet changeLogSet = mock(ChangeLogSet.class);\n        when(build.getChangeSet()).thenReturn(changeLogSet);\n\n        // commit messages like the one from the Maven release plugin must not\n        // match\n        Set<? extends Entry> entries = new HashSet(Arrays.asList(new MockEntry(\"prepare release project-4.7.1\")));\n        when(changeLogSet.iterator()).thenReturn(entries.iterator());\n\n        Set<String> ids = new LinkedHashSet<>();\n        DefaultIssueSelector.findIssues(build, ids, JiraSite.DEFAULT_ISSUE_PATTERN, null);\n        assertEquals(0, ids.size());\n    }\n}\n"
  },
  {
    "path": "src/test/java/hudson/plugins/jira/selector/ExplicitIssueSelectorTest.java",
    "content": "package hudson.plugins.jira.selector;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.collection.IsCollectionWithSize.hasSize;\nimport static org.hamcrest.core.IsEqual.equalTo;\n\nimport java.util.Collections;\nimport java.util.Set;\nimport org.junit.jupiter.api.Test;\n\nclass ExplicitIssueSelectorTest {\n\n    private static final String TEST_KEY = \"EXAMPLE-1\";\n\n    @Test\n    void returnsExplicitCollections() {\n        ExplicitIssueSelector jqlUpdaterIssueSelector = new ExplicitIssueSelector(Collections.singletonList(TEST_KEY));\n        Set<String> foundIssueIds = jqlUpdaterIssueSelector.findIssueIds(null, null, null);\n        assertThat(foundIssueIds, hasSize(1));\n        assertThat(foundIssueIds.iterator().next(), equalTo(TEST_KEY));\n    }\n}\n"
  },
  {
    "path": "src/test/java/hudson/plugins/jira/selector/JqlIssueSelectorTest.java",
    "content": "package hudson.plugins.jira.selector;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.collection.IsCollectionWithSize.hasSize;\nimport static org.hamcrest.collection.IsEmptyCollection.empty;\nimport static org.hamcrest.core.IsEqual.equalTo;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nimport com.atlassian.jira.rest.client.api.domain.Issue;\nimport hudson.model.AbstractProject;\nimport hudson.model.Run;\nimport hudson.plugins.jira.JiraSession;\nimport hudson.plugins.jira.JiraSite;\nimport java.io.IOException;\nimport java.util.Collections;\nimport java.util.Set;\nimport java.util.concurrent.TimeoutException;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\n\n@ExtendWith(MockitoExtension.class)\nclass JqlIssueSelectorTest {\n\n    private static final String TEST_JQL = \"key='EXAMPLE-1'\";\n\n    @Mock\n    private JiraSite site;\n\n    @Mock\n    private JiraSession session;\n\n    @Mock\n    private AbstractProject project;\n\n    @Mock\n    private Run run;\n\n    @BeforeEach\n    void prepare() throws IOException {\n        when(run.getParent()).thenReturn(project);\n        when(site.getSession(project)).thenReturn(session);\n    }\n\n    @Test\n    void dontDependOnRunAndTaskListener() {\n        JqlIssueSelector jqlUpdaterIssueSelector = new JqlIssueSelector(TEST_JQL);\n        Set<String> foundIssues = jqlUpdaterIssueSelector.findIssueIds(run, site, null);\n        assertThat(foundIssues, empty());\n    }\n\n    @Test\n    void callGetIssuesFromJqlSearch() throws IOException, TimeoutException {\n        Issue issue = mock(Issue.class);\n        when(issue.getKey()).thenReturn(\"EXAMPLE-1\");\n        when(session.getIssuesFromJqlSearch(TEST_JQL)).thenReturn(Collections.singletonList(issue));\n\n        JqlIssueSelector jqlUpdaterIssueSelector = new JqlIssueSelector(TEST_JQL);\n        Set<String> foundIssueIds = jqlUpdaterIssueSelector.findIssueIds(run, site, null);\n        assertThat(foundIssueIds, hasSize(1));\n        assertThat(foundIssueIds.iterator().next(), equalTo(\"EXAMPLE-1\"));\n    }\n}\n"
  },
  {
    "path": "src/test/java/hudson/plugins/jira/selector/perforce/JobIssueSelectorTest.java",
    "content": "package hudson.plugins.jira.selector.perforce;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nimport hudson.model.BuildListener;\nimport hudson.model.FreeStyleBuild;\nimport hudson.model.ParameterValue;\nimport hudson.model.ParametersAction;\nimport hudson.plugins.jira.JiraCarryOverAction;\nimport hudson.plugins.jira.JiraSite;\nimport hudson.plugins.jira.listissuesparameter.JiraIssueParameterValue;\nimport hudson.scm.ChangeLogSet;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.TreeSet;\nimport org.junit.jupiter.api.Test;\n\npublic abstract class JobIssueSelectorTest {\n\n    protected abstract JobIssueSelector createJobIssueSelector();\n\n    @Test\n    public void findsIssuesWithJiraParameters() {\n        FreeStyleBuild build = mock(FreeStyleBuild.class);\n        ChangeLogSet changeLogSet = mock(ChangeLogSet.class);\n        BuildListener listener = mock(BuildListener.class);\n        JiraSite jiraSite = mock(JiraSite.class);\n        JiraIssueParameterValue parameter = mock(JiraIssueParameterValue.class);\n        JiraIssueParameterValue parameterTwo = mock(JiraIssueParameterValue.class);\n        ParametersAction action = mock(ParametersAction.class);\n        List<ParameterValue> parameters = new ArrayList<>();\n\n        when(listener.getLogger()).thenReturn(System.out);\n        when(changeLogSet.iterator()).thenReturn(Collections.EMPTY_LIST.iterator());\n        when(build.getChangeSet()).thenReturn(changeLogSet);\n        when(build.getAction(ParametersAction.class)).thenReturn(action);\n        when(action.getParameters()).thenReturn(parameters);\n        when(parameter.getValue()).thenReturn(\"JIRA-123\");\n        when(parameterTwo.getValue()).thenReturn(\"JIRA-321\");\n\n        Set<String> ids;\n\n        JobIssueSelector jobIssueSelector = createJobIssueSelector();\n        // Initial state contains zero parameters\n        ids = jobIssueSelector.findIssueIds(build, jiraSite, listener);\n        assertTrue(ids.isEmpty());\n\n        parameters.add(parameter);\n        ids = jobIssueSelector.findIssueIds(build, jiraSite, listener);\n        assertEquals(1, ids.size());\n        assertEquals(\"JIRA-123\", ids.iterator().next());\n\n        parameters.add(parameterTwo);\n        ids = jobIssueSelector.findIssueIds(build, jiraSite, listener);\n        assertEquals(2, ids.size());\n        Set<String> expected = new TreeSet(Arrays.asList(\"JIRA-123\", \"JIRA-321\"));\n        assertEquals(expected, ids);\n    }\n\n    @Test\n    public void findsCarriedOnIssues() {\n\n        FreeStyleBuild build = mock(FreeStyleBuild.class);\n        FreeStyleBuild previousBuild = mock(FreeStyleBuild.class);\n        ArrayList<String> issues = new ArrayList<>();\n        issues.add(\"GC-131\");\n        JiraCarryOverAction jiraCarryOverAction = mock(JiraCarryOverAction.class);\n        when(build.getPreviousCompletedBuild()).thenReturn(previousBuild);\n        when(previousBuild.getAction(JiraCarryOverAction.class)).thenReturn(jiraCarryOverAction);\n        when(jiraCarryOverAction.getIDs()).thenReturn(issues);\n\n        ChangeLogSet changeLogSet = mock(ChangeLogSet.class);\n        BuildListener listener = mock(BuildListener.class);\n        JiraSite jiraSite = mock(JiraSite.class);\n\n        when(listener.getLogger()).thenReturn(System.out);\n        when(changeLogSet.iterator()).thenReturn(Collections.EMPTY_LIST.iterator());\n        when(build.getChangeSet()).thenReturn(changeLogSet);\n\n        JobIssueSelector jobIssueSelector = createJobIssueSelector();\n\n        Set<String> ids = jobIssueSelector.findIssueIds(build, jiraSite, listener);\n        assertEquals(1, ids.size());\n        Set<String> expected = new TreeSet<>(Collections.singletonList(\"GC-131\"));\n        assertEquals(expected, ids);\n    }\n}\n"
  },
  {
    "path": "src/test/java/hudson/plugins/jira/selector/perforce/P4JobIssueSelectorTest.java",
    "content": "package hudson.plugins.jira.selector.perforce;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nimport com.perforce.p4java.impl.generic.core.Fix;\nimport hudson.model.BuildListener;\nimport hudson.model.FreeStyleBuild;\nimport hudson.plugins.jira.JiraSite;\nimport hudson.scm.ChangeLogSet;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\nimport org.jenkinsci.plugins.p4.changes.P4ChangeEntry;\nimport org.jenkinsci.plugins.p4.changes.P4ChangeSet;\nimport org.junit.jupiter.api.Test;\n\nclass P4JobIssueSelectorTest extends JobIssueSelectorTest {\n\n    @Test\n    void findsTwoP4Jobs() {\n        final String jobIdIW1231 = \"IW-1231\";\n        final String jobIdEC3453 = \"EC-3453\";\n\n        FreeStyleBuild build = mock(FreeStyleBuild.class);\n        BuildListener listener = mock(BuildListener.class);\n        ChangeLogSet changeLogSet = mock(ChangeLogSet.class);\n        P4ChangeSet perforceChangeLogSet = mock(P4ChangeSet.class);\n        Fix fixIW1231 = mock(Fix.class);\n        Fix fixEC3453 = mock(Fix.class);\n\n        when(fixIW1231.getJobId()).thenReturn(jobIdIW1231);\n        when(fixEC3453.getJobId()).thenReturn(jobIdEC3453);\n\n        when(listener.getLogger()).thenReturn(System.out);\n        when(build.getChangeSet()).thenReturn(changeLogSet);\n\n        ArrayList<P4ChangeEntry> entries = new ArrayList<>();\n\n        P4ChangeEntry entry1 = new P4ChangeEntry(perforceChangeLogSet);\n        entry1.getJobs().add(fixIW1231);\n        entries.add(entry1);\n\n        P4ChangeEntry entry2 = new P4ChangeEntry(perforceChangeLogSet);\n        entry2.getJobs().add(fixEC3453);\n        entries.add(entry2);\n        when(changeLogSet.iterator()).thenReturn(entries.iterator());\n\n        List<ChangeLogSet<? extends ChangeLogSet.Entry>> changeSets = new ArrayList<>();\n        changeSets.add(changeLogSet);\n        when(build.getChangeSets()).thenReturn(changeSets);\n\n        Set<String> expected = new HashSet<>(Arrays.asList(jobIdEC3453, jobIdIW1231));\n\n        P4JobIssueSelector selector = new P4JobIssueSelector();\n        JiraSite jiraSite = mock(JiraSite.class);\n        Set<String> result = selector.findIssueIds(build, jiraSite, listener);\n\n        assertEquals(expected.size(), result.size());\n        assertEquals(expected, result);\n    }\n\n    @Override\n    protected JobIssueSelector createJobIssueSelector() {\n        return new P4JobIssueSelector();\n    }\n}\n"
  },
  {
    "path": "src/test/java/hudson/plugins/jira/versionparameter/JiraReleaseVersionParameterTest.java",
    "content": "package hudson.plugins.jira.versionparameter;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.*;\n\nimport hudson.model.ParametersDefinitionProperty;\nimport org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition;\nimport org.jenkinsci.plugins.workflow.job.WorkflowJob;\nimport org.junit.jupiter.api.Test;\nimport org.jvnet.hudson.test.JenkinsRule;\nimport org.jvnet.hudson.test.junit.jupiter.WithJenkins;\n\n@WithJenkins\nclass JiraReleaseVersionParameterTest {\n\n    @Test\n    void scriptedPipeline(JenkinsRule r) throws Exception {\n        WorkflowJob p = r.createProject(WorkflowJob.class);\n        p.setDefinition(new CpsFlowDefinition(\"\"\"\n                properties([\n                  parameters([\n                    jiraReleaseVersion(name: 'JIRA', description: 'Jira Test Description', jiraProjectKey: 'PRJ', jiraReleasePattern: 'v[0-9]+', jiraShowReleased: 'true', jiraShowArchived: 'true')\n                  ])\n                ])\"\"\", true));\n        r.buildAndAssertSuccess(p);\n\n        ParametersDefinitionProperty parameters = p.getProperty(ParametersDefinitionProperty.class);\n        assertThat(parameters, is(notNullValue()));\n        assertThat(\n                parameters.getParameterDefinitions(),\n                hasItem(allOf(\n                        instanceOf(JiraVersionParameterDefinition.class),\n                        hasProperty(\"name\", is(\"JIRA\")),\n                        hasProperty(\"description\", is(\"Jira Test Description\")),\n                        hasProperty(\"jiraProjectKey\", is(\"PRJ\")))));\n    }\n}\n"
  },
  {
    "path": "src/test/java/hudson/plugins/jira/versionparameter/JiraVersionParameterDefinitionTest.java",
    "content": "package hudson.plugins.jira.versionparameter;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.hasSize;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.Mockito.*;\nimport static org.mockito.Mockito.mockStatic;\n\nimport com.atlassian.jira.rest.client.api.domain.Version;\nimport hudson.cli.CLICommand;\nimport hudson.model.Item;\nimport hudson.model.Job;\nimport hudson.model.ParameterValue;\nimport hudson.model.ParametersDefinitionProperty;\nimport hudson.plugins.jira.JiraSession;\nimport hudson.plugins.jira.JiraSite;\nimport hudson.plugins.jira.Messages;\nimport hudson.plugins.jira.extension.ExtendedVersion;\nimport hudson.util.ListBoxModel;\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.stream.Stream;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.junit.jupiter.params.provider.NullSource;\nimport org.kohsuke.stapler.Stapler;\nimport org.kohsuke.stapler.StaplerRequest2;\nimport org.mockito.Mock;\nimport org.mockito.MockedStatic;\nimport org.mockito.junit.jupiter.MockitoExtension;\n\nclass JiraVersionParameterDefinitionTest {\n\n    List<ExtendedVersion> versions = new ArrayList<>();\n    JiraVersionParameterDefinition.Result resReleasedVer;\n    JiraVersionParameterDefinition.Result resUnreleasedVer;\n    JiraVersionParameterDefinition.Result resArchivedVer;\n    JiraVersionParameterDefinition.Result resReleasedArchivedVer;\n    private Job<?, ?> job;\n    private JiraSite site;\n    private JiraSession session;\n    private StaplerRequest2 request2;\n    ExtendedVersion extReleasedVer = new ExtendedVersion(null, 1L, \"1.0\", \"\", false, true, null, null);\n    ExtendedVersion extUnReleasedVer = new ExtendedVersion(null, 2L, \"1.1\", \"\", false, false, null, null);\n    ExtendedVersion extArchivedVer = new ExtendedVersion(null, 3L, \"1.2\", \"\", true, false, null, null);\n    ExtendedVersion extReleasedArchivedVer = new ExtendedVersion(null, 4L, \"1.3\", \"\", true, true, null, null);\n\n    @BeforeEach\n    void createMocksAndVersions() {\n        versions.add(extReleasedVer);\n        versions.add(extUnReleasedVer);\n        versions.add(extArchivedVer);\n        versions.add(extReleasedArchivedVer);\n        resReleasedVer = new JiraVersionParameterDefinition.Result(extReleasedVer);\n        resUnreleasedVer = new JiraVersionParameterDefinition.Result(extUnReleasedVer);\n        resArchivedVer = new JiraVersionParameterDefinition.Result(extArchivedVer);\n        resReleasedArchivedVer = new JiraVersionParameterDefinition.Result(extReleasedArchivedVer);\n        job = mock(Job.class);\n        site = mock(JiraSite.class);\n        session = mock(JiraSession.class);\n        request2 = mock(StaplerRequest2.class);\n    }\n\n    @Test\n    void parameterValueMethodOverrides() throws Exception {\n        JiraVersionParameterDefinition definition =\n                new JiraVersionParameterDefinition(\"pname\", \"pdesc\", \"JIRAKEY\", null, \"false\", \"true\", \"true\");\n\n        assertEquals(\"JIRAKEY\", definition.getJiraProjectKey());\n        assertEquals(\"false\", definition.getJiraShowReleased());\n        assertEquals(\"true\", definition.getJiraShowArchived());\n        assertEquals(\"true\", definition.getJiraShowUnreleased());\n\n        assertEquals(\"pdesc\", definition.getDescription());\n\n        CLICommand cliCommand = mock(CLICommand.class);\n\n        ParameterValue value = definition.createValue(cliCommand, \"Jira Version 1.2.3\");\n        assertEquals(definition.getName(), value.getName());\n        assertEquals(\"Jira Version 1.2.3\", value.getValue());\n    }\n\n    static Stream<Arguments> createValueInvalidParameters() {\n        return Stream.of(\n                Arguments.of((Object) new String[] {}),\n                Arguments.of((Object) new String[] {\"a\", \"b\"}),\n                Arguments.of((Object) new String[] {\"\"}));\n    }\n\n    @ParameterizedTest\n    @NullSource\n    @MethodSource(\"createValueInvalidParameters\")\n    void shouldCreateNullParameterForInvalidValues(String[] values) {\n        JiraVersionParameterDefinition definition =\n                new JiraVersionParameterDefinition(\"pname\", \"pdesc\", \"JIRAKEY\", null, \"false\", \"true\", \"true\");\n\n        when(request2.getParameterValues(any())).thenReturn(values);\n\n        ParameterValue result = definition.createValue(request2);\n\n        assertNull(result);\n    }\n\n    @Test\n    void shouldCreateValue() {\n        JiraVersionParameterDefinition definition =\n                new JiraVersionParameterDefinition(\"pname\", \"pdesc\", \"JIRAKEY\", null, \"false\", \"true\", \"true\");\n\n        when(request2.getParameterValues(any())).thenReturn(new String[] {\"value\"});\n\n        ParameterValue result = definition.createValue(request2);\n\n        assertNotNull(result);\n        assertEquals(\"pname\", result.getName());\n        assertEquals(\"value\", result.getValue());\n    }\n\n    @Test\n    void showReleasedVersions() {\n        withJiraStaticMocks(() -> {\n            JiraVersionParameterDefinition def =\n                    new JiraVersionParameterDefinition(\"name\", \"desc\", \"PROJ\", null, \"true\", \"false\", \"false\");\n            List<JiraVersionParameterDefinition.Result> result;\n            try {\n                result = def.getVersions();\n            } catch (IOException e) {\n                throw new RuntimeException(e);\n            }\n            List<JiraVersionParameterDefinition.Result> expected = new ArrayList<>(List.of(resReleasedVer));\n            assertEquals(expected, result);\n        });\n    }\n\n    @Test\n    void showUnreleasedVersions() {\n        withJiraStaticMocks(() -> {\n            JiraVersionParameterDefinition def =\n                    new JiraVersionParameterDefinition(\"name\", \"desc\", \"PROJ\", null, \"false\", \"false\", \"true\");\n            List<JiraVersionParameterDefinition.Result> result;\n            try {\n                result = def.getVersions();\n            } catch (IOException e) {\n                throw new RuntimeException(e);\n            }\n            List<JiraVersionParameterDefinition.Result> expected = new ArrayList<>(List.of(resUnreleasedVer));\n            assertEquals(expected, result);\n        });\n    }\n\n    @Test\n    void showArchivedVersions() {\n        withJiraStaticMocks(() -> {\n            JiraVersionParameterDefinition def =\n                    new JiraVersionParameterDefinition(\"name\", \"desc\", \"PROJ\", null, \"false\", \"true\", \"false\");\n            List<JiraVersionParameterDefinition.Result> result;\n            try {\n                result = def.getVersions();\n            } catch (IOException e) {\n                throw new RuntimeException(e);\n            }\n            List<JiraVersionParameterDefinition.Result> expected =\n                    new ArrayList<>(List.of(resReleasedArchivedVer, resArchivedVer));\n            assertEquals(expected, result);\n        });\n    }\n\n    @Test\n    void showAllVersions() {\n        withJiraStaticMocks(() -> {\n            JiraVersionParameterDefinition def =\n                    new JiraVersionParameterDefinition(\"name\", \"desc\", \"PROJ\", null, \"false\", \"false\", \"false\");\n            List<JiraVersionParameterDefinition.Result> result;\n            try {\n                result = def.getVersions();\n            } catch (IOException e) {\n                throw new RuntimeException(e);\n            }\n            List<JiraVersionParameterDefinition.Result> expected =\n                    new ArrayList<>(List.of(resReleasedArchivedVer, resArchivedVer, resUnreleasedVer, resReleasedVer));\n            assertEquals(expected, result);\n        });\n    }\n\n    @Test\n    void showReleasedUnreleasedVersions() {\n        withJiraStaticMocks(() -> {\n            JiraVersionParameterDefinition def =\n                    new JiraVersionParameterDefinition(\"name\", \"desc\", \"PROJ\", null, \"true\", \"false\", \"true\");\n            List<JiraVersionParameterDefinition.Result> result;\n            try {\n                result = def.getVersions();\n            } catch (IOException e) {\n                throw new RuntimeException(e);\n            }\n            List<JiraVersionParameterDefinition.Result> expected =\n                    new ArrayList<>(List.of(resUnreleasedVer, resReleasedVer));\n            assertEquals(expected, result);\n        });\n    }\n\n    @Test\n    void showReleasedArchivedVersions() {\n        withJiraStaticMocks(() -> {\n            JiraVersionParameterDefinition def =\n                    new JiraVersionParameterDefinition(\"name\", \"desc\", \"PROJ\", null, \"true\", \"true\", \"false\");\n            List<JiraVersionParameterDefinition.Result> result;\n            try {\n                result = def.getVersions();\n            } catch (IOException e) {\n                throw new RuntimeException(e);\n            }\n            List<JiraVersionParameterDefinition.Result> expected =\n                    new ArrayList<>(List.of(resReleasedArchivedVer, resArchivedVer, resReleasedVer));\n            assertEquals(expected, result);\n        });\n    }\n\n    @Test\n    void showUnreleasedArchivedVersions() {\n        withJiraStaticMocks(() -> {\n            JiraVersionParameterDefinition def =\n                    new JiraVersionParameterDefinition(\"name\", \"desc\", \"PROJ\", null, \"false\", \"true\", \"true\");\n            List<JiraVersionParameterDefinition.Result> result;\n            try {\n                result = def.getVersions();\n            } catch (IOException e) {\n                throw new RuntimeException(e);\n            }\n            List<JiraVersionParameterDefinition.Result> expected =\n                    new ArrayList<>(List.of(resReleasedArchivedVer, resArchivedVer, resUnreleasedVer));\n            assertEquals(expected, result);\n        });\n    }\n\n    @Test\n    void equalResults() {\n        JiraVersionParameterDefinition.Result res1 = new JiraVersionParameterDefinition.Result(extReleasedVer);\n        JiraVersionParameterDefinition.Result res2 = new JiraVersionParameterDefinition.Result(extReleasedVer);\n        assertTrue(res1.equals(res2));\n    }\n\n    @Test\n    void diffResults() {\n        JiraVersionParameterDefinition.Result res1 = new JiraVersionParameterDefinition.Result(extReleasedVer);\n        JiraVersionParameterDefinition.Result res2 = new JiraVersionParameterDefinition.Result(extUnReleasedVer);\n        assertFalse(res1.equals(res2));\n    }\n\n    @Test\n    void nullResultCompare() {\n        JiraVersionParameterDefinition.Result res1 = new JiraVersionParameterDefinition.Result(extReleasedVer);\n        JiraVersionParameterDefinition.Result res2 = null;\n        assertFalse(res1.equals(res2));\n    }\n\n    @Test\n    void diffClassResultCompare() {\n        JiraVersionParameterDefinition.Result res1 = new JiraVersionParameterDefinition.Result(extReleasedVer);\n        assertFalse(res1.equals(extReleasedVer));\n    }\n\n    @Test\n    void sameNameDiffIdResultCompare() {\n        JiraVersionParameterDefinition.Result res1 = new JiraVersionParameterDefinition.Result(extReleasedVer);\n        ExtendedVersion version = new ExtendedVersion(null, 2L, \"1.0\", \"\", false, true, null, null);\n        JiraVersionParameterDefinition.Result res2 = new JiraVersionParameterDefinition.Result(version);\n        assertFalse(res1.equals(res2));\n    }\n\n    @Test\n    void compareResultEqHashCode() {\n        JiraVersionParameterDefinition.Result res1 = new JiraVersionParameterDefinition.Result(extReleasedVer);\n        JiraVersionParameterDefinition.Result res2 = new JiraVersionParameterDefinition.Result(extReleasedVer);\n        assertEquals(res1.hashCode(), res2.hashCode());\n    }\n\n    @Test\n    void compareResultNotEqHashCode() {\n        JiraVersionParameterDefinition.Result res1 = new JiraVersionParameterDefinition.Result(extReleasedVer);\n        JiraVersionParameterDefinition.Result res2 = new JiraVersionParameterDefinition.Result(extArchivedVer);\n        assertNotEquals(res1.hashCode(), res2.hashCode());\n    }\n\n    @Test\n    void getJiraShowUnreleasedOn() {\n        JiraVersionParameterDefinition def =\n                new JiraVersionParameterDefinition(\"name\", \"desc\", \"PROJ\", null, \"false\", \"true\", \"true\");\n        assertEquals(\"true\", def.getJiraShowUnreleased());\n    }\n\n    private void withJiraStaticMocks(Runnable testLogic) {\n        try (MockedStatic<Stapler> staplerMock = mockStatic(Stapler.class);\n                MockedStatic<JiraSite> siteMock = mockStatic(JiraSite.class)) {\n            staplerMock.when(Stapler::getCurrentRequest2).thenReturn(request2);\n            siteMock.when(() -> JiraSite.get(job)).thenReturn(site);\n\n            when(request2.findAncestorObject(Job.class)).thenReturn(job);\n            when(site.getSession(job)).thenReturn(session);\n            when(session.getVersions(\"PROJ\")).thenReturn(versions);\n\n            testLogic.run();\n        } catch (Exception e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    @Nested\n    @ExtendWith(MockitoExtension.class)\n    class DescriptorImplTest {\n\n        private JiraVersionParameterDefinition.DescriptorImpl uut = new JiraVersionParameterDefinition.DescriptorImpl();\n\n        @Test\n        void shouldFillVersionItems(\n                @Mock Job<?, ?> job,\n                @Mock ParametersDefinitionProperty propertyDef,\n                @Mock JiraVersionParameterDefinition paramDef) {\n            when(job.hasPermission(Item.BUILD)).thenReturn(true);\n            when(job.getProperty(ParametersDefinitionProperty.class)).thenReturn(propertyDef);\n            when(propertyDef.getParameterDefinition(\"PARAM_NAME\")).thenReturn(paramDef);\n            Version version = new Version(null, 1L, \"1.0.0\", \"\", false, false, null);\n            JiraVersionParameterDefinition.Result item = new JiraVersionParameterDefinition.Result(version);\n            when(paramDef.getVersions(any())).thenReturn(List.of(item));\n\n            ListBoxModel result = uut.doFillVersionItems(job, \"PARAM_NAME\");\n\n            assertThat(result, hasSize(1));\n            ListBoxModel.Option option = result.get(0);\n            assertEquals(\"1.0.0\", option.value);\n            assertEquals(\"1.0.0\", option.name);\n            verify(job).hasPermission(Item.BUILD);\n        }\n\n        @Test\n        void shouldNotFillVersionsItemsIfPermissionMissing(@Mock Job<?, ?> job) {\n            ListBoxModel result = uut.doFillVersionItems(job, \"PARAM_NAME\");\n\n            assertThat(result, hasSize(1));\n            ListBoxModel.Option option = result.get(0);\n            assertEquals(\"\", option.value);\n            assertEquals(Messages.JiraVersionParameterDefinition_NoVersionsMatchedSearch(), option.name);\n            verify(job).hasPermission(Item.BUILD);\n        }\n\n        @Test\n        void shouldHaveNoSearchMatchesItemIfSearchMatchesNoItem(\n                @Mock Job<?, ?> job,\n                @Mock ParametersDefinitionProperty propertyDef,\n                @Mock JiraVersionParameterDefinition paramDef) {\n            when(job.hasPermission(Item.BUILD)).thenReturn(true);\n            when(job.getProperty(ParametersDefinitionProperty.class)).thenReturn(propertyDef);\n            when(propertyDef.getParameterDefinition(\"PARAM_NAME\")).thenReturn(paramDef);\n\n            ListBoxModel result = uut.doFillVersionItems(job, \"PARAM_NAME\");\n\n            assertThat(result, hasSize(1));\n            ListBoxModel.Option option = result.get(0);\n            assertEquals(\"\", option.value);\n            assertEquals(Messages.JiraVersionParameterDefinition_NoVersionsMatchedSearch(), option.name);\n            verify(job).hasPermission(Item.BUILD);\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/hudson/plugins/jira/versionparameter/VersionComparatorTest.java",
    "content": "package hudson.plugins.jira.versionparameter;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport com.atlassian.jira.rest.client.api.domain.Version;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.stream.Collectors;\nimport org.hamcrest.collection.ArrayMatching;\nimport org.junit.jupiter.api.Test;\n\nclass VersionComparatorTest {\n\n    @Test\n    void complexCompare() {\n        String[] input = {\n            \"9.9.9.9.9\",\n            \"V-5.2.3\",\n            \"PDFREPORT-2.3.4\",\n            \"PDFREPORT-2.3\",\n            \"1.12.2.3.4\",\n            \"1.3.4\",\n            \"PDFREPORT-\",\n            \"1.1.1.2\",\n            \"VER 1.0\",\n            \"1.1.1.1\",\n            \"FOO-1.1.1-RC1\",\n            \"FOO-1.1.1-RC2\",\n            \"1.1.1-RC1\",\n            \"1.1.1-RC2\",\n        };\n\n        String[] expected = {\n            \"9.9.9.9.9\",\n            \"1.12.2.3.4\",\n            \"1.3.4\",\n            \"1.1.1.2\",\n            \"1.1.1.1\",\n            \"1.1.1-RC2\",\n            \"1.1.1-RC1\",\n            \"VER 1.0\",\n            \"V-5.2.3\",\n            \"PDFREPORT-2.3.4\",\n            \"PDFREPORT-2.3\",\n            \"PDFREPORT-\",\n            \"FOO-1.1.1-RC2\",\n            \"FOO-1.1.1-RC1\",\n        };\n\n        List<String> result = Arrays.asList(input).stream()\n                .map(s -> new Version(null, null, s, null, false, false, null))\n                .sorted(VersionComparator.INSTANCE)\n                .map(Version::getName)\n                .collect(Collectors.toList());\n        assertThat(expected, ArrayMatching.arrayContaining(result.toArray(new String[result.size()])));\n    }\n\n    @Test\n    void singleComparisonsTests() {\n\n        assertEquals(0, compare(\"1.1.1.1\", \"1.1.1.1\"));\n        assertEquals(1, compare(\"A-1.1.1.1\", \"1.1.1.1\"));\n        assertEquals(-1, compare(\"1.1.1.1\", \"A-1.1.1.1\"));\n        assertEquals(1, compare(\"1.1.1.1\", \"1.1.1.1.1\"));\n        assertEquals(-1, compare(\"1.1.1.1\", \"1.1.1\"));\n        assertEquals(1, compare(\"1.1.1.2\", \"1.1.1.3\"));\n        assertEquals(-1, compare(\"2.2.2.1\", \"1.1.1.2\"));\n        assertEquals(-1, compare(\"2.2.2\", \"1.1.1.2\"));\n        assertEquals(-1, compare(\"2.0\", \"1.0.15.3\"));\n        assertEquals(1, compare(\"2.0.5.4\", \"4.0\"));\n        assertEquals(-1, compare(\"1.12.1.1\", \"1.1.1.2\"));\n        assertEquals(1, compare(\"1.1.1-RC1\", \"1.1.1-RC2\"));\n        assertEquals(1, compare(\"PDFREPORT-2.3.4\", \"1.2.3\"));\n        assertEquals(1, compare(\"PDFREPORT-2.3.4\", \"4.5.6\"));\n        assertEquals(1, compare(\"PDFREPORT-2.3.4\", \"x\"));\n        assertEquals(0, compare(\"PDFREPORT2-\", \"PDFREPORT2-\"));\n        assertEquals(1, compare(\"PDFREPORT-\", \"PDFREPORT2-\"));\n        assertEquals(-1, compare(\"1.1.2-RC1\", \"1.1.1-RC2\"));\n        assertEquals(-1, compare(\"2.2.2-RC1\", \"1.1.1-RC1\"));\n        assertEquals(1, compare(\"FOO-1.1.1-RC1\", \"FOO-1.1.1-RC2\"));\n    }\n\n    private int compare(String v1, String v2) {\n        return VersionComparator.INSTANCE.compare(\n                new Version(null, null, v1, null, false, false, null),\n                new Version(null, null, v2, null, false, false, null));\n    }\n}\n"
  },
  {
    "path": "src/test/resources/hudson/plugins/jira/JiraBuildActionTest/binaryCompatibility/config.xml",
    "content": "<?xml version='1.0' encoding='UTF-8'?>\n<hudson>\n  <version>2.222.1</version>\n  <systemMessage>Jenkins JiraBuildActionTest config</systemMessage>\n</hudson>"
  },
  {
    "path": "src/test/resources/hudson/plugins/jira/JiraBuildActionTest/binaryCompatibility/jobs/project/builds/2/build.xml",
    "content": "<?xml version='1.1' encoding='UTF-8'?>\n<build>\n  <queueId>39</queueId>\n  <timestamp>1585749717875</timestamp>\n  <startTime>1585749717879</startTime>\n  <result>SUCCESS</result>\n  <duration>240</duration>\n  <charset>UTF-8</charset>\n  <keepLog>false</keepLog>\n  <builtOn></builtOn>\n  <workspace>/var/jenkins_home/jobs/global/workspace</workspace>\n  <hudsonVersion>2.222.1</hudsonVersion>\n  <scm class=\"hudson.scm.NullChangeLogParser\"/>\n\n  <actions>\n    <hudson.model.CauseAction>\n      <causeBag class=\"linked-hash-map\">\n        <entry>\n          <hudson.model.Cause_-UserIdCause>\n            <userId>admin</userId>\n          </hudson.model.Cause_-UserIdCause>\n          <int>2</int>\n        </entry>\n      </causeBag>\n    </hudson.model.CauseAction>\n\n    <hudson.plugins.jira.JiraBuildAction>\n      <owner class=\"build\" reference=\"../../..\"/>\n      <issues>\n        <hudson.plugins.jira.model.JiraIssue>\n          <id>JIRA-123</id>\n          <title>Issue summary</title>\n        </hudson.plugins.jira.model.JiraIssue>\n      </issues>\n    </hudson.plugins.jira.JiraBuildAction>\n\n  </actions>\n</build>"
  },
  {
    "path": "src/test/resources/hudson/plugins/jira/JiraBuildActionTest/binaryCompatibility/jobs/project/config.xml",
    "content": "<?xml version='1.1' encoding='UTF-8'?>\n<project>\n  <actions/>\n  <description></description>\n  <keepDependencies>false</keepDependencies>\n  <properties>\n    <hudson.plugins.jira.JiraProjectProperty plugin=\"jira@3.0.16-SNAPSHOT\">\n      <siteName>https://jira.atlassian.net/</siteName>\n    </hudson.plugins.jira.JiraProjectProperty>\n  </properties>\n  <scm class=\"hudson.scm.NullSCM\"/>\n  <canRoam>true</canRoam>\n  <disabled>false</disabled>\n  <blockBuildWhenDownstreamBuilding>false</blockBuildWhenDownstreamBuilding>\n  <blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding>\n  <triggers/>\n  <concurrentBuild>false</concurrentBuild>\n  <builders/>\n  <publishers>\n    <hudson.plugins.jira.JiraIssueUpdater plugin=\"jira@3.0.16-SNAPSHOT\">\n      <issueSelector class=\"hudson.plugins.jira.selector.ExplicitIssueSelector\">\n        <jiraIssueKeys class=\"java.util.Arrays$ArrayList\">\n          <a class=\"string-array\">\n            <string>TEST-1</string>\n          </a>\n        </jiraIssueKeys>\n        <issueKeys>TEST-1</issueKeys>\n      </issueSelector>\n      <labels/>\n    </hudson.plugins.jira.JiraIssueUpdater>\n  </publishers>\n  <buildWrappers/>\n</project>"
  },
  {
    "path": "src/test/resources/hudson/plugins/jira/multiple-sites.yml",
    "content": "unclassified:\n  jiraglobalconfiguration:\n    sites:\n    - url: \"https://issues.jenkins-ci.org/\"\n    - url: \"https://jira.com/\"\n"
  },
  {
    "path": "src/test/resources/hudson/plugins/jira/oldJiraProjectProperty.xml",
    "content": "<hudson.plugins.jira.JiraProjectProperty_-DescriptorImpl>\n  <sites>\n    <hudson.plugins.jira.JiraSite>\n      <url>https://backwardsCompatURL.com/</url>\n      <useHTTPAuth>false</useHTTPAuth>\n      <supportsWikiStyleComment>false</supportsWikiStyleComment>\n      <recordScmChanges>false</recordScmChanges>\n      <disableChangelogAnnotations>false</disableChangelogAnnotations>\n      <updateJiraIssueForAllStatus>false</updateJiraIssueForAllStatus>\n      <timeout>10</timeout>\n      <readTimeout>30</readTimeout>\n      <threadExecutorNumber>10</threadExecutorNumber>\n      <dateTimePattern></dateTimePattern>\n      <appendChangeTimestamp>false</appendChangeTimestamp>\n    </hudson.plugins.jira.JiraSite>\n  </sites>\n</hudson.plugins.jira.JiraProjectProperty_-DescriptorImpl>\n"
  },
  {
    "path": "src/test/resources/hudson/plugins/jira/single-site.yml",
    "content": "unclassified:\n  jiraglobalconfiguration:\n    sites:\n    - url: \"https://jira.com/\"\n"
  },
  {
    "path": "src/test/resources/jira.properties",
    "content": "url=http://host/jira/rpc/soap/jirasoapservice-v2\nusername=user\npassword=passwd\ntoken=token"
  }
]