刘光辉
4 小时以前 06f314545ac3514d7ce28939920866ae8188d31c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
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;
    }
}