Ref.java
package jasper.domain;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.hypersistence.utils.hibernate.type.search.PostgreSQLTSVectorType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.IdClass;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jasper.domain.proj.HasOrigin;
import jasper.domain.proj.HasTags;
import jasper.domain.proj.Tag;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.Formula;
import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.annotations.Type;
import org.hibernate.type.SqlTypes;
import org.hibernate.validator.constraints.Length;
import org.springframework.data.annotation.CreatedDate;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import static jasper.component.Meta.expandTags;
import static jasper.config.JacksonConfiguration.om;
import static jasper.domain.proj.Tag.TAG_LEN;
import static jasper.domain.proj.Tag.matchesTag;
import static org.apache.commons.lang3.StringUtils.isBlank;
@Entity
@Getter
@Setter
@IdClass(RefId.class)
public class Ref implements HasTags {
public static final String REGEX = "^[^:/?#]+:(?://[^/?#]*)?[^?#]*(?:\\?[^#]*)?(?:#.*)?";
public static final String SCHEME_REGEX = "^[^:/?#]+:";
public static final int URL_LEN = 4096;
public static final int TITLE_LEN = 512;
@Id
@Column(updatable = false)
@NotBlank
@Pattern(regexp = REGEX)
@Length(max = URL_LEN)
private String url;
@Id
@Column(updatable = false)
@Pattern(regexp = HasOrigin.REGEX)
@Length(max = ORIGIN_LEN)
private String origin = "";
@Length(max = TITLE_LEN)
private String title;
private String comment;
@JdbcTypeCode(SqlTypes.JSON)
private List<@NotBlank @Length(max = TAG_LEN) @Pattern(regexp = Tag.REGEX) String> tags;
@JdbcTypeCode(SqlTypes.JSON)
private List<@NotBlank @Length(max = URL_LEN) @Pattern(regexp = REGEX) String> sources;
@JdbcTypeCode(SqlTypes.JSON)
private List<@NotBlank @Length(max = URL_LEN) @Pattern(regexp = REGEX) String> alternateUrls;
@JdbcTypeCode(SqlTypes.JSON)
private ObjectNode plugins;
@JdbcTypeCode(SqlTypes.JSON)
private Metadata metadata;
@Formula("SUBSTR(url, 1, INSTR(url, ':'))")
@Setter(AccessLevel.NONE)
private String scheme;
@Column(nullable = false)
@NotNull
private Instant published = Instant.now();
@CreatedDate
@Column(updatable = false, nullable = false)
private Instant created = Instant.now();
@Column(nullable = false)
private Instant modified = Instant.now();
@Type(PostgreSQLTSVectorType.class)
@Column(updatable = false, insertable = false)
private String textsearchEn;
public boolean hasPluginResponse(String tag) {
if (metadata == null) return false;
if (metadata.getPlugins() == null) return false;
return metadata.getPlugins().keySet().stream()
.filter(t -> matchesTag(tag, t))
.anyMatch(t -> metadata.getPlugins().get(t) > 0);
}
public void setOrigin(String value) {
origin = value == null ? "" : value;
}
@JsonIgnore
public List<String> getExpandedTags() {
if (metadata != null &&
metadata.getExpandedTags() != null &&
!metadata.getExpandedTags().isEmpty()) {
return metadata.getExpandedTags();
}
return expandTags(tags);
}
@JsonIgnore
public Ref removeTag(String tag) {
if (tags == null || isBlank(tag)) return this;
for (int i = tags.size() - 1; i >= 0; i--) {
var remove = tags.get(i);
if (matchesTag(tag, remove)) {
tags.remove(i);
setPlugin(remove, null);
}
}
if (plugins != null) {
var remove = new ArrayList<String>();
plugins.fieldNames().forEachRemaining(p -> {
if (matchesTag(tag, p)) remove.add(p);
});
for (var p : remove) setPlugin(p, null);
}
return this;
}
@JsonIgnore
public Ref removeTags(List<String> toRemove) {
if (tags == null || toRemove == null) return this;
for (var r : toRemove) removeTag(r);
return this;
}
@JsonIgnore
public Ref clearPlugins() {
if (plugins != null) {
var remove = new ArrayList<String>();
plugins.fieldNames().forEachRemaining(p -> {
if (!hasTag(p)) remove.add(p);
});
for (var p : remove) setPlugin(p, null);
}
return this;
}
@JsonIgnore
public Ref addTag(String tag) {
if (isBlank(tag)) return this;
if (isBlank(tag)) return this;
if (tags == null) {
if (tag.startsWith("-")) return this;
tags = new ArrayList<>();
tags.add(tag);
} else {
if (tag.startsWith("-")) {
removeTag(tag.substring(1));
} else if (!tags.contains(tag)) {
tags.add(tag);
}
}
return this;
}
@JsonIgnore
public Ref addTags(List<String> toAdd) {
if (toAdd == null) return this;
for (var t : toAdd) addTag(t);
return this;
}
@JsonIgnore
public Ref addPlugins(List<String> toAdd, ObjectNode from) {
if (toAdd == null || from == null) return this;
for (var t : toAdd) {
if (from.has(t)) setPlugin(t, from.get(t));
}
return this;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Ref ref = (Ref) o;
return url.equals(ref.url) && origin.equals(ref.origin);
}
@Override
public int hashCode() {
return Objects.hash(url, origin);
}
@JsonIgnore
public Ref setPlugin(String tag, Object jsonNode) {
if (jsonNode == null) {
if (plugins != null) plugins.remove(tag);
return this;
}
if (plugins == null) plugins = om().createObjectNode();
addTag(tag);
plugins.set(tag, om().convertValue(jsonNode, JsonNode.class));
return this;
}
@JsonIgnore
public boolean hasPlugin(String tag) {
if (plugins == null) return false;
if (!plugins.has(tag)) return false;
return plugins.get(tag) != null;
}
@JsonIgnore
public JsonNode getPlugin(String tag) {
if (plugins == null) return null;
if (!plugins.has(tag)) return null;
return plugins.get(tag);
}
@JsonIgnore
public <T> T getPlugin(String tag, Class<T> toValueType) {
if (plugins == null) return null;
if (!plugins.has(tag)) return null;
return om().convertValue(plugins.get(tag), toValueType);
}
@JsonIgnore
public long getPluginResponses(String tag) {
if (metadata == null) return 0;
if (metadata.getPlugins() == null) return 0;
if (!metadata.getPlugins().containsKey(tag)) return 0;
if (metadata.getPlugins().get(tag) == null) return 0;
return metadata.getPlugins().get(tag);
}
@JsonIgnore
public boolean hasTag(String ...tag) {
if (tags == null) return false;
return Arrays.stream(tag).allMatch(m -> tags.stream().anyMatch(t -> matchesTag(m, t)));
}
public static Ref from(String url, String origin, String ...tags) {
var result = new Ref();
result.setUrl(url);
result.setOrigin(origin);
for (var tag : tags) result.addTag(tag);
return result;
}
}