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

com.metaeffekt.mirror.contents.store.ContentIdentifierStore Maven / Gradle / Ivy

The newest version!
package com.metaeffekt.mirror.contents.store;

import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.WordUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.json.JSONArray;
import org.json.JSONObject;

import java.util.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * ContentIdentifierStore is an abstract class that provides a storage mechanism for content identifiers, each represented by
 * a subclass of ContentIdentifier. This class supports registering, retrieving, and managing content identifiers based on their
 * name and implementation. The identifiers are validated and stored in a list, ensuring no duplicates based on name and implementation.
 * 

* Subclasses must implement methods to create specific identifiers and define default identifiers. * * @param the type of ContentIdentifier stored in this ContentIdentifierStore */ @Slf4j public abstract class ContentIdentifierStore { private final ReadWriteLock accessContentIdentifierLock = new ReentrantReadWriteLock(); private final List contentIdentifiers = new ArrayList<>(); protected ContentIdentifierStore() { this.contentIdentifiers.addAll(this.createDefaultIdentifiers()); } protected abstract T createIdentifier(@NonNull String name, @NonNull String implementation); protected abstract Collection createDefaultIdentifiers(); public List values() { return Collections.unmodifiableList(this.contentIdentifiers); } public T registerIdentifier(T contentIdentifier) { final Lock readLock = accessContentIdentifierLock.readLock(); readLock.lock(); try { if (contentIdentifier == null) { log.warn("Skipping registration of content identifier [null] in {}: {}", this.getClass().getSimpleName(), contentIdentifiers.stream().map(ContentIdentifier::getName).collect(Collectors.toList())); return null; } else if (contentIdentifier.getName() == null) { log.warn("Skipping registration of content identifier [null] in {}->{}: {}", this.getClass().getSimpleName(), contentIdentifier.getClass().getSimpleName(), contentIdentifiers.stream().map(ContentIdentifier::getName).collect(Collectors.toList())); return null; } else if (contentIdentifier.getWellFormedName() == null) { contentIdentifier.setWellFormedName(ContentIdentifier.deriveWellFormedName(contentIdentifier.getName())); log.warn("Deriving missing well-formed name for content identifier [{}] in {}->{}: {}", contentIdentifier.getName(), this.getClass().getSimpleName(), contentIdentifier.getClass().getSimpleName(), contentIdentifiers.stream().map(ContentIdentifier::getName).collect(Collectors.toList())); } final T existing = contentIdentifiers.stream() .filter(ci -> ci.getName().equals(contentIdentifier.getName()) && ci.getImplementation().equals(contentIdentifier.getImplementation())) .findFirst().orElse(null); if (existing != null) { log.debug("Skipping registration of content identifier with name [{}], already present in {}->{}: {}", contentIdentifier.getName(), this.getClass().getSimpleName(), contentIdentifier.getClass().getSimpleName(), contentIdentifiers.stream().map(ContentIdentifier::getName).collect(Collectors.toList())); return existing; } } finally { readLock.unlock(); } final Lock writeLock = accessContentIdentifierLock.writeLock(); writeLock.lock(); try { contentIdentifiers.add(contentIdentifier); } finally { writeLock.unlock(); } return contentIdentifier; } public List fromMapNamesAndImplementations(Map entries) { return entries.entrySet().stream() .map(entry -> this.fromNameAndImplementation(entry.getKey(), entry.getValue())) .collect(Collectors.toList()); } public T fromNameAndImplementation(String name, String implementation) { final boolean hasName = !StringUtils.isEmpty(name); final boolean hasImplementation = !StringUtils.isEmpty(implementation); if (!hasName) { throw new IllegalArgumentException("Name must not be blank or null for content identifier in " + this.getClass().getSimpleName()); } final String effectiveImplementation = hasImplementation ? implementation : name; final Lock readLock = accessContentIdentifierLock.readLock(); readLock.lock(); try { // as a first attempt, use the name AND implementation to find matching content identifiers final Optional byNameAndImplementation = contentIdentifiers.stream() .filter(ci -> (name.equals(ci.getName()) || name.equals(ci.getWellFormedName())) && effectiveImplementation.equals(ci.getImplementation())) .findFirst(); if (byNameAndImplementation.isPresent()) { return byNameAndImplementation.get(); } final Optional byName = contentIdentifiers.stream() .filter(ci -> name.equals(ci.getName()) || name.equals(ci.getWellFormedName())) .findFirst(); if (byName.isPresent()) { return byName.get(); } } finally { readLock.unlock(); } // otherwise the identifier does not exist yet, register a new content identifier return this.registerIdentifier(this.createIdentifier(name, effectiveImplementation)); } public T fromNameWithoutCreation(String name) { final boolean hasName = !StringUtils.isEmpty(name); if (!hasName) { throw new IllegalArgumentException("Name must not be blank or null for content identifier in " + this.getClass().getSimpleName()); } final Lock readLock = accessContentIdentifierLock.readLock(); readLock.lock(); try { final Optional byName = contentIdentifiers.stream() .filter(ci -> name.equals(ci.getName()) || name.equals(ci.getWellFormedName())) .findFirst(); return byName.orElse(null); } finally { readLock.unlock(); } } public T fromNameAndImplementationWithoutCreation(String name, String implementation) { final boolean hasName = !StringUtils.isEmpty(name); final boolean hasImplementation = !StringUtils.isEmpty(implementation); if (!hasName) { throw new IllegalArgumentException("Name must not be blank or null for content identifier in " + this.getClass().getSimpleName()); } final Lock readLock = accessContentIdentifierLock.readLock(); readLock.lock(); try { final Optional byName = contentIdentifiers.stream() // returns true if: // - the name matches the name of the content identifier AND the implementation matches the implementation of the content identifier // - the name matches the name of the content identifier AND the implementation is not provided // - the name matches the well-formed name of the content identifier AND the implementation matches the implementation of the content identifier // - the name matches the well-formed name of the content identifier AND the implementation is not provided .filter(ci -> (name.equals(ci.getName()) || name.equals(ci.getWellFormedName())) && (!hasImplementation || implementation.equals(ci.getImplementation()))) .findFirst(); return byName.orElse(null); } finally { readLock.unlock(); } } public Optional fromId(String id) { if (StringUtils.isEmpty(id)) { throw new IllegalArgumentException("Id must not be blank or null for content identifier in " + this.getClass().getSimpleName()); } final Lock readLock = accessContentIdentifierLock.readLock(); readLock.lock(); try { return contentIdentifiers.stream() .filter(ci -> ci.patternMatchesId(id)) .findFirst(); } finally { readLock.unlock(); } } public SingleContentIdentifierParseResult fromJsonNameAndImplementation(JSONObject json) { final String name = ObjectUtils.firstNonNull(json.optString("source", null), json.optString("name", null), "unknown"); final String implementation = ObjectUtils.firstNonNull(json.optString("implementation", null), name, "unknown"); final T identifier = fromNameAndImplementation(name, implementation); final String id = json.optString("id", null); return new SingleContentIdentifierParseResult<>(identifier, id); } public List fromJsonNamesAndImplementations(JSONArray json) { final List identifiers = new ArrayList<>(); for (int i = 0; i < json.length(); i++) { final JSONObject jsonObject = json.getJSONObject(i); final SingleContentIdentifierParseResult pair = fromJsonNameAndImplementation(jsonObject); identifiers.add(pair.getIdentifier()); } return identifiers; } public Map> fromJsonMultipleReferencedIds(JSONArray json) { final Map> referencedIds = new HashMap<>(); for (int i = 0; i < json.length(); i++) { final JSONObject jsonObject = json.getJSONObject(i); final SingleContentIdentifierParseResult pair = fromJsonNameAndImplementation(jsonObject); final T identifier = pair.getIdentifier(); final String id = pair.getId(); referencedIds.computeIfAbsent(identifier, k -> new HashSet<>()).add(id); } return referencedIds; } public SingleContentIdentifierParseResult fromMap(Map map) { final String name = ObjectUtils.firstNonNull(map.get("source"), map.get("name"), "unknown").toString(); final String implementation = ObjectUtils.firstNonNull(map.get("implementation"), name, "unknown").toString(); final T identifier = fromNameAndImplementation(name, implementation); final String id = map.get("id") == null ? null : map.get("id").toString(); return new SingleContentIdentifierParseResult<>(identifier, id); } public Map> fromListMultipleReferencedIds(List> map) { final Map> referencedIds = new HashMap<>(); for (Map entry : map) { final SingleContentIdentifierParseResult pair = fromMap(entry); final T identifier = pair.getIdentifier(); final String id = pair.getId(); referencedIds.computeIfAbsent(identifier, k -> new HashSet<>()).add(id); } return referencedIds; } public static JSONArray toJson(Map> referencedIds) { final JSONArray jsonArray = new JSONArray(); for (Map.Entry> entry : referencedIds.entrySet()) { final String name = entry.getKey().getName(); final String implementation = entry.getKey().getImplementation(); final Set ids = entry.getValue(); for (String id : ids) { jsonArray.put(new JSONObject() .put("name", name) .put("implementation", implementation) .put("id", id)); } } return jsonArray; } @Data public static class SingleContentIdentifierParseResult { private final T identifier; private final String id; } public static Map> parseLegacyJsonReferencedIds(JSONObject referencedIds) { final Map> mapFormatReferencedIds = new HashMap<>(); referencedIds.toMap().forEach((key, value) -> { if (value instanceof Collection) { mapFormatReferencedIds.put(key, new HashSet<>((Collection) value)); } else { log.warn("Could not parse referenced ids of type [{}]: {}", key, value); } }); return parseLegacyJsonReferencedIds(mapFormatReferencedIds); } public static > Map> parseLegacyJsonReferencedIds(Map referencedIds) { final AdvisoryTypeStore advisoryTypeStore = AdvisoryTypeStore.get(); final VulnerabilityTypeStore vulnerabilityTypeStore = VulnerabilityTypeStore.get(); final OtherTypeStore otherTypeStore = OtherTypeStore.get(); final Map> parsedReferencedIds = new HashMap<>(); for (Map.Entry entry : referencedIds.entrySet()) { final AdvisoryTypeIdentifier advisoryIdentifier = advisoryTypeStore.fromNameWithoutCreation(entry.getKey()); final VulnerabilityTypeIdentifier vulnerabilityIdentifier = vulnerabilityTypeStore.fromNameWithoutCreation(entry.getKey()); final OtherTypeIdentifier otherIdentifier = otherTypeStore.fromNameWithoutCreation(entry.getKey()); if (advisoryIdentifier != null) { parsedReferencedIds.computeIfAbsent(advisoryIdentifier, k -> new HashSet<>()).addAll(entry.getValue()); } else if (vulnerabilityIdentifier != null) { parsedReferencedIds.computeIfAbsent(vulnerabilityIdentifier, k -> new HashSet<>()).addAll(entry.getValue()); } else if (otherIdentifier != null) { parsedReferencedIds.computeIfAbsent(otherIdentifier, k -> new HashSet<>()).addAll(entry.getValue()); } else { log.warn("Could not find content identifier for referenced ids of type [{}]: {}", entry.getKey(), entry.getValue()); } } return parsedReferencedIds; } public static JSONObject mergeIntoLegacyJson(List>> contentIdentifiersTypes) { final JSONObject json = new JSONObject(); for (Map> contentIdentifiers : contentIdentifiersTypes) { for (Map.Entry> entry : contentIdentifiers.entrySet()) { json.put(entry.getKey().getName(), new JSONArray(entry.getValue())); } } return json; } @Data @NoArgsConstructor public abstract static class ContentIdentifier { /** * Unique identifiable name of the content source.
* SHOULD only contain [a-zA-Z0-9_], but this will not be validated for. * Use {@link #prepareName(String)} to prepare the name to ensure it follows this pattern. */ private String name; /** * Well-formed name of the content source.
* Will be used as a display name and SHOULD therefore be human-readable. */ private String wellFormedName; /** * Specific implementation of the content source.
* May be left away if is identical to the {@link #name}. * Describes the implementation class (type) of a content source and will overwrite the {@link #name} when searching for an implementation class. */ private String implementation; /** * Pattern to identify the content source by a representation of an identifier provided by the source of this content identifier. */ private Pattern idPattern; public ContentIdentifier(String name, String wellFormedName, String implementation, Pattern idPattern) { this.name = name; this.wellFormedName = wellFormedName; this.setImplementation(implementation); this.idPattern = idPattern; } public void setImplementation(String implementation) { this.implementation = StringUtils.isEmpty(implementation) ? this.name : implementation; } public String name() { return this.name; } /** * Will use the {@link #idPattern} to check if the given id matches the pattern.
* If no pattern is provided, this will always return false. * * @param id Id to check * @return true if the id matches the pattern, false otherwise */ public boolean patternMatchesId(String id) { if (idPattern == null) { return false; } return idPattern.matcher(id).matches(); } public Set fromFreeText(String text) { if (StringUtils.isEmpty(text) || this.idPattern == null) { return Collections.emptySet(); } final Set ids = new HashSet<>(); final Matcher matcher = this.idPattern.matcher(text); while (matcher.find()) { ids.add(matcher.group()); } return ids; } @Override public String toString() { return Objects.toString(this.getName()); } public String toExtendedString() { if (this.implementation.equals(this.name)) { return this.getName() + " (" + this.getWellFormedName() + ")"; } else { return this.getName() + ":" + this.getImplementation() + " (" + this.getWellFormedName() + ")"; } } /** * Prepare the name to only contain [a-zA-Z0-9_]. * Will replace all characters not matching this pattern with an underscore and remove multiple underscores. * * @param name Name to prepare * @return Name with only [a-zA-Z0-9_] */ public static String prepareName(String name) { return name.replaceAll("[^a-zA-Z0-9_]", "_").replaceAll("_+", "_"); } /** * Derive a well-formed name from the given name.
* Will convert the name to lowercase, replace all [-_ ]+ with a single space and capitalize the first letter of each word.
* If the name starts with cert , it will be replaced with CERT- and converted to uppercase. *

* Example: cert cve -> CERT-CVE or gitlab -> Gitlab * * @param name Name to derive a well-formed name from * @return Well-formed name */ public static String deriveWellFormedName(String name) { name = name.toLowerCase().replaceAll("[-_ ]+", " "); if (name.startsWith("cert ")) { return name.replaceFirst("cert ", "CERT-").toUpperCase(); } else { return WordUtils.capitalize(name); } } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy