1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.mycore.datamodel.metadata;
20
21 import java.text.NumberFormat;
22 import java.util.ArrayList;
23 import java.util.HashMap;
24 import java.util.HashSet;
25 import java.util.List;
26 import java.util.Locale;
27 import java.util.Objects;
28 import java.util.stream.Collectors;
29
30 import org.apache.logging.log4j.LogManager;
31 import org.apache.logging.log4j.Logger;
32 import org.mycore.common.MCRException;
33 import org.mycore.common.MCRUtils;
34 import org.mycore.common.config.MCRConfiguration2;
35 import org.mycore.datamodel.common.MCRXMLMetadataManager;
36
37 import com.fasterxml.jackson.annotation.JsonClassDescription;
38 import com.fasterxml.jackson.annotation.JsonCreator;
39 import com.fasterxml.jackson.annotation.JsonFormat;
40 import com.fasterxml.jackson.annotation.JsonValue;
41
42
43
44
45
46
47
48
49
50
51
52
53 @JsonClassDescription("MyCoRe ObjectID in form {project}_{type}_{int32}, "
54 + "where project is a namespace and type defines the datamodel")
55 @JsonFormat(shape = JsonFormat.Shape.STRING)
56 public final class MCRObjectID implements Comparable<MCRObjectID> {
57
58
59
60 public static final int MAX_LENGTH = 64;
61
62 private static final MCRObjectIDFormat ID_FORMAT = new MCRObjectIDDefaultFormat();
63
64 private static final Logger LOGGER = LogManager.getLogger(MCRObjectID.class);
65
66
67 private static HashMap<String, Integer> lastNumber = new HashMap<>();
68
69 private static HashSet<String> VALID_TYPE_LIST;
70
71 static {
72 final String confPrefix = "MCR.Metadata.Type.";
73 VALID_TYPE_LIST = MCRConfiguration2.getPropertiesMap()
74 .entrySet()
75 .stream()
76 .filter(p -> p.getKey().startsWith(confPrefix))
77 .filter(p -> Boolean.parseBoolean(p.getValue()))
78 .map(prop -> prop.getKey().substring(confPrefix.length()))
79 .collect(Collectors.toCollection(HashSet::new));
80 }
81
82
83 private String projectId, objectType, combinedId;
84
85 private int numberPart;
86
87
88
89
90
91
92
93 MCRObjectID(String id) throws MCRException {
94 if (!setID(id)) {
95 throw new MCRException("The ID is not valid: " + id
96 + " , it should match the pattern String_String_Integer");
97 }
98 }
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120 public static synchronized MCRObjectID getNextFreeId(String baseId) {
121 return getNextFreeId(baseId, 0);
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 public static synchronized MCRObjectID getNextFreeId(String projectId, String type) {
147 return getNextFreeId(projectId + "_" + type);
148 }
149
150
151
152
153
154
155
156
157
158
159
160
161 public static synchronized MCRObjectID getNextFreeId(String baseId, int maxInWorkflow) {
162 int last = Math.max(getLastIDNumber(baseId), maxInWorkflow);
163 int numberDistance = ID_FORMAT.numberDistance();
164 int next = last + numberDistance;
165
166 int rest = next % numberDistance;
167 if (rest != 0) {
168 next += numberDistance - rest;
169 }
170
171 lastNumber.put(baseId, next);
172 String[] idParts = getIDParts(baseId);
173 return getInstance(formatID(idParts[0], idParts[1], next));
174 }
175
176
177
178
179
180
181 private static int getLastIDNumber(String baseId) {
182 int lastIDKnown = lastNumber.getOrDefault(baseId, 0);
183
184 String[] idParts = getIDParts(baseId);
185 int highestStoredID = MCRXMLMetadataManager.instance().getHighestStoredID(idParts[0], idParts[1]);
186
187 return Math.max(lastIDKnown, highestStoredID);
188 }
189
190
191
192
193
194
195
196 public static MCRObjectID getLastID(String baseId) {
197 int lastIDNumber = getLastIDNumber(baseId);
198 if (lastIDNumber == 0) {
199 return null;
200 }
201
202 String[] idParts = getIDParts(baseId);
203 return getInstance(formatID(idParts[0], idParts[1], lastIDNumber));
204 }
205
206
207
208
209
210
211
212
213
214 @JsonCreator(mode = JsonCreator.Mode.DELEGATING)
215 public static MCRObjectID getInstance(String id) {
216 return MCRObjectIDPool.getMCRObjectID(Objects.requireNonNull(id, "'id' must not be null."));
217 }
218
219
220
221
222
223
224 public static String formatID(String projectID, String type, int number) {
225 if (projectID == null) {
226 throw new IllegalArgumentException("projectID cannot be null");
227 }
228 if (type == null) {
229 throw new IllegalArgumentException("type cannot be null");
230 }
231 if (number < 0) {
232 throw new IllegalArgumentException("number must be non negative integer");
233 }
234 return projectID + '_' + type.toLowerCase(Locale.ROOT) + '_' + ID_FORMAT.numberFormat().format(number);
235 }
236
237
238
239
240
241
242
243
244
245 public static String formatID(String baseID, int number) {
246 String[] idParts = getIDParts(baseID);
247 return formatID(idParts[0], idParts[1], number);
248 }
249
250
251
252
253
254
255
256
257
258 public static String[] getIDParts(String id) {
259 return id.split("_");
260 }
261
262
263
264
265 public static List<String> listTypes() {
266 return new ArrayList<>(VALID_TYPE_LIST);
267 }
268
269
270
271
272
273
274
275
276 public static boolean isValidType(String type) {
277 return VALID_TYPE_LIST.contains(type);
278 }
279
280
281
282
283
284
285
286 public static boolean isValid(String id) {
287 if (id == null) {
288 return false;
289 }
290 String mcrId = id.trim();
291 if (mcrId.length() > MAX_LENGTH) {
292 return false;
293 }
294 String[] idParts = getIDParts(mcrId);
295 if (idParts.length != 3) {
296 return false;
297 }
298 String objectType = idParts[1].toLowerCase(Locale.ROOT).intern();
299 if (!MCRConfiguration2.getBoolean("MCR.Metadata.Type." + objectType).orElse(false)) {
300 LOGGER.warn("Property MCR.Metadata.Type.{} is not set. Thus {} cannot be a valid id", objectType, id);
301 return false;
302 }
303 try {
304 Integer numberPart = Integer.parseInt(idParts[2]);
305 if (numberPart < 0) {
306 return false;
307 }
308 } catch (NumberFormatException e) {
309 return false;
310 }
311 return true;
312 }
313
314
315
316
317
318
319
320 public String getProjectId() {
321 return projectId;
322 }
323
324
325
326
327
328
329
330 public String getTypeId() {
331 return objectType;
332 }
333
334
335
336
337
338
339
340 public String getNumberAsString() {
341 return ID_FORMAT.numberFormat().format(numberPart);
342 }
343
344
345
346
347
348
349
350 public int getNumberAsInteger() {
351 return numberPart;
352 }
353
354
355
356
357
358
359
360
361 public String getBase() {
362 return projectId + "_" + objectType;
363 }
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382 private boolean setID(String id) {
383 if (!isValid(id)) {
384 return false;
385 }
386 String[] idParts = getIDParts(id.trim());
387 projectId = idParts[0].intern();
388 objectType = idParts[1].toLowerCase(Locale.ROOT).intern();
389 numberPart = Integer.parseInt(idParts[2]);
390 this.combinedId = formatID(projectId, objectType, numberPart);
391 return true;
392 }
393
394
395
396
397
398
399
400
401
402 public boolean equals(MCRObjectID in) {
403 return this == in || (in != null && toString().equals(in.toString()));
404 }
405
406
407
408
409
410
411
412
413
414
415 @Override
416 public boolean equals(Object in) {
417 if (in instanceof MCRObjectID) {
418 return equals((MCRObjectID) in);
419 }
420 return false;
421 }
422
423 @Override
424 public int compareTo(MCRObjectID o) {
425 return MCRUtils.compareParts(this, o,
426 MCRObjectID::getProjectId,
427 MCRObjectID::getTypeId,
428 MCRObjectID::getNumberAsInteger);
429 }
430
431
432
433
434
435
436
437 @Override
438 @JsonValue
439 public String toString() {
440 return combinedId;
441 }
442
443
444
445
446
447
448
449 @Override
450 public int hashCode() {
451 return toString().hashCode();
452 }
453
454 public interface MCRObjectIDFormat {
455 int numberDistance();
456
457 NumberFormat numberFormat();
458 }
459
460 private static class MCRObjectIDDefaultFormat implements MCRObjectIDFormat {
461
462 private int numberDistance;
463
464
465
466
467
468
469 @Override
470 public int numberDistance() {
471 if (numberDistance == 0) {
472 numberDistance = MCRConfiguration2.getInt("MCR.Metadata.ObjectID.NumberDistance").orElse(1);
473 return MCRConfiguration2.getInt("MCR.Metadata.ObjectID.InitialNumberDistance").orElse(numberDistance);
474 }
475 return numberDistance;
476 }
477
478 @Override
479 public NumberFormat numberFormat() {
480 String numberPattern = MCRConfiguration2.getString("MCR.Metadata.ObjectID.NumberPattern")
481 .orElse("0000000000").trim();
482 NumberFormat format = NumberFormat.getIntegerInstance(Locale.ROOT);
483 format.setGroupingUsed(false);
484 format.setMinimumIntegerDigits(numberPattern.length());
485 return format;
486 }
487
488 }
489
490 }