View Javadoc
1   package fr.tiogars.chat.domains.taxonomy;
2   
3   import java.util.HashSet;
4   import java.util.List;
5   
6   import fr.tiogars.chat.domains.tag.entities.TagEntity;
7   import fr.tiogars.chat.domains.tag.models.CreateTagRequest;
8   import fr.tiogars.chat.domains.tag.models.UpdateTagRequest;
9   import fr.tiogars.chat.domains.tag.models.Tag;
10  import fr.tiogars.chat.domains.tag.repositories.TagRepository;
11  import fr.tiogars.chat.domains.nomenclature.entities.NomenclatureEntity;
12  import fr.tiogars.chat.domains.nomenclature.models.CreateNomenclatureRequest;
13  import fr.tiogars.chat.domains.nomenclature.models.UpdateNomenclatureRequest;
14  import fr.tiogars.chat.domains.nomenclature.models.Nomenclature;
15  import fr.tiogars.chat.domains.nomenclature.repositories.NomenclatureRepository;
16  import org.springframework.http.HttpStatus;
17  import org.springframework.stereotype.Service;
18  import org.springframework.transaction.annotation.Transactional;
19  import org.springframework.web.server.ResponseStatusException;
20  
21  @Service
22  @Transactional
23  public class TaxonomyService {
24      private final TagRepository tags;
25      private final NomenclatureRepository nomenclatures;
26  
27      public TaxonomyService(TagRepository tags, NomenclatureRepository nomenclatures) {
28          this.tags = tags;
29          this.nomenclatures = nomenclatures;
30      }
31  
32      public List<Tag> tags() {
33          return tags.findAllByOrderByCodeAsc().stream().map(Tag::from).toList();
34      }
35  
36      public Tag tag(Long id) {
37          return Tag.from(tags.findById(id).orElseThrow(() -> missing("Tag")));
38      }
39  
40      public Tag createTag(CreateTagRequest request) {
41          if (tags.existsByCode(request.code())) {
42              throw conflict("Tag code already exists.");
43          }
44          return Tag.from(tags.saveAndFlush(new TagEntity(request.code(), request.label(), request.description())));
45      }
46  
47      public Tag updateTag(Long id, UpdateTagRequest request) {
48          TagEntity entity = tags.lockById(id).orElseThrow(() -> missing("Tag"));
49          entity.update(request.label(), request.description());
50          return Tag.from(tags.saveAndFlush(entity));
51      }
52  
53      public void deleteTag(Long id) {
54          TagEntity entity = tags.lockById(id).orElseThrow(() -> missing("Tag"));
55          if (tags.countTaskReferences(id) > 0) {
56              throw conflict("Tag is referenced by a task.");
57          }
58          tags.delete(entity);
59          tags.flush();
60      }
61  
62      public List<Nomenclature> nomenclatures() {
63          return nomenclatures.findAllByOrderByCodeAsc().stream().map(Nomenclature::from).toList();
64      }
65  
66      public Nomenclature nomenclature(Long id) {
67          return Nomenclature.from(nomenclatures.findById(id).orElseThrow(() -> missing("Nomenclature")));
68      }
69  
70      public Nomenclature createNomenclature(CreateNomenclatureRequest request) {
71          nomenclatures.lockForest();
72          if (nomenclatures.existsByCode(request.code())) {
73              throw conflict("Nomenclature code already exists.");
74          }
75          NomenclatureEntity entity = new NomenclatureEntity(request.code(), request.label(), request.description());
76          entity.setParent(parent(null, request.parentId()));
77          return Nomenclature.from(nomenclatures.saveAndFlush(entity));
78      }
79  
80      public Nomenclature updateNomenclature(Long id, UpdateNomenclatureRequest request) {
81          nomenclatures.lockForest();
82          NomenclatureEntity entity = nomenclatures.findById(id).orElseThrow(() -> missing("Nomenclature"));
83          entity.setParent(parent(id, request.parentId()));
84          entity.update(request.label(), request.description());
85          return Nomenclature.from(nomenclatures.saveAndFlush(entity));
86      }
87  
88      public void deleteNomenclature(Long id) {
89          nomenclatures.lockForest();
90          NomenclatureEntity entity = nomenclatures.findById(id).orElseThrow(() -> missing("Nomenclature"));
91          if (nomenclatures.existsByParentId(id) || nomenclatures.countTaskReferences(id) > 0) {
92              throw conflict("Nomenclature is referenced by a task or child.");
93          }
94          nomenclatures.delete(entity);
95          nomenclatures.flush();
96      }
97  
98      private NomenclatureEntity parent(Long id, Long parentId) {
99          if (parentId == null) {
100             return null;
101         }
102         NomenclatureEntity parent = nomenclatures.findById(parentId)
103                 .orElseThrow(() -> invalid("Parent nomenclature does not exist."));
104         HashSet<Long> visited = new HashSet<>();
105         for (NomenclatureEntity ancestor = parent; ancestor != null; ancestor = ancestor.getParent()) {
106             if (ancestor.getId().equals(id) || !visited.add(ancestor.getId())) {
107                 throw invalid("Nomenclature parent would create a cycle.");
108             }
109         }
110         return parent;
111     }
112 
113     private static ResponseStatusException missing(String type) {
114         return new ResponseStatusException(HttpStatus.NOT_FOUND, type + " not found.");
115     }
116 
117     private static ResponseStatusException invalid(String message) {
118         return new ResponseStatusException(HttpStatus.BAD_REQUEST, message);
119     }
120 
121     private static ResponseStatusException conflict(String message) {
122         return new ResponseStatusException(HttpStatus.CONFLICT, message);
123     }
124 }