com.metaeffekt.mirror.contents.eol.EolLifecycle Maven / Gradle / Ivy
/*
* Copyright 2021-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES 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.metaeffekt.mirror.contents.eol;
import com.metaeffekt.artifact.analysis.utils.FileUtils;
import com.metaeffekt.artifact.analysis.version.Version;
import com.metaeffekt.artifact.analysis.vulnerability.enrichment.VersionComparator;
import com.metaeffekt.mirror.contents.eol.state.LtsState;
import com.metaeffekt.mirror.contents.eol.state.SupportState;
import org.apache.lucene.document.Document;
import org.json.JSONArray;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
public class EolLifecycle {
private static final Logger LOG = LoggerFactory.getLogger(EolLifecycle.class);
private final String product;
private final List cycles;
public EolLifecycle(String product, List cycles) {
this.product = product;
this.cycles = cycles;
}
public EolLifecycle(String product) {
this.product = product;
this.cycles = new ArrayList<>();
}
public String getProduct() {
return product;
}
public List getCycles() {
return cycles;
}
public List getOrderedCycles() {
try {
return cycles.stream().sorted(Comparator.comparing(o -> Version.of(o.getCycle()))).collect(Collectors.toList());
} catch (Exception e) {
LOG.debug("Error sorting cycles for product [{}], returning unordered list: {}", product, cycles, e);
return cycles;
}
}
public void addCycle(EolCycle cycle) {
this.cycles.add(cycle);
}
public EolCycle findCycleFromVersion(String cycleQueryVersion) {
if (cycleQueryVersion == null) {
return null;
}
return cycles.stream()
.max((Comparator.comparingInt(o -> o.matchVersion(cycleQueryVersion))))
.orElse(null);
}
public EolCycle findCycleFromCycle(String cycle) {
if (cycle == null) {
return null;
}
return cycles.stream()
.filter(c -> c.getCycle().equals(cycle))
.findFirst()
.orElse(null);
}
public List findCyclesAfter(EolCycle cycle) {
if (cycle == null) {
return null;
}
final List result = new ArrayList<>();
boolean found = false;
for (EolCycle c : cycles) {
if (found) {
result.add(c);
}
if (c.equals(cycle)) {
found = true;
}
}
return result;
}
public List findCyclesBefore(EolCycle cycle) {
if (cycle == null) {
return null;
}
final List result = new ArrayList<>();
boolean found = false;
for (EolCycle c : cycles) {
if (c.equals(cycle)) {
found = true;
}
if (!found) {
result.add(c);
}
}
return result;
}
public EolCycle findNextSupportedCycle(EolCycle cycle) {
if (cycle == null) {
return null;
}
final List cyclesAfter = findCyclesAfter(cycle);
if (cyclesAfter.isEmpty()) {
return null;
}
for (EolCycle c : cyclesAfter) {
final SupportState supportState = c.getSupportState();
if (supportState == SupportState.SUPPORT || supportState == SupportState.UPCOMING_SUPPORT_END_DATE || supportState == SupportState.DISTANT_SUPPORT_END_DATE) {
return c;
}
}
return null;
}
public EolCycle findNextExtendedSupportCycle(EolCycle cycle) {
if (cycle == null) {
return null;
}
final List cyclesAfter = findCyclesAfter(cycle);
if (cyclesAfter.isEmpty()) {
return null;
}
for (EolCycle c : cyclesAfter) {
final SupportState supportState = c.getExtendedSupportState();
if (supportState == SupportState.SUPPORT || supportState == SupportState.UPCOMING_SUPPORT_END_DATE || supportState == SupportState.DISTANT_SUPPORT_END_DATE) {
return c;
}
}
return null;
}
/**
* Finds the closest active LTS (Long Term Support) version cycle to the given cycle.
*
* Only returns cycles that have LTS support. Will first try to find a cycle that comes after the given cycle, then
* as fallback will go back from the cycle and return the first cycle backwards that has LTS support.
*
* @param cycle the cycle to find the closest active LTS version cycle for
* @return the closest active LTS version cycle, or null if no active LTS version cycle is found
*/
public EolCycle findClosestActiveLtsVersionCycle(EolCycle cycle) {
if (cycle == null) {
return null;
}
{
final LtsState ltsState = cycle.getLtsState();
if (ltsState == LtsState.LTS || ltsState == LtsState.LTS_DATE_REACHED) {
return cycle;
}
}
final List cyclesAfter = findCyclesAfter(cycle);
for (EolCycle c : cyclesAfter) {
final LtsState ltsState = c.getLtsState();
if (ltsState == LtsState.LTS || ltsState == LtsState.LTS_DATE_REACHED) {
return c;
}
}
final List cyclesBefore = findCyclesBefore(cycle);
for (int i = cyclesBefore.size() - 1; i >= 0; i--) {
final EolCycle c = cyclesBefore.get(i);
final LtsState ltsState = c.getLtsState();
if (ltsState == LtsState.LTS || ltsState == LtsState.LTS_DATE_REACHED) {
return c;
}
}
return null;
}
public EolCycle findLatestActiveLtsVersionCycle(EolCycle cycle) {
if (cycle == null) {
return null;
}
final List cyclesAfter = findCyclesAfter(cycle);
for (int i = cyclesAfter.size() - 1; i >= 0; i--) {
final EolCycle c = cyclesAfter.get(i);
final LtsState ltsState = c.getLtsState();
if (ltsState == LtsState.LTS || ltsState == LtsState.LTS_DATE_REACHED) {
return c;
}
}
return null;
}
public JSONArray toJson() {
final JSONArray json = new JSONArray();
for (EolCycle cycle : cycles) {
json.put(cycle.toJson());
}
return json;
}
public List toDocuments() {
final List documents = new ArrayList<>();
for (EolCycle cycle : cycles) {
documents.add(cycle.toDocument());
}
return documents;
}
public static EolLifecycle fromJson(String product, JSONArray json) {
final EolLifecycle productInfo = new EolLifecycle(product);
for (int i = 0; i < json.length(); i++) {
final EolCycle cycle = EolCycle.fromJson(json.getJSONObject(i));
cycle.setProduct(product);
productInfo.addCycle(cycle);
}
return productInfo;
}
/**
* Writes the product information to the given directory using the {@link EolLifecycle#toJson() JSON format} into
* a file named {product}.json
.
*
* @param dir the directory to write the product information to
* @throws IOException if an I/O error occurs while writing the file
*/
public void writeToDirectory(File dir) throws IOException {
final File file = new File(dir, product + ".json");
FileUtils.write(file, toJson().toString(), StandardCharsets.UTF_8);
}
public String toLink() {
return "https://endoflife.date/" + product;
}
@Override
public String toString() {
return toJson().toString();
}
public String getLatestVersion() {
return cycles.stream()
.map(EolCycle::getLatest)
.peek(cycle -> {
if (cycle == null) {
LOG.warn("Cycle is null for product, will filter out: {}", product);
LOG.warn(" All data: {}", this);
}
})
.filter(Objects::nonNull)
.max(VersionComparator.INSTANCE)
.orElse(null);
}
}