Skip to content

Commit e5242ef

Browse files
committed
MCR-3723 ! modernize and harmonize PI generator configuration, add unit tests
1 parent bd0c76b commit e5242ef

44 files changed

Lines changed: 2278 additions & 848 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

mycore-base/src/main/java/org/mycore/datamodel/classifications2/mapping/MCRDefaultXMappingClassificationGenerator.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,9 @@
5959
* <li> The property suffix {@link MCRXMappingClassificationGeneratorBase#EVALUATOR_KEY} can be used to
6060
* specify the evaluator used to obtain category IDs from.
6161
* <li> For the evaluator, the property suffix {@link MCRSentinel#DEFAULT_KEY} can be used to
62-
* exclude the evaluator from the configuration and use a default {@link MCRSimpleXMappingEvaluator} instead.
62+
* exclude the evaluator from the configuration and use a default {@link MCRSimpleXMappingEvaluator} instead.
6363
* <li> The property suffix {@link MCRXMappingClassificationGeneratorBase#ON_MISSING_MAPPED_CATEGORY_KEY} can be used to
64-
* specify the behaviour, when a mapped category ID is missing.
64+
* specify the behavior, when a mapped category ID is missing.
6565
* </ul>
6666
* Example:
6767
* <pre><code>

mycore-base/src/main/java/org/mycore/datamodel/classifications2/mapping/MCRXPathClassificationMappingCondition.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,13 @@
3131

3232
/**
3333
* A {@link MCRXPathClassificationMappingCondition} is a {@link Condition} that
34-
* determines the condition of a MyCoRe object based on a configurable X-Path evaluating the
34+
* determines the condition of a MyCoRe object based on a configurable XPath evaluating the
3535
* XML representation of the MyCoRe object.
3636
* <p>
3737
* The following configuration options are available:
3838
* <ul>
3939
* <li> The property suffix {@link MCRXPathClassificationMappingCondition#X_PATH_KEY} can be used to
40-
* specify the X-Path to be used.
40+
* specify the XPath to be used.
4141
* </ul>
4242
* Example:
4343
* <pre><code>
@@ -53,7 +53,7 @@ public final class MCRXPathClassificationMappingCondition implements Condition {
5353
private final String xPath;
5454

5555
public MCRXPathClassificationMappingCondition(String xPath) {
56-
this.xPath = Objects.requireNonNull(xPath, "X-Path must not be null");
56+
this.xPath = Objects.requireNonNull(xPath, "XPath must not be null");
5757
}
5858

5959
@Override

mycore-mods/src/main/java/org/mycore/mods/classification/mapping/MCRMODSXMappingClassificationGenerator.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,9 @@
5757
* <li> The property suffix {@link MCRXMappingClassificationGeneratorBase#EVALUATOR_KEY} can be used to
5858
* specify the evaluator used to obtain category IDs from.
5959
* <li> For the evaluator, the property suffix {@link MCRSentinel#DEFAULT_KEY} can be used to
60-
* exclude the evaluator from the configuration and use a default {@link MCRSimpleXMappingEvaluator} instead.
60+
* exclude the evaluator from the configuration and use a default {@link MCRSimpleXMappingEvaluator} instead.
6161
* <li> The property suffix {@link MCRXMappingClassificationGeneratorBase#ON_MISSING_MAPPED_CATEGORY_KEY} can be used to
62-
* specify the behaviour, when a mapped classification value is missing.
62+
* specify the behavior, when a mapped classification value is missing.
6363
* </ul>
6464
* Example:
6565
* <pre><code>

mycore-pi/src/main/java/org/mycore/pi/MCRGenericPIGenerator.java

Lines changed: 189 additions & 249 deletions
Large diffs are not rendered by default.

mycore-pi/src/main/java/org/mycore/pi/MCRPIGenerator.java

Lines changed: 3 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -18,59 +18,19 @@
1818

1919
package org.mycore.pi;
2020

21-
import static org.mycore.pi.MCRPIService.GENERATOR_CONFIG_PREFIX;
22-
23-
import java.util.Map;
24-
25-
import org.mycore.common.config.MCRConfigurationException;
26-
import org.mycore.common.config.annotation.MCRPostConstruction;
27-
import org.mycore.common.config.annotation.MCRRawProperties;
2821
import org.mycore.datamodel.metadata.MCRBase;
2922
import org.mycore.pi.exceptions.MCRPersistentIdentifierException;
3023

31-
public abstract class MCRPIGenerator<T extends MCRPersistentIdentifier> {
32-
33-
private String generatorID;
34-
35-
private Map<String, String> properties;
36-
37-
public final Map<String, String> getProperties() {
38-
return properties;
39-
}
40-
41-
@MCRPostConstruction
42-
public void init(String property) {
43-
generatorID = property.substring(GENERATOR_CONFIG_PREFIX.length());
44-
}
45-
46-
@MCRRawProperties(namePattern = "*", required = false)
47-
public void setProperties(Map<String, String> properties) {
48-
this.properties = properties;
49-
}
24+
public interface MCRPIGenerator<T extends MCRPersistentIdentifier> {
5025

5126
/**
5227
* generates a {@link MCRPersistentIdentifier}
5328
*
54-
* @param mcrBase the mycore object for which the identifier is generated
29+
* @param base the mycore object for which the identifier is generated
5530
* @param additional additional information dedicated to the object like a mcrpath
5631
* @return a unique persistence identifier
5732
* @throws MCRPersistentIdentifierException if something goes wrong while generating
5833
*/
59-
public abstract T generate(MCRBase mcrBase, String additional) throws MCRPersistentIdentifierException;
60-
61-
/**
62-
* checks if the property exists and throws a exception if not.
63-
* @param propertyName to check
64-
* @throws MCRConfigurationException if property does not exist
65-
*/
66-
protected void checkPropertyExists(final String propertyName) throws MCRConfigurationException {
67-
if (!getProperties().containsKey(propertyName)) {
68-
throw new MCRConfigurationException(
69-
"Missing property " + GENERATOR_CONFIG_PREFIX + getGeneratorID() + "." + propertyName);
70-
}
71-
}
34+
T generate(MCRBase base, String additional) throws MCRPersistentIdentifierException;
7235

73-
public String getGeneratorID() {
74-
return generatorID;
75-
}
7636
}

mycore-pi/src/main/java/org/mycore/pi/doi/MCRCreateDateDOIGenerator.java

Lines changed: 93 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -18,96 +18,127 @@
1818

1919
package org.mycore.pi.doi;
2020

21-
import java.util.Comparator;
21+
import static org.mycore.pi.util.MCRPIGeneratorUtils.formatCount;
22+
import static org.mycore.pi.util.MCRPIGeneratorUtils.getCountPattern;
23+
import static org.mycore.pi.util.MCRPIGeneratorUtils.getCreateDate;
24+
import static org.mycore.pi.util.MCRPIGeneratorUtils.readCountFromDatabase;
25+
2226
import java.util.Date;
2327
import java.util.HashMap;
24-
import java.util.List;
2528
import java.util.Locale;
2629
import java.util.Map;
2730
import java.util.Objects;
28-
import java.util.Optional;
2931
import java.util.concurrent.atomic.AtomicInteger;
30-
import java.util.function.Predicate;
31-
import java.util.regex.Matcher;
32+
import java.util.function.Supplier;
3233
import java.util.regex.Pattern;
3334

34-
import org.mycore.common.MCRException;
35-
import org.mycore.common.MCRPersistenceException;
36-
import org.mycore.common.config.MCRConfiguration2;
35+
import org.mycore.common.config.annotation.MCRConfigurationProxy;
36+
import org.mycore.common.config.annotation.MCRProperty;
3737
import org.mycore.datamodel.common.MCRISO8601Date;
3838
import org.mycore.datamodel.metadata.MCRBase;
39-
import org.mycore.datamodel.metadata.MCRObjectService;
4039
import org.mycore.pi.MCRPIGenerator;
41-
import org.mycore.pi.MCRPIManager;
42-
import org.mycore.pi.MCRPIRegistrationInfo;
40+
import org.mycore.pi.exceptions.MCRPersistentIdentifierException;
41+
42+
/**
43+
* {@link MCRCreateDateDOIGenerator} is a {@link MCRPIGenerator} for {@link MCRDigitalObjectIdentifier} identifiers
44+
* that generates identifiers using a given prefix and the current date and a per-date counter for the suffix.
45+
* <p>
46+
* The following configuration options are available:
47+
* <ul>
48+
* <li> The property suffix {@link MCRCreateDateDOIGenerator#DATE_FORMAT_KEY} can be used to
49+
* specify the date format to be used (optional, defaults to {@link MCRCreateDateDOIGenerator#DEFAULT_DATE_FORMAT}).
50+
* <li> The property suffix {@link MCRCreateDateDOIGenerator#PREFIX_KEY} can be used to
51+
* specify the prefix.
52+
* <li> The property suffix {@link MCRCreateDateDOIGenerator#COUNT_PRECISION_KEY} can be used to
53+
* specify number of digits to be used for the count (optional, defaults to <code>-1</code>,
54+
* which uses the natural number of digits).
55+
* </ul>
56+
* Example:
57+
* <pre><code>
58+
* [...].Class=org.mycore.pi.doi.MCRCreateDateDOIGenerator
59+
* [...].DateFormat=yyyy-MM-dd
60+
* [...].Prefix=10.1234
61+
* [...].CountPrecision=6
62+
* </code></pre>
63+
*/
64+
@MCRConfigurationProxy(proxyClass = MCRCreateDateDOIGenerator.Factory.class)
65+
public class MCRCreateDateDOIGenerator extends MCRDOIGeneratorBase {
66+
67+
public static final String DEFAULT_DATE_FORMAT = "yyyyMMdd-HHmmss";
4368

44-
public class MCRCreateDateDOIGenerator extends MCRPIGenerator<MCRDigitalObjectIdentifier> {
69+
public static final Locale DEFAULT_DATE_LOCALE = Locale.ENGLISH;
4570

46-
private static final String DATE_PATTERN = "yyyyMMdd-HHmmss";
71+
public static final String DATE_FORMAT_KEY = "DateFormat";
4772

48-
private static final String CREATE_DATE_PLACEHOLDER = "$createDate$";
73+
public static final String PREFIX_KEY = "Prefix";
4974

50-
private static final String DATE_REGEXP = CREATE_DATE_PLACEHOLDER + "-([0-9]+)";
75+
public static final String COUNT_PRECISION_KEY = "CountPrecision";
5176

5277
private static final Map<String, AtomicInteger> PATTERN_COUNT_MAP = new HashMap<>();
5378

54-
private final MCRDOIParser mcrdoiParser;
79+
private final String dateFormat;
5580

56-
private final String prefix = MCRConfiguration2.getStringOrThrow("MCR.DOI.Prefix");
81+
private final String prefix;
5782

58-
public MCRCreateDateDOIGenerator() {
59-
super();
60-
mcrdoiParser = new MCRDOIParser();
83+
private final int countPrecision;
84+
85+
private final String countPattern;
86+
87+
public MCRCreateDateDOIGenerator(MCRDOIParser parser, String dateFormat, String prefix,
88+
int countPrecision) {
89+
super(parser);
90+
this.dateFormat = Objects.requireNonNull(dateFormat, "Date format must not be null");
91+
this.prefix = Objects.requireNonNull(prefix, "Prefix must not be null");
92+
this.countPrecision = countPrecision;
93+
this.countPattern = getCountPattern(countPrecision);
6194
}
6295

6396
@Override
64-
public MCRDigitalObjectIdentifier generate(MCRBase mcrObj, String additional) {
65-
Date createdate = mcrObj.getService().getDate(MCRObjectService.DATE_TYPE_CREATEDATE);
66-
if (createdate != null) {
67-
MCRISO8601Date mcrdate = new MCRISO8601Date();
68-
mcrdate.setDate(createdate);
69-
String createDate = mcrdate.format(DATE_PATTERN, Locale.ENGLISH);
70-
final int count = getCountForCreateDate(createDate);
71-
Optional<MCRDigitalObjectIdentifier> parse = mcrdoiParser.parse(prefix + "/" + createDate + "-" + count);
72-
return parse.orElseThrow(() -> new MCRException("Error while parsing default doi!"));
73-
} else {
74-
throw new MCRPersistenceException("The object " + mcrObj.getId() + " doesn't have a createdate!");
75-
}
97+
protected String buildDOI(MCRBase base, String additional) throws MCRPersistentIdentifierException {
98+
99+
String prefixWithDate = prefix + "/" + formatDate(getCreateDate(base)) + "-";
100+
int count = getCount(Pattern.quote(prefixWithDate) + countPattern);
101+
102+
return prefixWithDate + formatCount(count, countPrecision);
103+
76104
}
77105

78-
private int getCountForCreateDate(String createDate) {
79-
return getCount(prefix + "/" + DATE_REGEXP.replace(CREATE_DATE_PLACEHOLDER, createDate));
106+
private String formatDate(Date date) {
107+
108+
MCRISO8601Date isoDate = new MCRISO8601Date();
109+
isoDate.setDate(date);
110+
111+
return isoDate.format(dateFormat, DEFAULT_DATE_LOCALE);
112+
80113
}
81114

82115
private synchronized int getCount(final String pattern) {
83-
AtomicInteger count = PATTERN_COUNT_MAP.computeIfAbsent(pattern, p -> {
84-
Pattern regExpPattern = Pattern.compile(p);
85-
Predicate<String> matching = regExpPattern.asPredicate();
86-
87-
List<MCRPIRegistrationInfo> list = MCRPIManager.getInstance()
88-
.getList(MCRDigitalObjectIdentifier.TYPE, -1, -1);
89-
90-
Comparator<Integer> integerComparator = Integer::compareTo;
91-
// extract the number of the PI
92-
Optional<Integer> highestNumber = list.stream()
93-
.map(MCRPIRegistrationInfo::getIdentifier)
94-
.filter(matching)
95-
.map(pi -> {
96-
// extract the number of the PI
97-
Matcher matcher = regExpPattern.matcher(pi);
98-
if (matcher.find() && matcher.groupCount() == 1) {
99-
String group = matcher.group(1);
100-
return Integer.parseInt(group, 10);
101-
} else {
102-
return null;
103-
}
104-
}).filter(Objects::nonNull)
105-
.max(integerComparator)
106-
.map(n -> n + 1);
107-
return new AtomicInteger(highestNumber.orElse(0));
108-
});
109-
110-
return count.getAndIncrement();
116+
return PATTERN_COUNT_MAP
117+
.computeIfAbsent(pattern, p -> readCountFromDatabase(MCRDigitalObjectIdentifier.TYPE, p))
118+
.getAndIncrement();
119+
}
120+
121+
public static class Factory implements Supplier<MCRCreateDateDOIGenerator> {
122+
123+
@MCRProperty(name = DATE_FORMAT_KEY, required = false)
124+
public String dateFormat;
125+
126+
@MCRProperty(name = PREFIX_KEY, defaultName = "MCR.DOI.Prefix")
127+
public String prefix;
128+
129+
@MCRProperty(name = COUNT_PRECISION_KEY, required = false)
130+
public String countPrecision = "-1";
131+
132+
@Override
133+
public MCRCreateDateDOIGenerator get() {
134+
return new MCRCreateDateDOIGenerator(new MCRDOIParser(), getDateFormat(), prefix,
135+
Integer.parseInt(countPrecision));
136+
}
137+
138+
private String getDateFormat() {
139+
return dateFormat != null ? dateFormat : DEFAULT_DATE_FORMAT;
140+
}
141+
111142
}
112143

113144
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/*
2+
* This file is part of *** M y C o R e ***
3+
* See https://www.mycore.de/ for details.
4+
*
5+
* MyCoRe is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* MyCoRe is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with MyCoRe. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
19+
package org.mycore.pi.doi;
20+
21+
import java.util.Date;
22+
import java.util.Objects;
23+
import java.util.function.Supplier;
24+
25+
import org.mycore.common.config.annotation.MCRConfigurationProxy;
26+
import org.mycore.common.config.annotation.MCRProperty;
27+
import org.mycore.datamodel.metadata.MCRBase;
28+
import org.mycore.pi.MCRPIGenerator;
29+
import org.mycore.pi.util.MCRFLDateScrambler;
30+
31+
/**
32+
* {@link MCRCurrentDateDOIGenerator} is a {@link MCRPIGenerator} for {@link MCRDigitalObjectIdentifier} identifiers
33+
* that generates identifiers using a given prefix and the current date (in seconds) value as the suffix.
34+
* <p>
35+
* Only one suffix per second will be generated.
36+
* <p>
37+
* The following configuration options are available:
38+
* <ul>
39+
* <li> The property suffix {@link MCRCurrentDateDOIGenerator#PREFIX_KEY} can be used to
40+
* specify the prefix.
41+
* </ul>
42+
* Example:
43+
* <pre><code>
44+
* [...].Class=org.mycore.pi.doi.MCRCurrentDateDOIGenerator
45+
* [...].Prefix=10.1234
46+
* </code></pre>
47+
*/
48+
@MCRConfigurationProxy(proxyClass = MCRCurrentDateDOIGenerator.Factory.class)
49+
public class MCRCurrentDateDOIGenerator extends MCRDOIGeneratorBase {
50+
51+
public static final String PREFIX_KEY = "Prefix";
52+
53+
private final String prefix;
54+
55+
private String lastSuffix;
56+
57+
public MCRCurrentDateDOIGenerator(MCRDOIParser parser, String prefix) {
58+
super(parser);
59+
this.prefix = Objects.requireNonNull(prefix, "Prefix must not be null");
60+
}
61+
62+
@Override
63+
protected synchronized String buildDOI(MCRBase base, String additional) {
64+
65+
Date date = new Date((System.currentTimeMillis() / 1000) * 1000);
66+
String suffix = MCRFLDateScrambler.scrambleDate(date);
67+
68+
if (suffix.equals(lastSuffix)) {
69+
try {
70+
Thread.sleep(500);
71+
} catch (InterruptedException ignored) {
72+
}
73+
return buildDOI(base, additional);
74+
}
75+
76+
lastSuffix = suffix;
77+
78+
return prefix + "/" + suffix;
79+
80+
}
81+
82+
public static class Factory implements Supplier<MCRCurrentDateDOIGenerator> {
83+
84+
@MCRProperty(name = PREFIX_KEY, defaultName = "MCR.DOI.Prefix")
85+
public String prefix;
86+
87+
@Override
88+
public MCRCurrentDateDOIGenerator get() {
89+
return new MCRCurrentDateDOIGenerator(new MCRDOIParser(), prefix);
90+
}
91+
92+
}
93+
94+
}

0 commit comments

Comments
 (0)