1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.mycore.datamodel.classifications2.impl;
20
21 import java.io.Serializable;
22 import java.net.URI;
23 import java.util.ArrayList;
24 import java.util.Collection;
25 import java.util.List;
26 import java.util.SortedSet;
27 import java.util.stream.Collectors;
28
29 import org.apache.logging.log4j.LogManager;
30 import org.apache.logging.log4j.Logger;
31 import org.hibernate.annotations.SortNatural;
32 import org.mycore.backend.jpa.MCRURIConverter;
33 import org.mycore.common.MCRException;
34 import org.mycore.datamodel.classifications2.MCRCategory;
35 import org.mycore.datamodel.classifications2.MCRCategoryID;
36 import org.mycore.datamodel.classifications2.MCRLabel;
37
38 import jakarta.persistence.Access;
39 import jakarta.persistence.AccessType;
40 import jakarta.persistence.CascadeType;
41 import jakarta.persistence.CollectionTable;
42 import jakarta.persistence.Column;
43 import jakarta.persistence.Convert;
44 import jakarta.persistence.ElementCollection;
45 import jakarta.persistence.Embedded;
46 import jakarta.persistence.Entity;
47 import jakarta.persistence.FetchType;
48 import jakarta.persistence.GeneratedValue;
49 import jakarta.persistence.GenerationType;
50 import jakarta.persistence.Id;
51 import jakarta.persistence.Index;
52 import jakarta.persistence.JoinColumn;
53 import jakarta.persistence.ManyToOne;
54 import jakarta.persistence.NamedQueries;
55 import jakarta.persistence.NamedQuery;
56 import jakarta.persistence.OneToMany;
57 import jakarta.persistence.OrderColumn;
58 import jakarta.persistence.Table;
59 import jakarta.persistence.Transient;
60 import jakarta.persistence.UniqueConstraint;
61
62
63
64
65
66
67
68
69 @Entity
70 @Table(name = "MCRCategory",
71 indexes = {
72 @Index(columnList = "ClassID, leftValue, rightValue", name = "ClassLeftRight"),
73 @Index(columnList = "leftValue", name = "ClassesRoot")
74 },
75 uniqueConstraints = {
76 @UniqueConstraint(columnNames = { "ClassID", "CategID" }, name = "ClassCategUnique"),
77 @UniqueConstraint(columnNames = { "ClassID", "leftValue" }, name = "ClassLeftUnique"),
78 @UniqueConstraint(columnNames = { "ClassID", "rightValue" }, name = "ClassRightUnique") })
79 @NamedQueries({
80 @NamedQuery(name = "MCRCategory.updateLeft",
81 query = "UPDATE MCRCategoryImpl cat SET cat.left=cat.left+:increment WHERE "
82 + "cat.id.rootID= :classID AND cat.left >= :left"),
83 @NamedQuery(name = "MCRCategory.updateRight",
84 query = "UPDATE MCRCategoryImpl cat SET cat.right=cat.right+:increment WHERE "
85 + "cat.id.rootID= :classID AND cat.right >= :left"),
86 @NamedQuery(name = "MCRCategory.commonAncestor",
87 query = "FROM MCRCategoryImpl as cat WHERE "
88 + "cat.id.rootID=:rootID AND cat.left < :left AND cat.right > :right "
89 + "ORDER BY cat.left DESC"),
90 @NamedQuery(name = "MCRCategory.byNaturalId",
91 query = "FROM MCRCategoryImpl as cat WHERE "
92 + "cat.id.rootID=:classID and (cat.id.id=:categID OR cat.id.id IS NULL AND :categID IS NULL)"),
93 @NamedQuery(name = "MCRCategory.byLabelInClass",
94 query = "FROM MCRCategoryImpl as cat "
95 + "INNER JOIN cat.labels as label "
96 + " WHERE cat.id.rootID=:rootID AND "
97 + " cat.left BETWEEN :left and :right AND "
98 + " label.lang=:lang AND "
99 + " label.text=:text"),
100 @NamedQuery(name = "MCRCategory.byLabel",
101 query = "FROM MCRCategoryImpl as cat "
102 + " INNER JOIN cat.labels as label "
103 + " WHERE label.lang=:lang AND "
104 + " label.text=:text"),
105 @NamedQuery(name = "MCRCategory.prefetchClassQuery",
106 query = MCRCategoryDTO.SELECT
107 + " WHERE cat.id.rootID=:classID ORDER BY cat.left"),
108 @NamedQuery(name = "MCRCategory.prefetchClassLevelQuery",
109 query = MCRCategoryDTO.SELECT
110 + " WHERE cat.id.rootID=:classID AND cat.level <= :endlevel ORDER BY cat.left"),
111 @NamedQuery(name = "MCRCategory.prefetchCategQuery",
112 query = MCRCategoryDTO.SELECT
113 + " WHERE cat.id.rootID=:classID AND (cat.left BETWEEN :left AND :right OR cat.left=0) ORDER BY cat.left"),
114 @NamedQuery(name = "MCRCategory.prefetchCategLevelQuery",
115 query = MCRCategoryDTO.SELECT
116 + " WHERE cat.id.rootID=:classID AND (cat.left BETWEEN :left AND :right OR cat.left=0) AND "
117 + "cat.level <= :endlevel ORDER BY cat.left"),
118 @NamedQuery(name = "MCRCategory.leftRightLevelQuery",
119 query = MCRCategoryDTO.LRL_SELECT
120 + " WHERE cat.id=:categID "),
121 @NamedQuery(name = "MCRCategory.parentQuery",
122 query = MCRCategoryDTO.SELECT
123 + " WHERE cat.id.rootID=:classID AND (cat.left < :left AND cat.right > :right OR cat.id.id=:categID) "
124 + "ORDER BY cat.left"),
125 @NamedQuery(name = "MCRCategory.rootCategs",
126 query = MCRCategoryDTO.SELECT
127 + " WHERE cat.left = 0 ORDER BY cat.id.rootID"),
128 @NamedQuery(name = "MCRCategory.rootIds", query = "SELECT cat.id FROM MCRCategoryImpl cat WHERE cat.left = 0"),
129 @NamedQuery(name = "MCRCategory.childCount",
130 query = "SELECT CAST(count(*) AS integer) FROM MCRCategoryImpl children WHERE "
131 + "children.parent=(SELECT cat.internalID FROM MCRCategoryImpl cat WHERE "
132 + "cat.id.rootID=:classID and (cat.id.id=:categID OR cat.id.id IS NULL AND :categID IS NULL))"),
133 })
134
135 @Access(AccessType.PROPERTY)
136 public class MCRCategoryImpl extends MCRAbstractCategoryImpl implements Serializable {
137
138 private static final long serialVersionUID = -7431317191711000317L;
139
140 private static Logger LOGGER = LogManager.getLogger(MCRCategoryImpl.class);
141
142 private int left, right, internalID;
143
144 int level;
145
146 public MCRCategoryImpl() {
147 }
148
149
150
151 @Id
152 @GeneratedValue(strategy = GenerationType.IDENTITY)
153 public int getInternalID() {
154 return internalID;
155 }
156
157 @Column(name = "leftValue")
158 public int getLeft() {
159 return left;
160 }
161
162 @Column(name = "rightValue")
163 public int getRight() {
164 return right;
165 }
166
167 @Column
168 public int getLevel() {
169 return level;
170 }
171
172 @Transient
173 int getPositionInParent() {
174 LOGGER.debug("getposition called for {}", getId());
175 if (parent == null) {
176 LOGGER.debug("getposition called with no parent set.");
177 return -1;
178 }
179 try {
180 int position = getParent().getChildren().indexOf(this);
181 if (position == -1) {
182
183
184 position = getPositionInParentByID();
185 }
186 return position;
187 } catch (RuntimeException e) {
188 LOGGER.error("Cannot use parent.getChildren() here", e);
189 throw e;
190 }
191 }
192
193
194 @Override
195 @Embedded
196 public MCRCategoryID getId() {
197 return super.getId();
198 }
199
200 @Override
201 @OneToMany(targetEntity = MCRCategoryImpl.class,
202 cascade = {
203 CascadeType.ALL },
204 mappedBy = "parent")
205 @OrderColumn(name = "positionInParent")
206 @Access(AccessType.FIELD)
207 public List<MCRCategory> getChildren() {
208 return super.getChildren();
209 }
210
211 @Override
212 @ElementCollection(fetch = FetchType.LAZY)
213 @CollectionTable(name = "MCRCategoryLabels",
214 joinColumns = @JoinColumn(name = "category"),
215 uniqueConstraints = {
216 @UniqueConstraint(columnNames = { "category", "lang" }) })
217 @SortNatural
218 public SortedSet<MCRLabel> getLabels() {
219 return super.getLabels();
220 }
221
222 @Override
223 @Column
224 @Convert(converter = MCRURIConverter.class)
225 public URI getURI() {
226 return super.getURI();
227 }
228
229 @ManyToOne(optional = true, targetEntity = MCRCategoryImpl.class)
230 @JoinColumn(name = "parentID")
231 @Access(AccessType.FIELD)
232 public MCRCategory getParent() {
233 return super.getParent();
234 }
235
236
237
238 @Override
239 public boolean hasChildren() {
240
241 if (children != null && children.size() > 0) {
242 return true;
243 }
244 if (right != left) {
245 return right - left > 1;
246 }
247 return super.hasChildren();
248 }
249
250 @Transient
251 private int getPositionInParentByID() {
252 int position = 0;
253 for (MCRCategory sibling : parent.getChildren()) {
254 if (getId().equals(sibling.getId())) {
255 return position;
256 }
257 position++;
258 }
259 if (LOGGER.isDebugEnabled()) {
260 StringBuilder sb = new StringBuilder("List of children of parent: ");
261 sb.append(parent.getId()).append('\n');
262 for (MCRCategory sibling : parent.getChildren()) {
263 sb.append(sibling.getId()).append('\n');
264 }
265 LOGGER.debug(sb.toString());
266
267 }
268 throw new IndexOutOfBoundsException(
269 "Position -1 is not valid: " + getId() + " parent:" + parent.getId() + " children: " + parent
270 .getChildren()
271 .stream()
272 .map(MCRCategory::getId)
273 .map(MCRCategoryID::getID)
274 .collect(Collectors.joining(", ")));
275 }
276
277
278
279
280
281 public void setChildren(List<MCRCategory> children) {
282 LOGGER.debug("Set children called for {}list'{}': {}", getId(), children.getClass().getName(), children);
283 childGuard.write(() -> setChildrenUnlocked(children));
284 }
285
286 @Override
287 protected void setChildrenUnlocked(List<MCRCategory> children) {
288 MCRCategoryChildList newChildren = new MCRCategoryChildList(root, this);
289 newChildren.addAll(children);
290 this.children = newChildren;
291 }
292
293
294
295
296
297 public void setLabels(SortedSet<MCRLabel> labels) {
298 this.labels = labels;
299 }
300
301
302
303
304
305 public void setLeft(int left) {
306 this.left = left;
307 }
308
309 public void setLevel(int level) {
310 this.level = level;
311 }
312
313
314
315
316
317 public void setRight(int right) {
318 this.right = right;
319 }
320
321
322
323
324
325
326 public void setRoot(MCRCategory root) {
327 this.root = root;
328 if (children != null) {
329 setChildren(children);
330 }
331 }
332
333 static Collection<MCRCategoryImpl> wrapCategories(Collection<? extends MCRCategory> categories, MCRCategory parent,
334 MCRCategory root) {
335 List<MCRCategoryImpl> list = new ArrayList<>(categories.size());
336 for (MCRCategory category : categories) {
337 list.add(wrapCategory(category, parent, root));
338 }
339 return list;
340 }
341
342 static MCRCategoryImpl wrapCategory(MCRCategory category, MCRCategory parent, MCRCategory root) {
343 MCRCategoryImpl catImpl;
344 if (category.getParent() != null && category.getParent() != parent) {
345 throw new MCRException("MCRCategory is already attached to a different parent.");
346 }
347 if (category instanceof MCRCategoryImpl) {
348 catImpl = (MCRCategoryImpl) category;
349
350 catImpl.parent = parent;
351 if (root == null) {
352 root = catImpl;
353 }
354 catImpl.setRoot(root);
355 if (parent != null) {
356 catImpl.level = parent.getLevel() + 1;
357 } else if (category.isCategory()) {
358 LOGGER.warn("Something went wrong here, category has no parent and is no root category: {}",
359 category.getId());
360 }
361
362 List<MCRCategory> children = new ArrayList<>(catImpl.getChildren().size());
363 children.addAll(catImpl.getChildren());
364
365 catImpl.getChildren().clear();
366
367 catImpl.getChildren().addAll(children);
368 return catImpl;
369 }
370 LOGGER.debug("wrap Category: {}", category.getId());
371 catImpl = new MCRCategoryImpl();
372 catImpl.setId(category.getId());
373 catImpl.labels = category.getLabels();
374 catImpl.parent = parent;
375 if (root == null) {
376 root = catImpl;
377 }
378 catImpl.setRoot(root);
379 catImpl.level = parent.getLevel() + 1;
380 catImpl.children = new ArrayList<>(category.getChildren().size());
381 catImpl.getChildren().addAll(category.getChildren());
382 return catImpl;
383 }
384
385 @Transient
386 MCRCategoryImpl getLeftSiblingOrOfAncestor() {
387 int index = getPositionInParent();
388 MCRCategoryImpl parent = (MCRCategoryImpl) getParent();
389 if (index > 0) {
390
391 return (MCRCategoryImpl) parent.getChildren().get(index - 1);
392 }
393 if (parent.getParent() != null) {
394
395 return parent.getLeftSiblingOrOfAncestor();
396 }
397 return parent;
398 }
399
400 @Transient
401 MCRCategoryImpl getLeftSiblingOrParent() {
402 int index = getPositionInParent();
403 MCRCategoryImpl parent = (MCRCategoryImpl) getParent();
404 if (index == 0) {
405 return parent;
406 }
407 return (MCRCategoryImpl) parent.getChildren().get(index - 1);
408 }
409
410 @Transient
411 MCRCategoryImpl getRightSiblingOrOfAncestor() {
412 int index = getPositionInParent();
413 MCRCategoryImpl parent = (MCRCategoryImpl) getParent();
414 if (index + 1 < parent.getChildren().size()) {
415
416 return (MCRCategoryImpl) parent.getChildren().get(index + 1);
417 }
418 if (parent.getParent() != null) {
419
420 return parent.getRightSiblingOrOfAncestor();
421 }
422 return parent;
423 }
424
425 @Transient
426 MCRCategoryImpl getRightSiblingOrParent() {
427 int index = getPositionInParent();
428 MCRCategoryImpl parent = (MCRCategoryImpl) getParent();
429 if (index + 1 == parent.getChildren().size()) {
430 return parent;
431 }
432
433 return (MCRCategoryImpl) parent.getChildren().get(index + 1);
434 }
435
436
437
438
439
440
441
442
443
444
445
446 public int calculateLeftRightAndLevel(int leftStart, int levelStart) {
447 int curValue = leftStart;
448 final int nextLevel = levelStart + 1;
449 setLeft(leftStart);
450 setLevel(levelStart);
451 for (MCRCategory child : getChildren()) {
452 LOGGER.debug(child.getId());
453 curValue = ((MCRCategoryImpl) child).calculateLeftRightAndLevel(++curValue, nextLevel);
454 }
455 setRight(++curValue);
456 return curValue;
457 }
458
459
460
461
462
463 public void setInternalID(int internalID) {
464 this.internalID = internalID;
465 }
466
467 public void setRootID(String rootID) {
468 if (getId() == null) {
469 setId(MCRCategoryID.rootID(rootID));
470 } else if (!getId().getRootID().equals(rootID)) {
471 setId(new MCRCategoryID(rootID, getId().getID()));
472 }
473 }
474
475 public void setCategID(String categID) {
476 if (getId() == null) {
477 setId(new MCRCategoryID(null, categID));
478 } else if (!getId().getID().equals(categID)) {
479 setId(new MCRCategoryID(getId().getRootID(), categID));
480 }
481 }
482
483 @Override
484 public int hashCode() {
485 final int prime = 31;
486 int result = 1;
487 result = prime * result + internalID;
488 result = prime * result + left;
489 result = prime * result + level;
490 result = prime * result + right;
491 result = prime * result + getId().hashCode();
492 return result;
493 }
494
495 @Override
496 public boolean equals(Object obj) {
497 if (this == obj) {
498 return true;
499 }
500 if (obj == null) {
501 return false;
502 }
503 if (getClass() != obj.getClass()) {
504 return false;
505 }
506 MCRCategoryImpl other = (MCRCategoryImpl) obj;
507 if (internalID != other.internalID) {
508 return false;
509 }
510 if (left != other.left) {
511 return false;
512 }
513 if (level != other.level) {
514 return false;
515 }
516 return right == other.right;
517 }
518
519 @Transient
520 public String getRootID() {
521 return getId().getRootID();
522 }
523
524 }