All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.igormaznitsa.mvnjlink.jdkproviders.providers.SapmachineOpenJdkProvider Maven / Gradle / Ivy

Go to download

There is a newer version: 1.2.4
Show newest version
/*
 * Copyright 2019 Igor Maznitsa.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.igormaznitsa.mvnjlink.jdkproviders.providers;

import com.igormaznitsa.meta.annotation.MustNotContainNull;
import com.igormaznitsa.meta.common.utils.GetUtils;
import com.igormaznitsa.mvnjlink.exceptions.FailureException;
import com.igormaznitsa.mvnjlink.jdkproviders.AbstractJdkProvider;
import com.igormaznitsa.mvnjlink.mojos.AbstractJdkToolMojo;
import com.igormaznitsa.mvnjlink.utils.ArchUtils;
import com.igormaznitsa.mvnjlink.utils.HttpUtils;
import com.igormaznitsa.mvnjlink.utils.StringUtils;
import com.igormaznitsa.mvnjlink.utils.WildCardMatcher;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.http.Header;
import org.apache.http.client.HttpClient;
import org.apache.maven.plugin.logging.Log;
import org.json.JSONArray;
import org.json.JSONObject;

import javax.annotation.Nonnull;
import java.io.IOException;
import java.nio.file.Path;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static com.igormaznitsa.meta.common.utils.Assertions.assertNotNull;
import static com.igormaznitsa.mvnjlink.utils.ArchUtils.unpackArchiveFile;
import static java.nio.file.Files.*;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Stream.of;
import static org.apache.commons.io.FileUtils.deleteDirectory;

/**
 * Provider of prebuilt SAPMACHINE OpenJDK  distributives.
 *
 * @since 1.0.2
 */
public class SapmachineOpenJdkProvider extends AbstractJdkProvider {

  private static final String RELEASES_LIST = "https://api.github.com/repos/SAP/SapMachine/releases";
  private static final Pattern ETAG_PATTERN = Pattern.compile("^\"?([a-fA-F0-9]{32}).*\"?$");

  public SapmachineOpenJdkProvider(@Nonnull final AbstractJdkToolMojo mojo) {
    super(mojo);
  }

  @Nonnull
  @Override
  public Path getPathToJdk(@Nonnull final Map config) throws IOException {
    final Log log = this.mojo.getLog();

    assertParameters(config, "type", "version", "arch");

    final String defaultOs = findCurrentOs("osx");

    log.debug("Default OS recognized as: " + defaultOs);

    final String jdkType = config.get("type");
    final String jdkVersion = config.get("version");
    final String jdkOs = GetUtils.ensureNonNull(config.get("os"), defaultOs);
    final String jdkArch = config.get("arch");
    final boolean keepArchiveFile = Boolean.parseBoolean(config.getOrDefault("keepArchive", "false"));

    final Path cacheFolder = this.mojo.findJdkCacheFolder();
    final Path cachedJdkPath = cacheFolder.resolve(String.format("SAPMACHINE_%s%s_%s_%s",
        StringUtils.escapeFileName(jdkType.toLowerCase(Locale.ENGLISH)),
        StringUtils.escapeFileName(jdkVersion.toLowerCase(Locale.ENGLISH)),
        StringUtils.escapeFileName(jdkOs.toLowerCase(Locale.ENGLISH)),
        StringUtils.escapeFileName(jdkArch.toLowerCase(Locale.ENGLISH))
    ));

    final Path result;

    if (isDirectory(cachedJdkPath)) {
      log.info("Found cached JDK: " + cachedJdkPath.getFileName());
      result = cachedJdkPath;
    } else {
      if (isOfflineMode()) {
        throw new FailureException("Unpacked '" + cachedJdkPath.getFileName() + "' is not found, stopping process because offline mode is active");
      } else {
        log.info("Can't find cached: " + cachedJdkPath.getFileName());
      }

      final HttpClient httpClient = HttpUtils.makeHttpClient(log, this.mojo.getProxy(), this.mojo.isDisableSSLcheck());

      final ReleaseList releaseList = new ReleaseList();
      List foundReleases = Collections.emptyList();
      int page = 1;

      while (!Thread.currentThread().isInterrupted()) {
        log.debug("Loading releases page: " + page);

        final ReleaseList pageReleases = new ReleaseList(log, doHttpGetText(httpClient, RELEASES_LIST + "?per_page=100&page=" + page, this.mojo.getConnectionTimeout(), "application/vnd.github.v3+json"));
        releaseList.add(pageReleases);

        foundReleases = releaseList.find(jdkType, jdkVersion, jdkOs, jdkArch);

        if (!foundReleases.isEmpty() || pageReleases.isEmpty()) {
          break;
        }

        page++;
      }

      if (foundReleases.isEmpty()) {
        log.warn("Found releases\n" + releaseList.makeReport());
        throw new IOException(String.format("Can't find release for version='%s', type='%s', os='%s', arch='%s'", jdkVersion, jdkType, jdkOs, jdkArch));
      } else {

        log.debug("Found releases: " + foundReleases);

        final Optional tarRelease = foundReleases.stream().filter(x -> "tar.gz".equalsIgnoreCase(x.extension)).findFirst();
        final Optional zipRelease = foundReleases.stream().filter(x -> "zip".equalsIgnoreCase(x.extension)).findFirst();

        final ReleaseList.Release releaseToLoad = of(tarRelease, zipRelease).filter(Optional::isPresent).findFirst().get().get();
        result = loadJdkIntoCacheIfNotExist(cacheFolder, assertNotNull(cachedJdkPath.getFileName()).toString(), tempFolder ->
            downloadAndUnpack(httpClient, cacheFolder, tempFolder, releaseToLoad, keepArchiveFile)
        );
      }
    }
    return result;
  }

  private void downloadAndUnpack(
      @Nonnull final HttpClient client,
      @Nonnull final Path tempFolder,
      @Nonnull final Path destUnpackFolder,
      @Nonnull final ReleaseList.Release release,
      final boolean keepArchiveFile
  ) throws IOException {

    final Log log = this.mojo.getLog();

    final Path pathToArchiveFile = tempFolder.resolve(release.fileName);

    boolean doLoadArchive = true;

    if (isRegularFile(pathToArchiveFile)) {
      log.info("Detected loaded archive: " + pathToArchiveFile.getFileName());
      doLoadArchive = false;
    }

    if (doLoadArchive) {
      final MessageDigest digest = DigestUtils.getMd5Digest();
      final Header[] responseHeaders = this.doHttpGetIntoFile(client, release.link, pathToArchiveFile, digest, this.mojo.getConnectionTimeout(), release.mime, HttpUtils.MIME_OCTET_STREAM);

      log.debug("Response headers: " + Arrays.toString(responseHeaders));

      final String calculatedMd5Digest = Hex.encodeHexString(digest.digest());

      log.info("Archive has been loaded successfuly, calculated MD5 digest is " + calculatedMd5Digest);

      final Optional
etag = of(responseHeaders).filter(x -> "ETag".equalsIgnoreCase(x.getName())).findFirst(); if (etag.isPresent()) { final Matcher matcher = ETAG_PATTERN.matcher(etag.get().getValue()); if (matcher.find()) { final String extractedEtag = matcher.group(1); if (calculatedMd5Digest.equalsIgnoreCase(extractedEtag)) { log.info("Calculated MD5 is equal to the ETag in response"); } else { log.warn("Calculated MD5 is not equal to the ETag in response: " + calculatedMd5Digest + " != " + extractedEtag); } } else { log.error("Can't extract MD5 from ETag: " + etag.get().getValue()); } } else { log.warn("ETag is not presented in the response or its value can't be parsed"); } } else { log.info("Archive loading is skipped"); } if (isDirectory(destUnpackFolder)) { log.info("Detected existing target folder, deleting it: " + destUnpackFolder.getFileName()); deleteDirectory(destUnpackFolder.toFile()); } final String archiveRootName = ArchUtils.findShortestDirectory(pathToArchiveFile); log.debug("Root folder in archive: " + archiveRootName); log.info("Unpacking archive..."); final int numberOfUnpackedFiles = unpackArchiveFile(this.mojo.getLog(), true, pathToArchiveFile, destUnpackFolder, archiveRootName); if (numberOfUnpackedFiles == 0) { throw new IOException("Extracted 0 files from archive! May be wrong root folder name: " + archiveRootName); } log.info("Archive has been unpacked successfully, extracted " + numberOfUnpackedFiles + " files"); if (keepArchiveFile) { log.info("Keep downloaded archive file in cache: " + pathToArchiveFile); } else { log.info("Deleting archive: " + pathToArchiveFile); delete(pathToArchiveFile); } } private static class ReleaseList { private final List releases = new ArrayList<>(); private ReleaseList(@Nonnull final Log log, @Nonnull final String json) { final JSONArray array = new JSONArray(json); for (int i = 0; i < array.length(); i++) { final JSONObject release = array.getJSONObject(i); if (!release.has("tag_name")) { continue; } final boolean draft = release.getBoolean("draft"); final boolean prerelease = release.getBoolean("prerelease"); if (draft || prerelease) { continue; } if (release.has("assets")) { final JSONArray assets = release.getJSONArray("assets"); for (int a = 0; a < assets.length(); a++) { final JSONObject asset = assets.getJSONObject(a); final String fileName = asset.getString("name"); final String mime = asset.getString("content_type"); final long size = asset.getLong("size"); if (fileName.endsWith(".zip") || fileName.endsWith(".tar.gz")) { final String link = asset.getString("browser_download_url"); try { this.releases.add(new Release(fileName, link, mime, size)); } catch (IllegalArgumentException ex) { log.debug("Ignoring because non-standard name: " + fileName); } } else { log.debug("Ignoring because non-unpackable file: " + asset); } } } } } private ReleaseList() { } public void add(@Nonnull final ReleaseList list) { this.releases.addAll(list.releases); } public boolean isEmpty() { return this.releases.isEmpty(); } @Nonnull @MustNotContainNull public List find(@Nonnull final String type, @Nonnull final String version, @Nonnull final String os, @Nonnull final String arch) { final WildCardMatcher matcher = new WildCardMatcher(version, true); return this.releases.stream() .filter(x -> x.type.equalsIgnoreCase(type)) .filter(x -> x.os.equalsIgnoreCase(os)) .filter(x -> x.arch.equalsIgnoreCase(arch)) .filter(x -> matcher.match(x.version)).collect(toList()); } @Nonnull public String makeReport() { return this.releases.stream().map(Release::toString).collect(joining("\n")); } private static class Release { private static final Pattern SAPMACHINE_FILENAME_PATTERN = Pattern.compile("^sapmachine-(jdk|jre)-([a-z\\-0-9.+]+)_([a-z]+)-([a-z0-9\\-]+)_bin.(.+)$", Pattern.CASE_INSENSITIVE); private final String type; private final String version; private final String os; private final String arch; private final String fileName; private final String link; private final String mime; private final String extension; private final long size; private Release( @Nonnull final String fileName, @Nonnull final String link, @Nonnull final String mime, final long size ) { this.fileName = fileName; this.link = link; this.mime = mime; this.size = size; final Matcher matcher = SAPMACHINE_FILENAME_PATTERN.matcher(fileName); if (matcher.find()) { this.type = matcher.group(1); this.version = matcher.group(2); this.os = matcher.group(3); this.arch = matcher.group(4); this.extension = matcher.group(5); } else { throw new IllegalArgumentException("Can't parse file name: " + fileName); } } @Nonnull @Override public String toString() { return String.format("Release[type='%s',version='%s',os='%s',arch='%s',ext='%s']", this.type, this.version, this.os, this.arch, this.extension); } } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy