package jnpf.limsService.provider;
|
|
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
|
import com.fasterxml.jackson.databind.annotation.JsonNaming;
|
import jnpf.exception.DataException;
|
import lombok.Data;
|
import lombok.Value;
|
|
import java.util.ArrayList;
|
import java.util.Collections;
|
import java.util.LinkedHashMap;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.Objects;
|
import java.util.stream.Collectors;
|
|
/** Business-facing data-item management; dataset and revision choreography stays behind this interface. */
|
public class LimsElnDataItemManager {
|
private final LimsElnSourceCatalog properties;
|
private final LimsElnDatasetCatalog paths;
|
private final LimsElnSourceMetadata metadata;
|
|
public LimsElnDataItemManager(LimsElnSourceCatalog properties, LimsElnDatasetCatalog paths,
|
LimsElnSourceMetadata metadata) {
|
this.properties = properties;
|
this.paths = paths;
|
this.metadata = metadata;
|
}
|
|
public List<DataItem> items() {
|
Map<String, LimsElnDatasetCatalog.Version> pathVersions = new LinkedHashMap<>();
|
for (LimsElnDatasetCatalog.Version version : paths.versions()) {
|
pathVersions.put(pathKey(version.getCode(), version.getRevision()), version);
|
}
|
Map<String, LimsElnDatasetCatalog.Draft> pathDefinitions = new LinkedHashMap<>();
|
Map<String, String> objectNames = objectNameMap();
|
Map<String, List<LimsElnSourceMetadata.Candidate>> fieldsByObject = new LinkedHashMap<>();
|
Map<String, Boolean> enabledStates = properties.enabledStates();
|
List<DataItem> result = new ArrayList<>();
|
for (LimsElnSourceCatalog.Version version : properties.versions()) {
|
LimsElnSourceCatalog.Definition definition = properties.definition(version);
|
LimsElnDatasetCatalog.Version path = pathVersions.get(
|
pathKey(definition.getDatasetCode(), definition.getDatasetRevision()));
|
if (path == null) throw new DataException("Source path version not found");
|
LimsElnDatasetCatalog.Draft pathDefinition = pathDefinitions.computeIfAbsent(
|
path.getId(), ignored -> paths.definition(path));
|
result.add(item(version, definition, path, pathDefinition, objectNames, fieldsByObject,
|
enabledStates.getOrDefault(version.getPropertyCode(), false)));
|
}
|
return result;
|
}
|
|
public List<SourcePath> sourcePaths() {
|
Map<String, String> objectNames = objectNameMap();
|
Map<String, List<LimsElnSourceMetadata.Candidate>> fieldsByObject = new LinkedHashMap<>();
|
List<SourcePath> result = new ArrayList<>();
|
for (LimsElnDatasetCatalog.Version version : paths.versions()) {
|
result.add(sourcePath(version, paths.definition(version), objectNames, fieldsByObject));
|
}
|
return result;
|
}
|
|
public SourcePath createSourcePath(SourcePathDraft command) {
|
if (command == null || command.getRootObjectId() == null || command.getTargetObjectId() == null
|
|| command.getOutputFieldId() == null || command.getSampleId() == null) {
|
throw new DataException("Source path and preview sample are required");
|
}
|
boolean related = !Objects.equals(command.getRootObjectId(), command.getTargetObjectId());
|
if (related && (command.getFromFieldId() == null || command.getTargetFieldId() == null)) {
|
throw new DataException("Source path relation is required");
|
}
|
LimsElnDatasetCatalog.Selection output = new LimsElnDatasetCatalog.Selection();
|
output.setFieldId(command.getOutputFieldId());
|
output.setStep(related ? 1 : 0);
|
|
LimsElnDatasetCatalog.Draft draft = new LimsElnDatasetCatalog.Draft();
|
draft.setCode(command.getCode());
|
draft.setDisplayName(command.getDisplayName());
|
draft.setRootObjectId(command.getRootObjectId());
|
draft.setFields(Collections.singletonMap(command.getOutputKey(), output));
|
if (related) {
|
LimsElnDatasetCatalog.Relation relation = new LimsElnDatasetCatalog.Relation();
|
relation.setFromStep(0);
|
relation.setFromFieldId(command.getFromFieldId());
|
relation.setObjectId(command.getTargetObjectId());
|
relation.setTargetFieldId(command.getTargetFieldId());
|
draft.setRelations(Collections.singletonList(relation));
|
}
|
return sourcePath(paths.createPublished(draft, command.getSampleId()));
|
}
|
|
public DataItem create(DataItemDraft command) {
|
SourceSelection source = source(command);
|
return item(properties.createDraft(draft(command, source)));
|
}
|
|
public DataItem save(String id, DataItemDraft command) {
|
if (command == null || command.getExpectedEditVersion() == null) {
|
throw new DataException("Expected edit version is required");
|
}
|
LimsElnSourceCatalog.Version current = properties.detail(id);
|
if (!current.getPropertyCode().equals(command.getPropertyCode())) {
|
throw new DataException("Source property code is immutable");
|
}
|
if (current.getEditVersion() != command.getExpectedEditVersion()) {
|
throw new LimsElnReadonlyResolver.SourceFailure("SOURCE_CONFLICT", "Source version has changed");
|
}
|
SourceSelection source = source(command);
|
LimsElnSourceCatalog.Draft draft = draft(command, source);
|
return item("PUBLISHED".equals(current.getStatus())
|
? properties.newVersion(id, draft)
|
: properties.updateDraft(id, command.getExpectedEditVersion(), draft));
|
}
|
|
public Preview preview(String id, String sampleId) {
|
LimsElnSourceCatalog.Preview result = properties.preview(id, sampleId);
|
Object value = result.getValues() == null || result.getValues().isEmpty() ? null : result.getValues().get(0);
|
return new Preview(value);
|
}
|
|
public DataItem publish(String id, int expectedEditVersion, String sampleId) {
|
return item(properties.publish(id, expectedEditVersion, sampleId));
|
}
|
|
private LimsElnSourceCatalog.Draft draft(DataItemDraft command, SourceSelection source) {
|
if (command == null) throw new DataException("Data item is required");
|
LimsElnSourceCatalog.Definition definition = new LimsElnSourceCatalog.Definition();
|
definition.setDatasetCode(source.path.getCode());
|
definition.setDatasetRevision(source.path.getRevision());
|
definition.setFieldId(command.getOutputKey());
|
return new LimsElnSourceCatalog.Draft(command.getPropertyCode(), command.getDisplayName(),
|
source.dataType, definition);
|
}
|
|
private SourceSelection source(DataItemDraft command) {
|
if (command == null || command.getSourcePathId() == null || command.getOutputKey() == null) {
|
throw new DataException("Source path and return content are required");
|
}
|
LimsElnDatasetCatalog.Version path = paths.detail(command.getSourcePathId());
|
if (!"PUBLISHED".equals(path.getStatus())) throw new DataException("Published source path required");
|
LimsElnDatasetCatalog.Draft definition = paths.definition(path);
|
LimsElnDatasetCatalog.Selection selection = definition.getFields().get(command.getOutputKey());
|
if (selection == null) throw new DataException("Source path return content not found");
|
LimsElnSourceMetadata.Candidate field = candidate(definition, selection);
|
String dataType = "DATE_ONLY".equals(selection.getFormat()) ? "DATE" : field.getDataType();
|
return new SourceSelection(path, dataType);
|
}
|
|
private DataItem item(LimsElnSourceCatalog.Version version) {
|
LimsElnSourceCatalog.Definition definition = properties.definition(version);
|
LimsElnDatasetCatalog.Version path = paths.version(definition.getDatasetCode(), definition.getDatasetRevision());
|
LimsElnDatasetCatalog.Draft pathDefinition = paths.definition(path);
|
return item(version, definition, path, pathDefinition, objectNameMap(), new LinkedHashMap<>(),
|
properties.enabled(version.getPropertyCode()));
|
}
|
|
private DataItem item(LimsElnSourceCatalog.Version version, LimsElnSourceCatalog.Definition definition,
|
LimsElnDatasetCatalog.Version path, LimsElnDatasetCatalog.Draft pathDefinition,
|
Map<String, String> objectNames,
|
Map<String, List<LimsElnSourceMetadata.Candidate>> fieldsByObject, boolean enabled) {
|
LimsElnDatasetCatalog.Selection selection = pathDefinition.getFields().get(definition.getFieldId());
|
if (selection == null) throw new DataException("Source path return content not found");
|
PathDescription description = describe(pathDefinition, definition.getFieldId(), objectNames, fieldsByObject);
|
return new DataItem(version.getId(), version.getPropertyCode(), version.getDisplayName(), version.getDataType(),
|
version.getStatus(), enabled, version.getRevision(),
|
version.getEditVersion(), path.getId(), definition.getFieldId(), description.path,
|
description.outputName, returnContent(selection), scope(pathDefinition), version.getPublishedAt(),
|
version.getPublishedBy());
|
}
|
|
private SourcePath sourcePath(LimsElnDatasetCatalog.Version version) {
|
LimsElnDatasetCatalog.Draft definition = paths.definition(version);
|
return sourcePath(version, definition, objectNameMap(), new LinkedHashMap<>());
|
}
|
|
private SourcePath sourcePath(LimsElnDatasetCatalog.Version version,
|
LimsElnDatasetCatalog.Draft definition, Map<String, String> objectNames,
|
Map<String, List<LimsElnSourceMetadata.Candidate>> fieldsByObject) {
|
List<PathOutput> outputs = definition.getFields().keySet().stream().map(key -> {
|
LimsElnDatasetCatalog.Selection selection = definition.getFields().get(key);
|
PathDescription description = describe(definition, key, objectNames, fieldsByObject);
|
LimsElnSourceMetadata.Candidate candidate = candidate(definition, selection, fieldsByObject);
|
String type = "DATE_ONLY".equals(selection.getFormat()) ? "DATE" : candidate.getDataType();
|
return new PathOutput(key, description.outputName, type, returnContent(selection), description.path);
|
}).collect(Collectors.toList());
|
String pathLabel = outputs.isEmpty() ? objectNames(definition, objectNames).stream().collect(Collectors.joining(" > "))
|
: outputs.get(0).getPath();
|
return new SourcePath(version.getId(), version.getCode(), version.getDisplayName(), version.getStatus(),
|
version.getRevision(), version.getEditVersion(), pathLabel, scope(definition), outputs,
|
version.getPublishedAt(), version.getPublishedBy());
|
}
|
|
private PathDescription describe(LimsElnDatasetCatalog.Draft definition, String outputKey,
|
Map<String, String> objectNames,
|
Map<String, List<LimsElnSourceMetadata.Candidate>> fieldsByObject) {
|
LimsElnDatasetCatalog.Selection selection = definition.getFields().get(outputKey);
|
if (selection == null) throw new DataException("Source path return content not found");
|
List<String> objects = objectNames(definition, objectNames);
|
if (selection.getStep() < 0 || selection.getStep() >= objects.size()) {
|
throw new DataException("Invalid source path output");
|
}
|
LimsElnSourceMetadata.Candidate field = candidate(definition, selection, fieldsByObject);
|
List<String> segments = new ArrayList<>(objects.subList(0, selection.getStep() + 1));
|
segments.add(field.getDisplayName());
|
return new PathDescription(String.join(" > ", segments), field.getDisplayName());
|
}
|
|
private Map<String, String> objectNameMap() {
|
return metadata.objects().stream().collect(Collectors.toMap(
|
LimsElnSourceMetadata.BusinessObject::getId,
|
LimsElnSourceMetadata.BusinessObject::getDisplayName,
|
(left, right) -> left,
|
LinkedHashMap::new));
|
}
|
|
private List<String> objectNames(LimsElnDatasetCatalog.Draft definition, Map<String, String> names) {
|
List<String> result = new ArrayList<>();
|
result.add(names.getOrDefault(definition.getRootObjectId(), definition.getRootObjectId()));
|
for (LimsElnDatasetCatalog.Relation relation : definition.getRelations()) {
|
result.add(names.getOrDefault(relation.getObjectId(), relation.getObjectId()));
|
}
|
return result;
|
}
|
|
private LimsElnSourceMetadata.Candidate candidate(LimsElnDatasetCatalog.Draft definition,
|
LimsElnDatasetCatalog.Selection selection) {
|
return candidate(definition, selection, new LinkedHashMap<>());
|
}
|
|
private LimsElnSourceMetadata.Candidate candidate(LimsElnDatasetCatalog.Draft definition,
|
LimsElnDatasetCatalog.Selection selection,
|
Map<String, List<LimsElnSourceMetadata.Candidate>> fieldsByObject) {
|
List<String> objectIds = new ArrayList<>();
|
objectIds.add(definition.getRootObjectId());
|
objectIds.addAll(definition.getRelations().stream().map(LimsElnDatasetCatalog.Relation::getObjectId)
|
.collect(Collectors.toList()));
|
if (selection.getStep() < 0 || selection.getStep() >= objectIds.size()) {
|
throw new DataException("Invalid source path output");
|
}
|
String objectId = objectIds.get(selection.getStep());
|
return fieldsByObject.computeIfAbsent(objectId, metadata::fields).stream()
|
.filter(item -> item.getId().equals(selection.getFieldId())).findFirst()
|
.orElseThrow(() -> new DataException("Source path field is no longer available"));
|
}
|
|
private static String returnContent(LimsElnDatasetCatalog.Selection selection) {
|
return "DISPLAY_NAME".equals(selection.getValueMode()) ? "显示名称" : "原值";
|
}
|
|
private static String scope(LimsElnDatasetCatalog.Draft definition) {
|
return definition.getConditions() == null || definition.getConditions().isEmpty()
|
? "全部检验项" : "指定范围(" + definition.getConditions().size() + " 条规则)";
|
}
|
|
private static String pathKey(String code, Integer revision) {
|
return code + "\u0000" + revision;
|
}
|
|
@Data
|
@JsonNaming(PropertyNamingStrategies.LowerCamelCaseStrategy.class)
|
public static class DataItemDraft {
|
private String propertyCode;
|
private String displayName;
|
private String sourcePathId;
|
private String outputKey;
|
private Integer expectedEditVersion;
|
}
|
|
@Data
|
@JsonNaming(PropertyNamingStrategies.LowerCamelCaseStrategy.class)
|
public static class SourcePathDraft {
|
private String code;
|
private String displayName;
|
private String rootObjectId;
|
private String targetObjectId;
|
private String fromFieldId;
|
private String targetFieldId;
|
private String outputKey;
|
private String outputFieldId;
|
private String sampleId;
|
}
|
|
@Value
|
@JsonNaming(PropertyNamingStrategies.LowerCamelCaseStrategy.class)
|
public static class Preview { Object value; }
|
|
@Value
|
@JsonNaming(PropertyNamingStrategies.LowerCamelCaseStrategy.class)
|
public static class DataItem {
|
String id;
|
String propertyCode;
|
String displayName;
|
String dataType;
|
String status;
|
boolean enabled;
|
int revision;
|
int editVersion;
|
String sourcePathId;
|
String outputKey;
|
String sourcePath;
|
String outputName;
|
String returnContent;
|
String scope;
|
String publishedAt;
|
String publishedBy;
|
}
|
|
@Value
|
@JsonNaming(PropertyNamingStrategies.LowerCamelCaseStrategy.class)
|
public static class SourcePath {
|
String id;
|
String code;
|
String displayName;
|
String status;
|
int revision;
|
int editVersion;
|
String path;
|
String scope;
|
List<PathOutput> outputs;
|
String publishedAt;
|
String publishedBy;
|
}
|
|
@Value
|
@JsonNaming(PropertyNamingStrategies.LowerCamelCaseStrategy.class)
|
public static class PathOutput {
|
String key;
|
String displayName;
|
String dataType;
|
String returnContent;
|
String path;
|
}
|
|
@Value
|
private static class SourceSelection {
|
LimsElnDatasetCatalog.Version path;
|
String dataType;
|
}
|
|
@Value
|
private static class PathDescription {
|
String path;
|
String outputName;
|
}
|
}
|