From 3f07048e3d294f56e9b90ac5ea2c6f74e9ad0f98 Mon Sep 17 00:00:00 2001
From: Dmitry Shibanov <dmitry-shibanov@github.com>
Date: Tue, 7 Feb 2023 16:29:21 +0100
Subject: [PATCH] Revert "Revert "Add support for Oracle JDK (#401)" (#421)"
 (#450)

This reverts commit c3ac5dd0ed8db40fedb61c32fbe677e6b355e94c.
---
 .github/workflows/e2e-versions.yml            |  14 ++
 README.md                                     |   1 +
 .../distributors/oracle-installer.test.ts     |  97 ++++++++++++++
 __tests__/verify-java.sh                      |   2 +-
 dist/setup/index.js                           | 123 ++++++++++++++++++
 docs/advanced-usage.md                        |  14 ++
 src/distributions/distribution-factory.ts     |   6 +-
 src/distributions/oracle/installer.ts         | 103 +++++++++++++++
 src/distributions/oracle/models.ts            |   1 +
 9 files changed, 359 insertions(+), 2 deletions(-)
 create mode 100644 __tests__/distributors/oracle-installer.test.ts
 create mode 100644 src/distributions/oracle/installer.ts
 create mode 100644 src/distributions/oracle/models.ts

diff --git a/.github/workflows/e2e-versions.yml b/.github/workflows/e2e-versions.yml
index 47250ccc..f7b301e5 100644
--- a/.github/workflows/e2e-versions.yml
+++ b/.github/workflows/e2e-versions.yml
@@ -26,6 +26,16 @@ jobs:
         exclude:
         - distribution: microsoft
           version: 8
+        include:
+        - distribution: oracle
+          os: macos-latest
+          version: 17
+        - distribution: oracle
+          os: windows-latest
+          version: 19
+        - distribution: oracle
+          os: ubuntu-latest
+          version: 19
     steps:
       - name: Checkout
         uses: actions/checkout@v3
@@ -52,6 +62,10 @@ jobs:
         - '11.0'
         - '8.0.302'
         - '16.0.2+7'
+        include:
+        - distribution: oracle
+          os: ubuntu-latest
+          version: '19.0.1'
     steps:
       - name: Checkout
         uses: actions/checkout@v3
diff --git a/README.md b/README.md
index 709fdff5..de1db16e 100644
--- a/README.md
+++ b/README.md
@@ -103,6 +103,7 @@ Currently, the following distributions are supported:
 | `liberica` | Liberica JDK | [Link](https://bell-sw.com/) | [Link](https://bell-sw.com/liberica_eula/) |
 | `microsoft` | Microsoft Build of OpenJDK | [Link](https://www.microsoft.com/openjdk) | [Link](https://docs.microsoft.com/java/openjdk/faq)
 | `corretto` | Amazon Corretto Build of OpenJDK | [Link](https://aws.amazon.com/corretto/) | [Link](https://aws.amazon.com/corretto/faqs/)
+| `oracle` | Oracle JDK | [Link](https://www.oracle.com/java/technologies/downloads/) | [Link](https://java.com/freeuselicense)
 
 **NOTE:** The different distributors can provide discrepant list of available versions / supported configurations. Please refer to the official documentation to see the list of supported versions.
 
diff --git a/__tests__/distributors/oracle-installer.test.ts b/__tests__/distributors/oracle-installer.test.ts
new file mode 100644
index 00000000..86cba920
--- /dev/null
+++ b/__tests__/distributors/oracle-installer.test.ts
@@ -0,0 +1,97 @@
+import { OracleDistribution } from '../../src/distributions/oracle/installer';
+import os from 'os';
+import * as core from '@actions/core';
+import { getDownloadArchiveExtension } from '../../src/util';
+
+describe('findPackageForDownload', () => {
+  let distribution: OracleDistribution;
+  let spyDebug: jest.SpyInstance;
+
+  beforeEach(() => {
+    distribution = new OracleDistribution({
+      version: '',
+      architecture: 'x64',
+      packageType: 'jdk',
+      checkLatest: false
+    });
+
+    spyDebug = jest.spyOn(core, 'debug');
+    spyDebug.mockImplementation(() => {});
+  });
+
+  it.each([
+    [
+      '19',
+      '19',
+      'https://download.oracle.com/java/19/latest/jdk-19_{{OS_TYPE}}-x64_bin.{{ARCHIVE_TYPE}}'
+    ],
+    [
+      '19.0.1',
+      '19.0.1',
+      'https://download.oracle.com/java/19/archive/jdk-19.0.1_{{OS_TYPE}}-x64_bin.{{ARCHIVE_TYPE}}'
+    ],
+    [
+      '18.0.2.1',
+      '18.0.2.1',
+      'https://download.oracle.com/java/18/archive/jdk-18.0.2.1_{{OS_TYPE}}-x64_bin.{{ARCHIVE_TYPE}}'
+    ],
+    [
+      '17',
+      '17',
+      'https://download.oracle.com/java/17/latest/jdk-17_{{OS_TYPE}}-x64_bin.{{ARCHIVE_TYPE}}'
+    ],
+    [
+      '17.0.1',
+      '17.0.1',
+      'https://download.oracle.com/java/17/archive/jdk-17.0.1_{{OS_TYPE}}-x64_bin.{{ARCHIVE_TYPE}}'
+    ]
+  ])('version is %s -> %s', async (input, expectedVersion, expectedUrl) => {
+    const result = await distribution['findPackageForDownload'](input);
+    expect(result.version).toBe(expectedVersion);
+    const osType = distribution.getPlatform();
+    const archiveType = getDownloadArchiveExtension();
+    const url = expectedUrl.replace('{{OS_TYPE}}', osType).replace('{{ARCHIVE_TYPE}}', archiveType);
+    expect(result.url).toBe(url);
+  });
+
+  it.each([
+    ['amd64', 'x64'],
+    ['arm64', 'aarch64']
+  ])(
+    'defaults to os.arch(): %s mapped to distro arch: %s',
+    async (osArch: string, distroArch: string) => {
+      jest.spyOn(os, 'arch').mockReturnValue(osArch);
+      jest.spyOn(os, 'platform').mockReturnValue('linux');
+
+      const version = '17';
+      const distro = new OracleDistribution({
+        version,
+        architecture: '', // to get default value
+        packageType: 'jdk',
+        checkLatest: false
+      });
+
+      const osType = distribution.getPlatform();
+      if (osType === 'windows' && distroArch == 'aarch64') {
+        return; // skip, aarch64 is not available for Windows
+      }
+      const archiveType = getDownloadArchiveExtension();
+      const result = await distro['findPackageForDownload'](version);
+      const expectedUrl = `https://download.oracle.com/java/17/latest/jdk-17_${osType}-${distroArch}_bin.${archiveType}`;
+
+      expect(result.url).toBe(expectedUrl);
+    }
+  );
+
+  it('should throw an error', async () => {
+    await expect(distribution['findPackageForDownload']('8')).rejects.toThrow(
+      /Oracle JDK is only supported for JDK 17 and later/
+    );
+    await expect(distribution['findPackageForDownload']('11')).rejects.toThrow(
+      /Oracle JDK is only supported for JDK 17 and later/
+    );
+    await expect(distribution['findPackageForDownload']('18')).rejects.toThrow(
+      /Could not find Oracle JDK for SemVer */
+    );
+  });
+});
diff --git a/__tests__/verify-java.sh b/__tests__/verify-java.sh
index 1bad5d35..069d9008 100755
--- a/__tests__/verify-java.sh
+++ b/__tests__/verify-java.sh
@@ -24,7 +24,7 @@ fi
 ACTUAL_JAVA_VERSION="$(java -version 2>&1)"
 echo "Found java version: $ACTUAL_JAVA_VERSION"
 
-GREP_RESULT=$(echo $ACTUAL_JAVA_VERSION | grep "^openjdk version \"$EXPECTED_JAVA_VERSION")
+GREP_RESULT=$(echo $ACTUAL_JAVA_VERSION | grep -E "^(openjdk|java) version \"$EXPECTED_JAVA_VERSION")
 if [ -z "$GREP_RESULT" ]; then
   echo "::error::Unexpected version"
   echo "Expected version: $EXPECTED_JAVA_VERSION"
diff --git a/dist/setup/index.js b/dist/setup/index.js
index 3b8eb745..20ec26a7 100644
--- a/dist/setup/index.js
+++ b/dist/setup/index.js
@@ -104159,6 +104159,7 @@ const installer_4 = __nccwpck_require__(8579);
 const installer_5 = __nccwpck_require__(883);
 const installer_6 = __nccwpck_require__(3613);
 const installer_7 = __nccwpck_require__(4750);
+const installer_8 = __nccwpck_require__(4298);
 var JavaDistribution;
 (function (JavaDistribution) {
     JavaDistribution["Adopt"] = "adopt";
@@ -104170,6 +104171,7 @@ var JavaDistribution;
     JavaDistribution["JdkFile"] = "jdkfile";
     JavaDistribution["Microsoft"] = "microsoft";
     JavaDistribution["Corretto"] = "corretto";
+    JavaDistribution["Oracle"] = "oracle";
 })(JavaDistribution || (JavaDistribution = {}));
 function getJavaDistribution(distributionName, installerOptions, jdkFile) {
     switch (distributionName) {
@@ -104190,6 +104192,8 @@ function getJavaDistribution(distributionName, installerOptions, jdkFile) {
             return new installer_6.MicrosoftDistributions(installerOptions);
         case JavaDistribution.Corretto:
             return new installer_7.CorrettoDistribution(installerOptions);
+        case JavaDistribution.Oracle:
+            return new installer_8.OracleDistribution(installerOptions);
         default:
             return null;
     }
@@ -104597,6 +104601,125 @@ class MicrosoftDistributions extends base_installer_1.JavaBase {
 exports.MicrosoftDistributions = MicrosoftDistributions;
 
 
+/***/ }),
+
+/***/ 4298:
+/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
+
+"use strict";
+
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
+}) : (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    o[k2] = m[k];
+}));
+var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
+    Object.defineProperty(o, "default", { enumerable: true, value: v });
+}) : function(o, v) {
+    o["default"] = v;
+});
+var __importStar = (this && this.__importStar) || function (mod) {
+    if (mod && mod.__esModule) return mod;
+    var result = {};
+    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
+    __setModuleDefault(result, mod);
+    return result;
+};
+var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
+    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
+    return new (P || (P = Promise))(function (resolve, reject) {
+        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
+        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
+        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
+        step((generator = generator.apply(thisArg, _arguments || [])).next());
+    });
+};
+var __importDefault = (this && this.__importDefault) || function (mod) {
+    return (mod && mod.__esModule) ? mod : { "default": mod };
+};
+Object.defineProperty(exports, "__esModule", ({ value: true }));
+exports.OracleDistribution = void 0;
+const core = __importStar(__nccwpck_require__(2186));
+const tc = __importStar(__nccwpck_require__(7784));
+const fs_1 = __importDefault(__nccwpck_require__(7147));
+const path_1 = __importDefault(__nccwpck_require__(1017));
+const base_installer_1 = __nccwpck_require__(9741);
+const util_1 = __nccwpck_require__(2629);
+const http_client_1 = __nccwpck_require__(9925);
+const ORACLE_DL_BASE = 'https://download.oracle.com/java';
+class OracleDistribution extends base_installer_1.JavaBase {
+    constructor(installerOptions) {
+        super('Oracle', installerOptions);
+    }
+    downloadTool(javaRelease) {
+        return __awaiter(this, void 0, void 0, function* () {
+            core.info(`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`);
+            const javaArchivePath = yield tc.downloadTool(javaRelease.url);
+            core.info(`Extracting Java archive...`);
+            let extension = util_1.getDownloadArchiveExtension();
+            let extractedJavaPath = yield util_1.extractJdkFile(javaArchivePath, extension);
+            const archiveName = fs_1.default.readdirSync(extractedJavaPath)[0];
+            const archivePath = path_1.default.join(extractedJavaPath, archiveName);
+            const version = this.getToolcacheVersionName(javaRelease.version);
+            let javaPath = yield tc.cacheDir(archivePath, this.toolcacheFolderName, version, this.architecture);
+            return { version: javaRelease.version, path: javaPath };
+        });
+    }
+    findPackageForDownload(range) {
+        return __awaiter(this, void 0, void 0, function* () {
+            const arch = this.distributionArchitecture();
+            if (arch !== 'x64' && arch !== 'aarch64') {
+                throw new Error(`Unsupported architecture: ${this.architecture}`);
+            }
+            if (!this.stable) {
+                throw new Error('Early access versions are not supported');
+            }
+            if (this.packageType !== 'jdk') {
+                throw new Error('Oracle JDK provides only the `jdk` package type');
+            }
+            const platform = this.getPlatform();
+            const extension = util_1.getDownloadArchiveExtension();
+            let major;
+            let fileUrl;
+            if (range.includes('.')) {
+                major = range.split('.')[0];
+                fileUrl = `${ORACLE_DL_BASE}/${major}/archive/jdk-${range}_${platform}-${arch}_bin.${extension}`;
+            }
+            else {
+                major = range;
+                fileUrl = `${ORACLE_DL_BASE}/${range}/latest/jdk-${range}_${platform}-${arch}_bin.${extension}`;
+            }
+            if (parseInt(major) < 17) {
+                throw new Error('Oracle JDK is only supported for JDK 17 and later');
+            }
+            const response = yield this.http.head(fileUrl);
+            if (response.message.statusCode === http_client_1.HttpCodes.NotFound) {
+                throw new Error(`Could not find Oracle JDK for SemVer ${range}`);
+            }
+            if (response.message.statusCode !== http_client_1.HttpCodes.OK) {
+                throw new Error(`Http request for Oracle JDK failed with status code: ${response.message.statusCode}`);
+            }
+            return { url: fileUrl, version: range };
+        });
+    }
+    getPlatform(platform = process.platform) {
+        switch (platform) {
+            case 'darwin':
+                return 'macos';
+            case 'win32':
+                return 'windows';
+            case 'linux':
+                return 'linux';
+            default:
+                throw new Error(`Platform '${platform}' is not supported. Supported platforms: 'linux', 'macos', 'windows'`);
+        }
+    }
+}
+exports.OracleDistribution = OracleDistribution;
+
+
 /***/ }),
 
 /***/ 8579:
diff --git a/docs/advanced-usage.md b/docs/advanced-usage.md
index f5463bb7..481fc53e 100644
--- a/docs/advanced-usage.md
+++ b/docs/advanced-usage.md
@@ -6,6 +6,7 @@
   - [Liberica](#Liberica)
   - [Microsoft](#Microsoft)
   - [Amazon Corretto](#Amazon-Corretto)
+  - [Oracle](#Oracle)
 - [Installing custom Java package type](#Installing-custom-Java-package-type)
 - [Installing custom Java architecture](#Installing-custom-Java-architecture)
 - [Installing custom Java distribution from local file](#Installing-Java-from-local-file)
@@ -110,6 +111,19 @@ steps:
 - run: java -cp java HelloWorldApp
 ```
 
+### Oracle
+**NOTE:** Oracle Java SE Development Kit is only available for version 17 and later.
+
+```yaml
+steps:
+- uses: actions/checkout@v3
+- uses: actions/setup-java@v3
+  with:
+    distribution: 'oracle'
+    java-version: '17'
+- run: java -cp java HelloWorldApp
+```
+
 ## Installing custom Java package type
 ```yaml
 steps:
diff --git a/src/distributions/distribution-factory.ts b/src/distributions/distribution-factory.ts
index 9e14a804..4de93128 100644
--- a/src/distributions/distribution-factory.ts
+++ b/src/distributions/distribution-factory.ts
@@ -7,6 +7,7 @@ import { TemurinDistribution, TemurinImplementation } from './temurin/installer'
 import { LibericaDistributions } from './liberica/installer';
 import { MicrosoftDistributions } from './microsoft/installer';
 import { CorrettoDistribution } from './corretto/installer';
+import { OracleDistribution } from './oracle/installer';
 
 enum JavaDistribution {
   Adopt = 'adopt',
@@ -17,7 +18,8 @@ enum JavaDistribution {
   Liberica = 'liberica',
   JdkFile = 'jdkfile',
   Microsoft = 'microsoft',
-  Corretto = 'corretto'
+  Corretto = 'corretto',
+  Oracle = 'oracle'
 }
 
 export function getJavaDistribution(
@@ -43,6 +45,8 @@ export function getJavaDistribution(
       return new MicrosoftDistributions(installerOptions);
     case JavaDistribution.Corretto:
       return new CorrettoDistribution(installerOptions);
+    case JavaDistribution.Oracle:
+      return new OracleDistribution(installerOptions);
     default:
       return null;
   }
diff --git a/src/distributions/oracle/installer.ts b/src/distributions/oracle/installer.ts
new file mode 100644
index 00000000..ed00482f
--- /dev/null
+++ b/src/distributions/oracle/installer.ts
@@ -0,0 +1,103 @@
+import * as core from '@actions/core';
+import * as tc from '@actions/tool-cache';
+
+import fs from 'fs';
+import path from 'path';
+
+import { JavaBase } from '../base-installer';
+import { JavaDownloadRelease, JavaInstallerOptions, JavaInstallerResults } from '../base-models';
+import { extractJdkFile, getDownloadArchiveExtension } from '../../util';
+import { HttpCodes } from '@actions/http-client';
+
+const ORACLE_DL_BASE = 'https://download.oracle.com/java';
+
+export class OracleDistribution extends JavaBase {
+  constructor(installerOptions: JavaInstallerOptions) {
+    super('Oracle', installerOptions);
+  }
+
+  protected async downloadTool(javaRelease: JavaDownloadRelease): Promise<JavaInstallerResults> {
+    core.info(
+      `Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`
+    );
+    const javaArchivePath = await tc.downloadTool(javaRelease.url);
+
+    core.info(`Extracting Java archive...`);
+    let extension = getDownloadArchiveExtension();
+
+    let extractedJavaPath = await extractJdkFile(javaArchivePath, extension);
+
+    const archiveName = fs.readdirSync(extractedJavaPath)[0];
+    const archivePath = path.join(extractedJavaPath, archiveName);
+    const version = this.getToolcacheVersionName(javaRelease.version);
+
+    let javaPath = await tc.cacheDir(
+      archivePath,
+      this.toolcacheFolderName,
+      version,
+      this.architecture
+    );
+
+    return { version: javaRelease.version, path: javaPath };
+  }
+
+  protected async findPackageForDownload(range: string): Promise<JavaDownloadRelease> {
+    const arch = this.distributionArchitecture();
+    if (arch !== 'x64' && arch !== 'aarch64') {
+      throw new Error(`Unsupported architecture: ${this.architecture}`);
+    }
+
+    if (!this.stable) {
+      throw new Error('Early access versions are not supported');
+    }
+
+    if (this.packageType !== 'jdk') {
+      throw new Error('Oracle JDK provides only the `jdk` package type');
+    }
+
+    const platform = this.getPlatform();
+    const extension = getDownloadArchiveExtension();
+    let major;
+    let fileUrl;
+    if (range.includes('.')) {
+      major = range.split('.')[0];
+      fileUrl = `${ORACLE_DL_BASE}/${major}/archive/jdk-${range}_${platform}-${arch}_bin.${extension}`;
+    } else {
+      major = range;
+      fileUrl = `${ORACLE_DL_BASE}/${range}/latest/jdk-${range}_${platform}-${arch}_bin.${extension}`;
+    }
+
+    if (parseInt(major) < 17) {
+      throw new Error('Oracle JDK is only supported for JDK 17 and later');
+    }
+
+    const response = await this.http.head(fileUrl);
+
+    if (response.message.statusCode === HttpCodes.NotFound) {
+      throw new Error(`Could not find Oracle JDK for SemVer ${range}`);
+    }
+
+    if (response.message.statusCode !== HttpCodes.OK) {
+      throw new Error(
+        `Http request for Oracle JDK failed with status code: ${response.message.statusCode}`
+      );
+    }
+
+    return { url: fileUrl, version: range };
+  }
+
+  public getPlatform(platform: NodeJS.Platform = process.platform): OsVersions {
+    switch (platform) {
+      case 'darwin':
+        return 'macos';
+      case 'win32':
+        return 'windows';
+      case 'linux':
+        return 'linux';
+      default:
+        throw new Error(
+          `Platform '${platform}' is not supported. Supported platforms: 'linux', 'macos', 'windows'`
+        );
+    }
+  }
+}
diff --git a/src/distributions/oracle/models.ts b/src/distributions/oracle/models.ts
new file mode 100644
index 00000000..2838ead4
--- /dev/null
+++ b/src/distributions/oracle/models.ts
@@ -0,0 +1 @@
+export type OsVersions = 'linux' | 'macos' | 'windows';