package jnpf.limsController; import cn.dev33.satoken.annotation.SaCheckLogin; import com.fasterxml.jackson.databind.PropertyNamingStrategies; import com.fasterxml.jackson.databind.annotation.JsonNaming; import jnpf.base.ActionResult; import jnpf.exception.DataException; import jnpf.limsService.provider.LimsElnReadonlyResolver.SourceFailure; import jnpf.limsService.provider.LimsElnReadonlyResolver; import jnpf.limsService.provider.LimsElnDatasetCatalog; import jnpf.limsService.provider.LimsElnDataItemManager; import jnpf.limsService.provider.LimsElnSourceCatalog; import jnpf.limsService.provider.LimsElnSourceMetadata; import jnpf.limsService.provider.LimsElnPreviewSampleCatalog; import lombok.Data; import org.springframework.web.bind.annotation.*; /** Independent configuration endpoints; business task endpoints remain unchanged. */ @RestController @RequestMapping("/eln/sources") @SaCheckLogin public class LimsElnSourceController { private final LimsElnSourceMetadata metadata; private final LimsElnDatasetCatalog datasets; private final LimsElnDataItemManager dataItems; private final LimsElnPreviewSampleCatalog previewSamples; private final LimsElnSourceCatalog properties; private final LimsElnReadonlyResolver resolver; public LimsElnSourceController(LimsElnSourceMetadata metadata, LimsElnDatasetCatalog datasets, LimsElnDataItemManager dataItems, LimsElnPreviewSampleCatalog previewSamples, LimsElnSourceCatalog properties, LimsElnReadonlyResolver resolver) { this.metadata = metadata; this.datasets = datasets; this.dataItems = dataItems; this.previewSamples = previewSamples; this.properties = properties; this.resolver = resolver; } @GetMapping("/dataItems") public ActionResult dataItems() { return ok(dataItems.items()); } @GetMapping("/sourcePaths") public ActionResult sourcePaths() { return ok(dataItems.sourcePaths()); } @PostMapping("/sourcePaths") public ActionResult createSourcePath(@RequestBody LimsElnDataItemManager.SourcePathDraft command) { return ok(dataItems.createSourcePath(command)); } @GetMapping("/previewSamples") public ActionResult previewSamples(@RequestParam(value = "keyword", required = false) String keyword) { return ok(previewSamples.search(keyword)); } @PostMapping("/dataItems") public ActionResult createDataItem(@RequestBody LimsElnDataItemManager.DataItemDraft command) { return ok(dataItems.create(command)); } @PutMapping("/dataItems/{id}") public ActionResult saveDataItem(@PathVariable("id") String id, @RequestBody LimsElnDataItemManager.DataItemDraft command) { return ok(dataItems.save(id, command)); } @PostMapping("/dataItems/{id}/preview") public ActionResult previewDataItem(@PathVariable("id") String id, @RequestBody Publication command) { return ok(dataItems.preview(id, command.getSampleId())); } @PostMapping("/dataItems/{id}/publish") public ActionResult publishDataItem(@PathVariable("id") String id, @RequestBody Publication command) { return ok(dataItems.publish(id, editVersion(command.getExpectedEditVersion()), command.getSampleId())); } @GetMapping("/objects") public ActionResult objects() { return ok(metadata.objects()); } @GetMapping("/objects/{id}/fields") public ActionResult fields(@PathVariable("id") String id) { return ok(metadata.fields(id)); } @GetMapping("/objects/{id}/fields/{fieldId}/options") public ActionResult options(@PathVariable("id") String id, @PathVariable("fieldId") String fieldId) { return ok(metadata.options(id, fieldId)); } @GetMapping("/enumObjects") public ActionResult enumObjects() { return ok(metadata.enumObjects()); } @PostMapping("/enumOptions") public ActionResult enumOptions(@RequestBody LimsElnSourceMetadata.EnumSelection selection) { return ok(metadata.enumOptions(selection)); } @GetMapping("/datasets") public ActionResult datasets() { return ok(datasets.versions()); } @GetMapping("/datasets/{id}") public ActionResult dataset(@PathVariable("id") String id) { return ok(datasets.detail(id)); } @PutMapping("/datasets/{id}") public ActionResult updateDataset(@PathVariable("id") String id, @RequestBody DatasetEdit command) { return ok(datasets.updateDraft(id, editVersion(command.getExpectedEditVersion()), command.getDraft())); } @PostMapping("/datasets/{id}/versions") public ActionResult newDatasetVersion(@PathVariable("id") String id, @RequestBody LimsElnDatasetCatalog.Draft draft) { return ok(datasets.newVersion(id, draft)); } @PostMapping("/datasets") public ActionResult createDataset(@RequestBody LimsElnDatasetCatalog.Draft draft) { return ok(datasets.createDraft(draft)); } @PostMapping("/datasets/{id}/validate") public ActionResult validateDataset(@PathVariable("id") String id) { return ok(datasets.validateStructure(id)); } @PostMapping("/datasets/{id}/preview") public ActionResult previewDataset(@PathVariable("id") String id, @RequestBody Publication command) { return ok(datasets.preview(id, command.getSampleId())); } @PostMapping("/datasets/{id}/publish") public ActionResult publishDataset(@PathVariable("id") String id, @RequestBody Publication command) { return ok(datasets.publish(id, editVersion(command.getExpectedEditVersion()), command.getSampleId())); } @GetMapping("/properties") public ActionResult properties() { return ok(properties.versions()); } @GetMapping("/properties/{id}") public ActionResult property(@PathVariable("id") String id) { return ok(properties.detail(id)); } @PutMapping("/properties/{id}") public ActionResult updateProperty(@PathVariable("id") String id, @RequestBody PropertyEdit command) { if (command.getDraft() == null) throw new DataException("Source draft is required"); return ok(properties.updateDraft(id, editVersion(command.getExpectedEditVersion()), command.getDraft().draft())); } @PostMapping("/properties/{id}/versions") public ActionResult newPropertyVersion(@PathVariable("id") String id, @RequestBody PropertyDraft command) { return ok(properties.newVersion(id, command.draft())); } @GetMapping("/properties/{code}/enabled") public ActionResult propertyEnabled(@PathVariable("code") String code) { return ok(properties.enabled(code)); } @PutMapping("/properties/{code}/enabled") public ActionResult setPropertyEnabled(@PathVariable("code") String code, @RequestBody Enabled command) { if (command.getEnabled() == null) throw new DataException("Enabled state is required"); properties.setEnabled(code, command.getEnabled()); return ok(command.getEnabled()); } @GetMapping("/catalog") public ActionResult catalog() { return ok(properties.catalog()); } @GetMapping("/references/{code}/{revision}") public ActionResult reference(@PathVariable("code") String code, @PathVariable("revision") int revision) { return ok(properties.reference(code, revision)); } @GetMapping("/references") public ActionResult references() { return ok(properties.references()); } @PostMapping("/properties") public ActionResult createProperty(@RequestBody PropertyDraft command) { return ok(properties.createDraft(command.draft())); } @PostMapping("/properties/{id}/publish") public ActionResult publishProperty(@PathVariable("id") String id, @RequestBody Publication command) { return ok(properties.publish(id, editVersion(command.getExpectedEditVersion()), command.getSampleId())); } @PostMapping("/resolve") public ActionResult resolve(@RequestBody Resolve command) { if (command.getProperties() == null || command.getProperties().size() > 50 || command.getProperties().stream().anyMatch(ref -> ref == null || ref.getRevision() == null)) { throw new DataException("Invalid source references"); } return ok(resolver.resolve(command.getTestWorkItemIds(), command.getProperties().stream() .map(ref -> new LimsElnReadonlyResolver.PropertyRef(ref.getPropertyCode(), ref.getRevision())) .collect(java.util.stream.Collectors.toList()))); } private static int editVersion(Integer value) { if (value == null || value < 0) throw new DataException("Expected edit version is required"); return value; } private static ActionResult ok(Object data) { return ActionResult.success("OK", data); } @ExceptionHandler(DataException.class) public ActionResult sourceFailure(DataException failure) { String errorCode = failure instanceof SourceFailure ? ((SourceFailure) failure).getCode() : "SOURCE_INVALID"; int status = "SOURCE_FORBIDDEN".equals(errorCode) ? 403 : "SOURCE_UNAVAILABLE".equals(errorCode) ? 503 : "SOURCE_CONFLICT".equals(errorCode) ? 409 : 400; ActionResult> response = ActionResult.fail(status, failure.getMessage()); response.setData(java.util.Collections.singletonMap("errorCode", errorCode)); return response; } @ExceptionHandler(org.springframework.dao.DataAccessException.class) public ActionResult databaseFailure(org.springframework.dao.DataAccessException failure) { Throwable cause = failure.getMostSpecificCause(); String state = cause instanceof java.sql.SQLException ? ((java.sql.SQLException) cause).getSQLState() : null; if ("42501".equals(state)) return sourceFailure(new SourceFailure("SOURCE_FORBIDDEN", "Source database access denied")); if (state != null && (state.startsWith("42") || state.startsWith("22"))) { return sourceFailure(new SourceFailure("SOURCE_INVALID", "Source configuration is no longer readable")); } if ("57014".equals(state)) return sourceFailure(new SourceFailure("SOURCE_LIMIT", "Source query timed out")); return sourceFailure(new SourceFailure("SOURCE_UNAVAILABLE", "Source database is unavailable")); } @ExceptionHandler(org.springframework.http.converter.HttpMessageNotReadableException.class) public ActionResult invalidRequest() { return sourceFailure(new SourceFailure("SOURCE_INVALID", "Invalid source request")); } @Data @JsonNaming(PropertyNamingStrategies.LowerCamelCaseStrategy.class) public static class Publication { private Integer expectedEditVersion; private String sampleId; } @Data @JsonNaming(PropertyNamingStrategies.LowerCamelCaseStrategy.class) public static class DatasetEdit { private Integer expectedEditVersion; private LimsElnDatasetCatalog.Draft draft; } @Data @JsonNaming(PropertyNamingStrategies.LowerCamelCaseStrategy.class) public static class PropertyEdit { private Integer expectedEditVersion; private PropertyDraft draft; } @Data public static class Enabled { private Boolean enabled; } @Data @JsonNaming(PropertyNamingStrategies.LowerCamelCaseStrategy.class) public static class Resolve { private java.util.List testWorkItemIds; private java.util.List properties; } @Data @JsonNaming(PropertyNamingStrategies.LowerCamelCaseStrategy.class) public static class PropertyRef { private String propertyCode; private Integer revision; } @Data @JsonNaming(PropertyNamingStrategies.LowerCamelCaseStrategy.class) public static class PropertyDraft { private String propertyCode; private String displayName; private String dataType; private LimsElnSourceCatalog.Definition definition; LimsElnSourceCatalog.Draft draft() { return new LimsElnSourceCatalog.Draft(propertyCode, displayName, dataType, definition); } } }