com.metaeffekt.mirror.contents.store.ContentIdentifierStore Maven / Gradle / Ivy
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