Добавил тесты и стабилизировал сущности. Рефакторинг ValueTransformationChain

This commit is contained in:
kirillius 2026-01-08 09:47:38 +03:00
parent 2d7c7fea9c
commit dcb15ebdc1
35 changed files with 836 additions and 129 deletions

View File

@ -0,0 +1,7 @@
package ru.kirillius.XCP.Commons;
import java.io.Closeable;
public interface Initializable extends Closeable {
void initialize(Context context);
}

View File

@ -1,7 +1,5 @@
package ru.kirillius.XCP.Commons; package ru.kirillius.XCP.Commons;
import java.io.Closeable; public interface Service extends Initializable {
public interface Service extends Closeable {
void initialize(Context context);
} }

View File

@ -0,0 +1,17 @@
package ru.kirillius.XCP.Data;
import ru.kirillius.XCP.Commons.Initializable;
import ru.kirillius.java.utils.events.EventHandler;
import tools.jackson.databind.node.ObjectNode;
public interface DataAdapter extends Initializable {
Object send(Object value, ObjectNode properties);
Object receive(ObjectNode properties);
EventHandler<Object> subscribe();
void unsubscribe(EventHandler<Object> subscription);
}

View File

@ -1,6 +0,0 @@
package ru.kirillius.XCP.Data;
@Deprecated
public interface DataTransferProtocol {
//TODO
}

View File

@ -1,20 +1,19 @@
package ru.kirillius.XCP.Data; package ru.kirillius.XCP.Data;
public interface PollSettings { public interface PollSettings {
int getMaxValueCount();
void setMaxValueCount(int maxValueCount);
long getPollInterval(); long getPollInterval();
void setPollInterval(long pollInterval); void setPollInterval(long pollInterval);
boolean isInterruptIfBusy(); boolean isInterruptable();
void setInterruptIfBusy(boolean interruptIfBusy); void setInterruptable(boolean interruptable);
boolean setEnabled(); boolean isRateMeasurement();
void setRateMeasurement(boolean rateMeasurement);
void setEnabled(boolean enabled);
boolean isEnabled(); boolean isEnabled();
} }

View File

@ -1,6 +0,0 @@
package ru.kirillius.XCP.Data;
@Deprecated
public interface ValueModifier {
//TODO
}

View File

@ -1,14 +0,0 @@
package ru.kirillius.XCP.Data;
import tools.jackson.databind.node.ObjectNode;
public interface ValueModifierSettings {
Class<? extends ValueModifier> getModifierClass();
void setModifierClass(Class<? extends ValueModifier> modifierClass);
ObjectNode getParameters();
void setParameters(ObjectNode parameters);
}

View File

@ -0,0 +1,24 @@
package ru.kirillius.XCP.Data;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSetter;
import com.fasterxml.jackson.annotation.Nulls;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import tools.jackson.databind.annotation.JsonDeserialize;
import java.util.ArrayList;
import java.util.List;
@NoArgsConstructor
@AllArgsConstructor
public final class ValueTransformationChain {
@Getter
@Setter
@JsonSetter(nulls = Nulls.SKIP)
@JsonProperty
@JsonDeserialize(contentAs = ValueTransformationStep.class)
private List<ValueTransformationStep> steps = new ArrayList<>();
}

View File

@ -0,0 +1,19 @@
package ru.kirillius.XCP.Data;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import tools.jackson.databind.node.ObjectNode;
@NoArgsConstructor
@AllArgsConstructor
public final class ValueTransformationStep {
@Getter
@Setter
private String transformerId;
@Getter
@Setter
ObjectNode configuration;
}

View File

@ -0,0 +1,10 @@
package ru.kirillius.XCP.Data;
import org.javatuples.Pair;
import tools.jackson.databind.node.ObjectNode;
import java.util.function.Function;
public interface ValueTransformer extends Function<Pair<Object, ObjectNode>, Object> {
}

View File

@ -0,0 +1,12 @@
package ru.kirillius.XCP.Data;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ValueTransformerDescriptor {
String transformerId() default "";
}

View File

@ -1,16 +1,14 @@
package ru.kirillius.XCP.Persistence; package ru.kirillius.XCP.Persistence;
import ru.kirillius.XCP.Data.DataTransferProtocol; import ru.kirillius.XCP.Data.ValueTransformationChain;
import ru.kirillius.XCP.Data.ValueModifierSettings;
import java.util.List;
public interface IOEntity extends NodeEntity { public interface IOEntity extends NodeEntity {
List<ValueModifierSettings> getModifiers(); ValueTransformationChain getTransformationChain();
void setModifiers(List<ValueModifierSettings> modifiers); void setTransformationChain(ValueTransformationChain transformationChain);
Class<? extends DataTransferProtocol> getProtocol(); String getAdapterId();
void setAdapterId(String adapterId);
void setProtocol(Class<? extends DataTransferProtocol> protocol);
} }

View File

@ -9,5 +9,7 @@ import java.util.Collection;
public interface NodeRepository<E extends NodeEntity> extends Repository<E> { public interface NodeRepository<E extends NodeEntity> extends Repository<E> {
StreamHandler<E> getByGroup(Group group); StreamHandler<E> getByGroup(Group group);
StreamHandler<E> getByTags(Collection<Tag> tags); StreamHandler<E> getByAllTags(Collection<Tag> tags);
StreamHandler<E> getByAnyTag(Collection<Tag> tags);
} }

View File

@ -1,8 +1,8 @@
package ru.kirillius.XCP.Persistence.Repositories; package ru.kirillius.XCP.Persistence.Repositories;
import ru.kirillius.XCP.Persistence.Entities.Input; import ru.kirillius.XCP.Persistence.Entities.Output;
import ru.kirillius.XCP.Persistence.NodeRepository; import ru.kirillius.XCP.Persistence.NodeRepository;
public interface OutputRepository extends NodeRepository<Input> { public interface OutputRepository extends NodeRepository<Output> {
} }

View File

@ -9,9 +9,11 @@ import java.util.Collection;
import java.util.stream.Stream; import java.util.stream.Stream;
public interface TagRepository extends Repository<Tag> { public interface TagRepository extends Repository<Tag> {
Tag getByName(String name); Tag getByNameOrCreate(String name);
ResourceHandler<Stream<Tag>> getByNames(Collection<String> names); TagCollection getByNamesOrCreate(Collection<String> names);
TagCollection createCollection(); TagCollection createCollection();
TagCollection createCollection(ResourceHandler<Stream<Tag>> handler);
} }

View File

@ -13,10 +13,6 @@ public class EntityReferenceSerializer extends StdSerializer<EntityReference> {
this.repositoryService = repositoryService; this.repositoryService = repositoryService;
} }
// protected EntityReferenceSerializer(Class<PersistenceEntity> t) {
// super(t);
// }
@Override @Override
public void serialize(EntityReference reference, JsonGenerator gen, SerializationContext provider) throws JacksonException { public void serialize(EntityReference reference, JsonGenerator gen, SerializationContext provider) throws JacksonException {
var value = reference.get(); var value = reference.get();

View File

@ -8,7 +8,6 @@ import tools.jackson.databind.module.SimpleSerializers;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
class PersistenceSerializationModule extends JacksonModule { class PersistenceSerializationModule extends JacksonModule {
public PersistenceSerializationModule(RepositoryServiceImpl repositoryService) { public PersistenceSerializationModule(RepositoryServiceImpl repositoryService) {
@ -31,12 +30,12 @@ class PersistenceSerializationModule extends JacksonModule {
@Override @Override
public void setupModule(SetupContext context) { public void setupModule(SetupContext context) {
context.addSerializers(new SimpleSerializers(List.of( context.addSerializers(new SimpleSerializers(List.of(
new EntityReferenceSerializer(repositoryService) new EntityReferenceSerializer(repositoryService),
// new TagSetSerializer(repositoryService) new TagCollectionSerializer()
))); )));
context.addDeserializers(new SimpleDeserializers(Map.of( context.addDeserializers(new SimpleDeserializers(Map.of(
EntityReference.class, new EntityReferenceDeserializer(repositoryService) EntityReference.class, new EntityReferenceDeserializer(repositoryService),
// Set.class, new TagSetDeserializer(repositoryService) TagCollection.class, new TagCollectionDeserializer(repositoryService)
))); )));
} }
} }

View File

@ -0,0 +1,21 @@
package ru.kirillius.XCP.Persistence;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.*;
import ru.kirillius.XCP.Data.PollSettings;
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class PollSettingsImpl implements PollSettings {
@JsonProperty
private long pollInterval = 300;
@JsonProperty
private boolean interruptable = true;
@JsonProperty
private boolean rateMeasurement = false;
@JsonProperty
private boolean enabled = true;
}

View File

@ -20,22 +20,37 @@ public abstract class AbstractNodeRepository<E extends NodeEntity> extends Abstr
@Override @Override
public StreamHandler<E> getByGroup(Group group) { public StreamHandler<E> getByGroup(Group group) {
return search("WHERE group = ?1", List.of(group)); return search("WHERE parent = ?1", List.of(group));
} }
@SuppressWarnings("unchecked")
@Override @Override
public StreamHandler<E> getByTags(Collection<Tag> tags) { public StreamHandler<E> getByAllTags(Collection<Tag> tags) {
var hql = "SELECT n FROM " + tableName + " n JOIN n.tags t " + var hql = "SELECT n FROM " + tableName + " n JOIN n.tags t " +
"WHERE t.name IN :tagNames " + "WHERE t IN :tags " +
"GROUP BY n " + "GROUP BY n.id " +
"HAVING COUNT(DISTINCT t.name) = :tagCount"; "HAVING COUNT(DISTINCT t.id) = :tagCount";
var session = repositoryService.openSession(); var session = repositoryService.openSession();
var transaction = session.beginTransaction(); var transaction = session.beginTransaction();
var query = (Query<E>) session.createQuery(hql, entityImplementationClass); var query = (Query<E>) session.createQuery(hql, entityImplementationClass);
query.setParameter("tagNames", tags); query.setParameter("tags", tags);
query.setParameter("tagCount", tags.size()); query.setParameter("tagCount", (long) tags.size());
return new ResourceHandlerImpl<>(query, transaction, session);
}
@SuppressWarnings("unchecked")
@Override
public StreamHandler<E> getByAnyTag(Collection<Tag> tags) {
var hql = "SELECT DISTINCT n FROM " + tableName + " n " +
"JOIN n.tags t " +
"WHERE t.name IN :tagNames";
var session = repositoryService.openSession();
var transaction = session.beginTransaction();
var query = (Query<E>) session.createQuery(hql, entityImplementationClass);
query.setParameter("tagNames", tags.stream().map(Tag::getName).toList());
return new ResourceHandlerImpl<>(query, transaction, session); return new ResourceHandlerImpl<>(query, transaction, session);
} }
} }

View File

@ -6,9 +6,8 @@ import jakarta.persistence.*;
import lombok.*; import lombok.*;
import org.hibernate.annotations.UuidGenerator; import org.hibernate.annotations.UuidGenerator;
import ru.kirillius.XCP.Commons.StreamHandler; import ru.kirillius.XCP.Commons.StreamHandler;
import ru.kirillius.XCP.Persistence.*;
import ru.kirillius.XCP.Persistence.Entities.Group; import ru.kirillius.XCP.Persistence.Entities.Group;
import ru.kirillius.XCP.Persistence.Entities.Tag; import ru.kirillius.XCP.Persistence.*;
import ru.kirillius.XCP.Serialization.SerializationUtils; import ru.kirillius.XCP.Serialization.SerializationUtils;
import tools.jackson.databind.node.ObjectNode; import tools.jackson.databind.node.ObjectNode;
@ -145,13 +144,13 @@ public class GroupRepositoryImpl extends AbstractNodeRepository<Group> implement
@Override @Override
public void setTags(Set<Tag> tags) { public void setTags(TagCollection tags) {
this.tags = tags.stream().map(t -> (TagRepositoryImpl.TagEntity) t).collect(Collectors.toSet()); this.tags = tags.stream().map(t -> (TagRepositoryImpl.TagEntity) t).collect(Collectors.toSet());
} }
@Override @Override
public Set<Tag> getTags() { public TagCollection getTags() {
return new HashSet<>(tags); return new TagCollectionImpl(tags);
} }
@Column(nullable = false) @Column(nullable = false)

View File

@ -0,0 +1,160 @@
package ru.kirillius.XCP.Persistence.Repositories;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.UuidGenerator;
import ru.kirillius.XCP.Data.PollSettings;
import ru.kirillius.XCP.Data.ValueTransformationChain;
import ru.kirillius.XCP.Persistence.Entities.Group;
import ru.kirillius.XCP.Persistence.Entities.Input;
import ru.kirillius.XCP.Persistence.*;
import ru.kirillius.XCP.Serialization.PollSettingsConverter;
import ru.kirillius.XCP.Serialization.SerializationUtils;
import ru.kirillius.XCP.Serialization.ValueTransformationChainConverter;
import tools.jackson.databind.annotation.JsonDeserialize;
import tools.jackson.databind.node.ObjectNode;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
@EntityImplementation(InputRepositoryImpl.InputEntity.class)
public class InputRepositoryImpl extends AbstractNodeRepository<Input> implements InputRepository {
public InputRepositoryImpl(RepositoryServiceImpl repositoryService) {
super(repositoryService);
}
@Override
public void store(Input entity) {
if (entity != null && entity.getParent() == null) {
throw new IllegalStateException("Saving inputs without group is prohibited");
}
super.store(entity);
}
@Entity
@Table(name = "Inputs")
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public static class InputEntity implements Input {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@JsonProperty
private long id = 0;
@JsonProperty
@Column(unique = true, nullable = false)
@UuidGenerator
private UUID uuid;
@Column(nullable = false)
@JsonProperty
private String name = "";
@Column(nullable = false)
@JsonProperty
@Getter
@Setter
private boolean protectedEntity;
@Column(nullable = false)
@JsonProperty
@Getter
@Setter
private boolean enabled;
@ManyToOne(fetch = FetchType.EAGER)
@JsonIgnore
private GroupRepositoryImpl.GroupEntity parent;
@JsonProperty("parent")
EntityReference getParentReference() {
return new EntityReference(getParent());
}
@JsonProperty("parent")
void setParentReference(EntityReference entityReference) {
parent = entityReference == null ? null : (GroupRepositoryImpl.GroupEntity) entityReference.get();
}
public Group getParent() {
return parent;
}
public void setParent(Group parent) {
this.parent = (GroupRepositoryImpl.GroupEntity) parent;
}
@Override
public void setTags(TagCollection tags) {
this.tags = tags.stream().map(t -> (TagRepositoryImpl.TagEntity) t).collect(Collectors.toSet());
}
@Override
public TagCollection getTags() {
return new TagCollectionImpl(tags);
}
@Column(nullable = false)
@JsonProperty
@Getter
@Setter
private ObjectNode properties = SerializationUtils.EmptyObject();
@JsonIgnore
@ManyToMany(fetch = FetchType.EAGER)
private Set<TagRepositoryImpl.TagEntity> tags = new HashSet<>();
@Override
public boolean equals(Object o) {
if (!(o instanceof InputEntity that)) return false;
return Objects.equals(uuid, that.uuid);
}
@Override
public int hashCode() {
return Objects.hashCode(uuid);
}
@Override
public PollSettings getPollSettings() {
return pollSettings;
}
@Override
public void setPollSettings(PollSettings pollSettings) {
this.pollSettings = (PollSettingsImpl) pollSettings;
}
@Column(nullable = false)
@JsonProperty
@Convert(converter = PollSettingsConverter.class)
@JsonDeserialize(as = PollSettingsImpl.class)
private PollSettingsImpl pollSettings = new PollSettingsImpl();
@Column(nullable = false)
@JsonProperty
@Getter
@Setter
private String adapterId = "";
@Getter
@Setter
@JsonProperty
@Convert(converter = ValueTransformationChainConverter.class)
@Column(nullable = false)
private ValueTransformationChain transformationChain = new ValueTransformationChain();
}
}

View File

@ -0,0 +1,141 @@
package ru.kirillius.XCP.Persistence.Repositories;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.UuidGenerator;
import ru.kirillius.XCP.Data.ValueTransformationChain;
import ru.kirillius.XCP.Persistence.Entities.Group;
import ru.kirillius.XCP.Persistence.Entities.Output;
import ru.kirillius.XCP.Persistence.*;
import ru.kirillius.XCP.Serialization.SerializationUtils;
import ru.kirillius.XCP.Serialization.ValueTransformationChainConverter;
import tools.jackson.databind.node.ObjectNode;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
@EntityImplementation(OutputRepositoryImpl.OutputEntity.class)
public class OutputRepositoryImpl extends AbstractNodeRepository<Output> implements OutputRepository {
public OutputRepositoryImpl(RepositoryServiceImpl repositoryService) {
super(repositoryService);
}
@Override
public void store(Output entity) {
if (entity != null && entity.getParent() == null) {
throw new IllegalStateException("Saving outputs without group is prohibited");
}
super.store(entity);
}
@Entity
@Table(name = "Outputs")
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public static class OutputEntity implements Output {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@JsonProperty
private long id = 0;
@JsonProperty
@Column(unique = true, nullable = false)
@UuidGenerator
private UUID uuid;
@Column(nullable = false)
@JsonProperty
private String name = "";
@Column(nullable = false)
@JsonProperty
@Getter
@Setter
private boolean protectedEntity;
@Column(nullable = false)
@JsonProperty
@Getter
@Setter
private boolean enabled;
@ManyToOne(fetch = FetchType.EAGER)
@JsonIgnore
private GroupRepositoryImpl.GroupEntity parent;
@JsonProperty("parent")
EntityReference getParentReference() {
return new EntityReference(getParent());
}
@JsonProperty("parent")
void setParentReference(EntityReference entityReference) {
parent = entityReference == null ? null : (GroupRepositoryImpl.GroupEntity) entityReference.get();
}
public Group getParent() {
return parent;
}
public void setParent(Group parent) {
this.parent = (GroupRepositoryImpl.GroupEntity) parent;
}
@Override
public void setTags(TagCollection tags) {
this.tags = tags.stream().map(t -> (TagRepositoryImpl.TagEntity) t).collect(Collectors.toSet());
}
@Override
public TagCollection getTags() {
return new TagCollectionImpl(tags);
}
@Column(nullable = false)
@JsonProperty
@Getter
@Setter
private ObjectNode properties = SerializationUtils.EmptyObject();
@JsonIgnore
@ManyToMany(fetch = FetchType.EAGER)
private Set<TagRepositoryImpl.TagEntity> tags = new HashSet<>();
@Override
public boolean equals(Object o) {
if (!(o instanceof OutputEntity that)) return false;
return Objects.equals(uuid, that.uuid);
}
@Override
public int hashCode() {
return Objects.hashCode(uuid);
}
@Column(nullable = false)
@JsonProperty
@Getter
@Setter
private String adapterId = "";
@Getter
@Setter
@JsonProperty
@Convert(converter = ValueTransformationChainConverter.class)
@Column(nullable = false)
private ValueTransformationChain transformationChain = new ValueTransformationChain();
}
}

View File

@ -23,9 +23,8 @@ public class TagRepositoryImpl extends AbstractRepository<Tag> implements TagRep
super(repositoryService); super(repositoryService);
} }
@Override @Override
public Tag getByName(String name) { public Tag getByNameOrCreate(String name) {
try (var handler = buildQueryParametrized("WHERE name = ?1", name)) { try (var handler = buildQueryParametrized("WHERE name = ?1", name)) {
var result = handler.get().findFirst(); var result = handler.get().findFirst();
if (result.isPresent()) { if (result.isPresent()) {
@ -43,8 +42,29 @@ public class TagRepositoryImpl extends AbstractRepository<Tag> implements TagRep
} }
@Override @Override
public ResourceHandler<Stream<Tag>> getByNames(Collection<String> names) { public TagCollection getByNamesOrCreate(Collection<String> names) {
return search("where name IN (?1)", List.of(names)); var tags = new TagCollectionImpl();
try (var handler = search("where name IN (?1)", List.of(names))) {
tags.addAll(handler.get().toList());
} catch (IOException e) {
throw new RuntimeException("Unable to find tags by names " + names, e);
}
if (tags.size() != names.size()) {
var foundNames = tags.stream().map(Tag::getName).toList();
names.forEach(tagName -> {
if(!foundNames.contains(tagName)) {
var tag = create();
tag.setName(tagName);
store(tag);
tags.add(tag);
}
});
}
return tags;
} }
@Override @Override
@ -52,6 +72,11 @@ public class TagRepositoryImpl extends AbstractRepository<Tag> implements TagRep
return new TagCollectionImpl(); return new TagCollectionImpl();
} }
@Override
public TagCollection createCollection(ResourceHandler<Stream<Tag>> handler) {
return new TagCollectionImpl(handler.get().toList());
}
@Entity @Entity
@Table(name = "Tags") @Table(name = "Tags")
@Builder @Builder
@ -112,6 +137,5 @@ public class TagRepositoryImpl extends AbstractRepository<Tag> implements TagRep
return Objects.hashCode(name); return Objects.hashCode(name);
} }
} }
} }

View File

@ -1,37 +1,29 @@
package ru.kirillius.XCP.Persistence; package ru.kirillius.XCP.Persistence;
import ru.kirillius.XCP.Persistence.Entities.Tag;
import ru.kirillius.XCP.Persistence.Repositories.TagRepository; import ru.kirillius.XCP.Persistence.Repositories.TagRepository;
import tools.jackson.core.JacksonException; import tools.jackson.core.JacksonException;
import tools.jackson.core.JsonGenerator;
import tools.jackson.core.JsonParser; import tools.jackson.core.JsonParser;
import tools.jackson.core.JsonToken;
import tools.jackson.databind.DeserializationContext; import tools.jackson.databind.DeserializationContext;
import tools.jackson.databind.SerializationContext;
import tools.jackson.databind.deser.std.StdDeserializer; import tools.jackson.databind.deser.std.StdDeserializer;
import tools.jackson.databind.ser.std.StdSerializer;
import java.util.HashSet; public class TagCollectionDeserializer extends StdDeserializer<TagCollection> {
import java.util.Set;
public class TagSetDeserializer extends StdDeserializer<Set<Tag>> {
private final RepositoryServiceImpl repositoryService; private final RepositoryServiceImpl repositoryService;
public TagSetDeserializer(RepositoryServiceImpl repositoryService) { public TagCollectionDeserializer(RepositoryServiceImpl repositoryService) {
super(Set.class); super(TagCollection.class);
this.repositoryService = repositoryService; this.repositoryService = repositoryService;
} }
@Override @Override
public Set<Tag> deserialize(JsonParser p, DeserializationContext ctxt) throws JacksonException { public TagCollection deserialize(JsonParser p, DeserializationContext ctxt) throws JacksonException {
var result = new HashSet<Tag>(); var result = new TagCollectionImpl();
var tagNames = p.readValueAs(String[].class); var tagNames = p.readValueAs(String[].class);
var repository = repositoryService.getRepository(TagRepository.class); var repository = repositoryService.getRepository(TagRepository.class);
for (var name : tagNames) { for (var name : tagNames) {
var tag = repository.getByName(name); var tag = repository.getByNameOrCreate(name);
result.add(tag); result.add(tag);
} }
return result; return result;

View File

@ -2,7 +2,14 @@ package ru.kirillius.XCP.Persistence;
import ru.kirillius.XCP.Persistence.Entities.Tag; import ru.kirillius.XCP.Persistence.Entities.Tag;
import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
public class TagCollectionImpl extends HashSet<Tag> implements TagCollection { public class TagCollectionImpl extends HashSet<Tag> implements TagCollection {
public TagCollectionImpl() {
}
public TagCollectionImpl(Collection<? extends Tag> c) {
super(c);
}
} }

View File

@ -0,0 +1,24 @@
package ru.kirillius.XCP.Persistence;
import tools.jackson.core.JacksonException;
import tools.jackson.core.JsonGenerator;
import tools.jackson.databind.SerializationContext;
import tools.jackson.databind.ser.std.StdSerializer;
public class TagCollectionSerializer extends StdSerializer<TagCollection> {
public TagCollectionSerializer() {
super(TagCollection.class);
}
@Override
public void serialize(TagCollection set, JsonGenerator gen, SerializationContext provider) throws JacksonException {
if(set == null) {
gen.writeNull();
return;
}
gen.writeStartArray();
set.forEach(tag -> gen.writeString(tag.getName()));
gen.writeEndArray();
}
}

View File

@ -1,30 +0,0 @@
package ru.kirillius.XCP.Persistence;
import ru.kirillius.XCP.Persistence.Entities.Tag;
import tools.jackson.core.JacksonException;
import tools.jackson.core.JsonGenerator;
import tools.jackson.databind.JavaType;
import tools.jackson.databind.SerializationContext;
import tools.jackson.databind.ser.std.StdSerializer;
import java.util.Set;
public class TagSetSerializer extends StdSerializer<Set<Tag>> {
private final RepositoryServiceImpl repositoryService;
public TagSetSerializer(RepositoryServiceImpl repositoryService) {
super(Set.class);
this.repositoryService = repositoryService;
}
@Override
public void serialize(Set<Tag> set, JsonGenerator gen, SerializationContext provider) throws JacksonException {
if(set == null) {
gen.writeNull();
return;
}
gen.writeStartArray();
set.forEach(tag -> gen.writeString(tag.getName()));
gen.writeEndArray();
}
}

View File

@ -0,0 +1,19 @@
package ru.kirillius.XCP.Serialization;
import jakarta.persistence.AttributeConverter;
import ru.kirillius.XCP.Persistence.PollSettingsImpl;
import tools.jackson.databind.ObjectMapper;
public class PollSettingsConverter implements AttributeConverter<PollSettingsImpl, String> {
private final static ObjectMapper mapper = new ObjectMapper();
@Override
public String convertToDatabaseColumn(PollSettingsImpl pollSettings) {
return mapper.writeValueAsString(pollSettings);
}
@Override
public PollSettingsImpl convertToEntityAttribute(String s) {
return mapper.readValue(s, PollSettingsImpl.class);
}
}

View File

@ -0,0 +1,19 @@
package ru.kirillius.XCP.Serialization;
import jakarta.persistence.AttributeConverter;
import ru.kirillius.XCP.Data.ValueTransformationChain;
import tools.jackson.databind.ObjectMapper;
public class ValueTransformationChainConverter implements AttributeConverter<ValueTransformationChain, String> {
private final static ObjectMapper mapper = new ObjectMapper();
@Override
public String convertToDatabaseColumn(ValueTransformationChain pollSettings) {
return mapper.writeValueAsString(pollSettings);
}
@Override
public ValueTransformationChain convertToEntityAttribute(String s) {
return mapper.readValue(s, ValueTransformationChain.class);
}
}

View File

@ -0,0 +1,115 @@
package ru.kirillius.XCP.Persistence.Repositories;
import org.junit.jupiter.api.Test;
import ru.kirillius.XCP.Persistence.NodeEntity;
import ru.kirillius.XCP.Persistence.NodeRepository;
import java.io.IOException;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
abstract class GenericNodeRepositoryTest<E extends NodeEntity, R extends AbstractNodeRepository<E>> extends GenericRepositoryTest<E, R> {
@Test
void getByGroup() throws IOException {
try (var service = spawnRepositoryService()) {
@SuppressWarnings("unchecked")
var repository = (NodeRepository<E>) service.getRepository(service.getRepositoryBaseType(repositoryClass));
var groupRepository = service.getRepository(GroupRepository.class);
var root = groupRepository.create();
groupRepository.store(root);
var firstChildGroup = groupRepository.create();
firstChildGroup.setParent(root);
groupRepository.store(firstChildGroup);
var secondChildGroup = groupRepository.create();
secondChildGroup.setParent(root);
groupRepository.store(secondChildGroup);
var firstChild = repository.create();
modify(firstChild, service);
firstChild.setParent(firstChildGroup);
repository.store(firstChild);
var secondChild = repository.create();
modify(secondChild, service);
secondChild.setParent(secondChildGroup);
repository.store(secondChild);
try (var handler = repository.getByGroup(firstChildGroup)) {
var found = handler.get().findFirst().orElse(null);
assertThat(found).isNotNull().isEqualTo(firstChild);
}
try (var handler = repository.getByGroup(secondChildGroup)) {
var found = handler.get().findFirst().orElse(null);
assertThat(found).isNotNull().isEqualTo(secondChild);
}
}
}
@Test
void testByTags() throws IOException {
try (var service = spawnRepositoryService()) {
var tagRepository = service.getRepository(TagRepository.class);
@SuppressWarnings("unchecked")
var repository = (NodeRepository<E>) service.getRepository(service.getRepositoryBaseType(repositoryClass));
var first = repository.create();
modify(first, service);
first.setTags(tagRepository.getByNamesOrCreate(List.of(
"first",
"foo",
"bar"
)));
repository.store(first);
var second = repository.create();
modify(second, service);
second.setTags(tagRepository.getByNamesOrCreate(List.of(
"second",
"foo",
"third"
)));
repository.store(second);
var third = repository.create();
modify(third, service);
third.setTags(tagRepository.getByNamesOrCreate(List.of(
"omg",
"third",
"yabba"
)));
repository.store(third);
try (var handler = repository.getByAllTags(tagRepository.getByNamesOrCreate(List.of("first")))) {
assertThat(handler.get().toList()).containsExactlyInAnyOrder(first).doesNotContain(second, third);
}
try (var handler = repository.getByAllTags(tagRepository.getByNamesOrCreate(List.of("foo")))) {
assertThat(handler.get().toList()).containsExactlyInAnyOrder(first, second).doesNotContain(third);
}
try (var handler = repository.getByAllTags(tagRepository.getByNamesOrCreate(List.of("third")))) {
assertThat(handler.get().toList()).containsExactlyInAnyOrder(third, second).doesNotContain(first);
}
try (var handler = repository.getByAnyTag(tagRepository.getByNamesOrCreate(List.of("first", "second", "third")))) {
assertThat(handler.get().toList()).containsExactlyInAnyOrder(first, second, third);
}
try (var handler = repository.getByAnyTag(tagRepository.getByNamesOrCreate(List.of("omg", "yabba")))) {
assertThat(handler.get().toList()).containsExactlyInAnyOrder(third).doesNotContain(second, first);
}
try (var handler = repository.getByAllTags(tagRepository.getByNamesOrCreate(List.of("shrash")))) {
assertThat(handler.get().toList()).isEmpty();
}
}
}
}

View File

@ -52,6 +52,7 @@ abstract class GenericRepositoryTest<E extends PersistenceEntity, R extends Abst
try (var service = spawnRepositoryService()) { try (var service = spawnRepositoryService()) {
var repository = service.getRepositoryForEntity(entityClass); var repository = service.getRepositoryForEntity(entityClass);
var entity = repository.create(); var entity = repository.create();
modify(entity, service);
repository.store(entity); repository.store(entity);
assertThat(entity.getId()).isNotZero(); assertThat(entity.getId()).isNotZero();
} }
@ -62,6 +63,7 @@ abstract class GenericRepositoryTest<E extends PersistenceEntity, R extends Abst
try (var service = spawnRepositoryService()) { try (var service = spawnRepositoryService()) {
var repository = service.getRepositoryForEntity(entityClass); var repository = service.getRepositoryForEntity(entityClass);
var entity = repository.create(); var entity = repository.create();
modify(entity, service);
repository.store(entity); repository.store(entity);
var loaded = repository.load(entity.getId()); var loaded = repository.load(entity.getId());
assertThat(loaded).isNotNull().isEqualTo(entity); assertThat(loaded).isNotNull().isEqualTo(entity);
@ -76,8 +78,9 @@ abstract class GenericRepositoryTest<E extends PersistenceEntity, R extends Abst
try (var service = spawnRepositoryService()) { try (var service = spawnRepositoryService()) {
var repository = service.getRepositoryForEntity(entityClass); var repository = service.getRepositoryForEntity(entityClass);
var entity = repository.create(); var entity = repository.create();
modify(entity, service);
repository.store(entity); repository.store(entity);
modify(entity,service); modify(entity, service);
repository.store(entity); repository.store(entity);
var loaded = repository.load(entity.getId()); var loaded = repository.load(entity.getId());
assertThat(loaded).isNotNull().isEqualTo(entity); assertThat(loaded).isNotNull().isEqualTo(entity);
@ -89,6 +92,7 @@ abstract class GenericRepositoryTest<E extends PersistenceEntity, R extends Abst
try (var service = spawnRepositoryService()) { try (var service = spawnRepositoryService()) {
var repository = service.getRepositoryForEntity(entityClass); var repository = service.getRepositoryForEntity(entityClass);
var entity = repository.create(); var entity = repository.create();
modify(entity, service);
repository.store(entity); repository.store(entity);
assertThat(repository.getCount()).isEqualTo(1); assertThat(repository.getCount()).isEqualTo(1);
repository.remove(entity); repository.remove(entity);
@ -101,6 +105,7 @@ abstract class GenericRepositoryTest<E extends PersistenceEntity, R extends Abst
try (var service = spawnRepositoryService()) { try (var service = spawnRepositoryService()) {
var repository = service.getRepositoryForEntity(entityClass); var repository = service.getRepositoryForEntity(entityClass);
var entity = repository.create(); var entity = repository.create();
modify(entity, service);
repository.store(entity); repository.store(entity);
var serialized = repository.serialize(entity); var serialized = repository.serialize(entity);
@ -109,7 +114,7 @@ abstract class GenericRepositoryTest<E extends PersistenceEntity, R extends Abst
assertThat(deserialized).isNotNull().isEqualTo(entity); assertThat(deserialized).isNotNull().isEqualTo(entity);
var anotherEntity = repository.create(); var anotherEntity = repository.create();
modify(anotherEntity,service); modify(anotherEntity, service);
repository.store(anotherEntity); repository.store(anotherEntity);
var anotherSerialized = repository.serialize(anotherEntity); var anotherSerialized = repository.serialize(anotherEntity);

View File

@ -1,22 +1,20 @@
package ru.kirillius.XCP.Persistence.Repositories; package ru.kirillius.XCP.Persistence.Repositories;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import ru.kirillius.XCP.Commons.StreamHandler;
import ru.kirillius.XCP.Persistence.Entities.Group; import ru.kirillius.XCP.Persistence.Entities.Group;
import ru.kirillius.XCP.Persistence.Entities.Tag; import ru.kirillius.XCP.Persistence.Entities.Tag;
import ru.kirillius.XCP.Persistence.Repository;
import ru.kirillius.XCP.Persistence.RepositoryService; import ru.kirillius.XCP.Persistence.RepositoryService;
import ru.kirillius.XCP.Persistence.TagCollectionImpl;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static ru.kirillius.XCP.Persistence.TestEnvironment.instantiateTestService; import static ru.kirillius.XCP.Persistence.TestEnvironment.instantiateTestService;
class GroupRepositoryImplTest extends GenericRepositoryTest<Group, GroupRepositoryImpl> { class GroupRepositoryImplTest extends GenericNodeRepositoryTest<Group, GroupRepositoryImpl> {
@Override @Override
protected RepositoryService spawnRepositoryService() { protected RepositoryService spawnRepositoryService() {
var service = instantiateTestService(List.of(repositoryClass, TagRepositoryImpl.class)); var service = instantiateTestService(List.of(repositoryClass, TagRepositoryImpl.class));
@ -29,6 +27,7 @@ class GroupRepositoryImplTest extends GenericRepositoryTest<Group, GroupReposito
return service; return service;
} }
@Test @Test
void getChildrenOf() throws IOException { void getChildrenOf() throws IOException {
try (var service = spawnRepositoryService()) { try (var service = spawnRepositoryService()) {
@ -139,7 +138,7 @@ class GroupRepositoryImplTest extends GenericRepositoryTest<Group, GroupReposito
} }
try (var handler = service.getRepositoryForEntity(Tag.class).loadAll()) { try (var handler = service.getRepositoryForEntity(Tag.class).loadAll()) {
entity.setTags(new HashSet<>(handler.get().toList())); entity.setTags(new TagCollectionImpl(handler.get().toList()));
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }

View File

@ -0,0 +1,47 @@
package ru.kirillius.XCP.Persistence.Repositories;
import ru.kirillius.XCP.Persistence.Entities.Input;
import ru.kirillius.XCP.Persistence.Entities.Tag;
import ru.kirillius.XCP.Persistence.RepositoryService;
import ru.kirillius.XCP.Persistence.TagCollectionImpl;
import java.io.IOException;
import java.util.List;
import java.util.UUID;
import static ru.kirillius.XCP.Persistence.TestEnvironment.instantiateTestService;
class InputRepositoryImplTest extends GenericNodeRepositoryTest<Input, InputRepositoryImpl> {
@Override
protected RepositoryService spawnRepositoryService() {
var service = instantiateTestService(List.of(repositoryClass, TagRepositoryImpl.class, GroupRepositoryImpl.class));
for (var i = 0; i < 5; i++) {
var tagRepository = service.getRepositoryForEntity(Tag.class);
var tag = tagRepository.create();
tag.setName("tag" + i);
tagRepository.store(tag);
}
return service;
}
@Override
protected void modify(Input entity, RepositoryService service) {
var groupRepository = service.getRepository(GroupRepository.class);
var root = groupRepository.getRoot();
if (root == null) {
root = groupRepository.create();
groupRepository.store(root);
}
entity.setParent(root);
entity.setName(UUID.randomUUID().toString());
//var inputRepository = service.getRepository(InputRepository.class);
try (var handler = service.getRepositoryForEntity(Tag.class).loadAll()) {
entity.setTags(new TagCollectionImpl(handler.get().toList()));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

View File

@ -0,0 +1,47 @@
package ru.kirillius.XCP.Persistence.Repositories;
import ru.kirillius.XCP.Persistence.Entities.Output;
import ru.kirillius.XCP.Persistence.Entities.Tag;
import ru.kirillius.XCP.Persistence.RepositoryService;
import ru.kirillius.XCP.Persistence.TagCollectionImpl;
import java.io.IOException;
import java.util.List;
import java.util.UUID;
import static ru.kirillius.XCP.Persistence.TestEnvironment.instantiateTestService;
class OutputRepositoryImplTest extends GenericNodeRepositoryTest<Output, OutputRepositoryImpl> {
@Override
protected RepositoryService spawnRepositoryService() {
var service = instantiateTestService(List.of(repositoryClass, TagRepositoryImpl.class, GroupRepositoryImpl.class));
for (var i = 0; i < 5; i++) {
var tagRepository = service.getRepositoryForEntity(Tag.class);
var tag = tagRepository.create();
tag.setName("tag" + i);
tagRepository.store(tag);
}
return service;
}
@Override
protected void modify(Output entity, RepositoryService service) {
var groupRepository = service.getRepository(GroupRepository.class);
var root = groupRepository.getRoot();
if (root == null) {
root = groupRepository.create();
groupRepository.store(root);
}
entity.setParent(root);
entity.setName(UUID.randomUUID().toString());
//var inputRepository = service.getRepository(InputRepository.class);
try (var handler = service.getRepositoryForEntity(Tag.class).loadAll()) {
entity.setTags(new TagCollectionImpl(handler.get().toList()));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

View File

@ -5,22 +5,68 @@ import ru.kirillius.XCP.Persistence.Entities.Tag;
import ru.kirillius.XCP.Persistence.RepositoryService; import ru.kirillius.XCP.Persistence.RepositoryService;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
class TagRepositoryImplTest extends GenericRepositoryTest<Tag, TagRepositoryImpl> { class TagRepositoryImplTest extends GenericRepositoryTest<Tag, TagRepositoryImpl> {
@Test @Test
void testGetByName() throws IOException { void testGetByNameOrCreate() throws IOException {
try (var service = spawnRepositoryService()) { try (var service = spawnRepositoryService()) {
var repository = service.getRepository(TagRepository.class); var repository = service.getRepository(TagRepository.class);
var tag = repository.create(); var tag = repository.create();
tag.setName("test"); tag.setName("test");
repository.store(tag); repository.store(tag);
var found = repository.getByName(tag.getName()); var found = repository.getByNameOrCreate(tag.getName());
assertThat(found).isEqualTo(tag); assertThat(found).isEqualTo(tag);
} }
} }
@Test
public void testGetByNamesOrCreateAsCollectionOrCreate() throws IOException {
try (var service = spawnRepositoryService()) {
var repository = service.getRepository(TagRepository.class);
var tags = new ArrayList<Tag>();
for (var i = 0; i < 10; i++) {
var tag = repository.create();
tag.setName("test" + i);
repository.store(tag);
tags.add(tag);
}
var tagNames = tags.stream().map(Tag::getName).toList();
assertThat(repository.getByNamesOrCreate(tagNames)).containsExactlyInAnyOrderElementsOf(tags);
}
}
@Test
public void testCreateCollection() throws IOException {
try (var service = spawnRepositoryService()) {
var repository = service.getRepository(TagRepository.class);
var tags = new ArrayList<Tag>();
for (var i = 0; i < 10; i++) {
var tag = repository.create();
tag.setName("test" + i);
repository.store(tag);
tags.add(tag);
}
var emptyCollection = repository.createCollection();
assertThat(emptyCollection).isNotNull().isEmpty();
try (var handler = repository.loadAll()) {
var collection = repository.createCollection(handler);
assertThat(collection).containsExactlyInAnyOrderElementsOf(tags);
}
}
}
@Override @Override
protected void modify(Tag entity, RepositoryService service) { protected void modify(Tag entity, RepositoryService service) {
entity.setName("test" + Math.random()); entity.setName("test" + Math.random());