Meta.java
package jasper.component;
import io.micrometer.core.annotation.Timed;
import jasper.domain.Metadata;
import jasper.domain.Ref;
import jasper.domain.Ref_;
import jasper.repository.RefRepository;
import jasper.repository.RefRepositoryCustom;
import jasper.repository.spec.OriginSpec;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import static jasper.domain.proj.Tag.matchesTemplate;
import static jasper.repository.spec.OriginSpec.isUnderOrigin;
import static jasper.repository.spec.RefSpec.hasInternalResponse;
import static jasper.repository.spec.RefSpec.hasResponse;
import static jasper.repository.spec.RefSpec.isUrl;
import static jasper.repository.spec.RefSpec.isUrls;
import static java.time.Instant.now;
import static java.util.stream.Collectors.toMap;
import static org.springframework.data.domain.Sort.Order.desc;
import static org.springframework.data.domain.Sort.by;
@Component
public class Meta {
private static final Logger logger = LoggerFactory.getLogger(Meta.class);
@Autowired
RefRepository refRepository;
@Autowired
RefRepositoryCustom refRepositoryCustom;
@Autowired
Messages messages;
private record UserUrlResponse(String tag, List<String> responses) { }
@Timed(value = "jasper.meta", histogram = true)
public void ref(String rootOrigin, Ref ref) {
if (ref == null) return;
ref.setMetadata(Metadata
.builder()
.expandedTags(expandTags(ref.getTags()))
.responses(refRepository.findAllResponsesWithoutTag(ref.getUrl(), rootOrigin, "internal"))
.internalResponses(refRepository.findAllResponsesWithTag(ref.getUrl(), rootOrigin, "internal"))
.userUrls(refRepositoryCustom.findAllUserPluginTagsInResponses(ref.getUrl(), rootOrigin)
.stream()
.map(tag -> new UserUrlResponse(
tag,
refRepository.findAllResponsesWithTag(ref.getUrl(), rootOrigin, tag)))
.filter(p -> !p.responses.isEmpty())
.collect(toMap(UserUrlResponse::tag, UserUrlResponse::responses)))
.plugins(refRepositoryCustom.countPluginTagsInResponses(ref.getUrl(), rootOrigin)
.stream()
.collect(toMap(r -> (String) r[0], r -> ((Number) r[1]).longValue())))
.build()
);
}
@Timed(value = "jasper.meta", histogram = true)
public void response(String rootOrigin, Ref ref) {
if (ref == null) return;
ref.setMetadata(Metadata
.builder()
.expandedTags(expandTags(ref.getTags()))
.build()
);
}
@Timed(value = "jasper.meta", histogram = true)
public void responseSource(String rootOrigin, Ref ref, Ref existing) {
if (ref != null && existing != null && existing.getTags() != null && existing.getTags().equals(ref.getTags())) return;
sources(rootOrigin, ref, existing);
}
public static List<String> expandTags(List<String> tags) {
if (tags == null) return new ArrayList<>();
var result = new ArrayList<>(tags);
for (var i = result.size() - 1; i >= 0; i--) {
var t = result.get(i);
while (t.contains("/")) {
t = t.substring(0, t.lastIndexOf("/"));
if (!result.contains(t)) {
result.add(t);
}
}
}
return result;
}
@Timed(value = "jasper.meta", histogram = true)
public void regen(String rootOrigin, Ref ref) {
var originalDate = ref.getMetadata() == null ? now().toString() : ref.getMetadata().getModified();
ref(rootOrigin, ref);
ref.getMetadata().setModified(originalDate);
ref.getMetadata().setObsolete(refRepository.newerExists(ref.getUrl(), rootOrigin, ref.getModified()));
if (ref.getMetadata().isObsolete()) return;
refRepository.updateObsolete(ref.getUrl(), rootOrigin);
var cleanupSources = refRepository.findAll(OriginSpec.<Ref>isUnderOrigin(rootOrigin)
.and(hasResponse(ref.getUrl()).or(hasInternalResponse(ref.getUrl()))));
for (var source : cleanupSources) {
if (ref.getSources() != null && ref.getSources().contains(source.getUrl())) {
ref(rootOrigin, source);
} else {
removeSource(rootOrigin, source, ref);
}
}
}
@Timed(value = "jasper.meta", histogram = true)
public void sources(String rootOrigin, Ref ref, Ref existing) {
if (ref != null) {
// Creating or updating (not deleting)
refRepository.updateObsolete(ref.getUrl(), rootOrigin);
// Update sources
List<Ref> sources = refRepository.findAll(isUrls(ref.getSources()).and(isUnderOrigin(rootOrigin)));
for (var source : sources) {
if (source.getUrl().equals(ref.getUrl())) continue;
var metadata = source.getMetadata();
if (metadata == null) {
logger.debug("Ref missing metadata: {}", ref.getUrl());
metadata = Metadata
.builder()
.responses(new ArrayList<>())
.internalResponses(new ArrayList<>())
.plugins(new HashMap<>())
.build();
}
if (ref.hasTag("internal")) {
metadata.addInternalResponse(ref.getUrl());
} else {
metadata.addResponse(ref.getUrl());
}
if (existing != null) {
metadata.removePlugins(existing.getExpandedTags().stream()
.filter(tag -> matchesTemplate("plugin", tag))
.toList(),
ref.getUrl());
}
metadata.addPlugins(ref.getExpandedTags().stream()
.filter(tag -> matchesTemplate("plugin", tag))
.toList(),
ref.getUrl());
source.setMetadata(metadata);
try {
refRepository.save(source);
messages.updateMetadata(source);
} catch (DataAccessException e) {
logger.error("Error updating source metadata for {} {}", ref.getOrigin(), ref.getUrl(), e);
}
}
} else {
// Deleting
var maybeLatest = refRepository.findAll(isUrl(existing.getUrl()).and(isUnderOrigin(rootOrigin)), PageRequest.of(0, 1, by(desc(Ref_.MODIFIED))));
if (!maybeLatest.isEmpty()) {
var latest = maybeLatest.getContent().get(0);
if (latest.getMetadata() != null) {
latest.getMetadata().setObsolete(false);
refRepository.save(latest);
messages.updateMetadata(latest);
}
}
}
if (existing != null && existing.getSources() != null) {
// Updating or deleting (not new)
var removedSources = ref == null
? existing.getSources()
: existing.getSources().stream()
.filter(s -> ref.getSources() == null || !ref.getSources().contains(s))
.toList();
List<Ref> removed = refRepository.findAll(isUrls(removedSources).and(isUnderOrigin(rootOrigin)));
for (var source : removed) {
if (source.getUrl().equals(existing.getUrl())) continue;
removeSource(rootOrigin, source, existing);
messages.updateMetadata(source);
}
}
}
private void removeSource(String rootOrigin, Ref source, Ref existing) {
var metadata = source.getMetadata();
if (metadata == null) return;
metadata.remove(existing.getUrl());
metadata.removePlugins(existing.getExpandedTags().stream()
.filter(tag -> matchesTemplate("plugin", tag))
.toList(),
existing.getUrl());
source.setMetadata(metadata);
try {
refRepository.save(source);
} catch (DataAccessException e) {
logger.error("{} Error updating source metadata for {} {}",
rootOrigin, source.getOrigin(), source.getUrl(), e);
}
}
}